π κΈ°λ³Έ μ
ν
π application.yml
# server port config
server:
port: 8001
# DB config
spring:
datasource:
driver-class-name: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@localhost:1521:xe
username: C##GREEDY
password: GREEDY
# JPA config
jpa:
generate-ddl: false # create tableμ ν λλ§ true(κΈ°λ³Έκ°)
show-sql: true # JPAκ° μννλ SQLꡬ문μ μ½μμμ νμΈ κ°λ₯
database: oracle
properties:
hibernate:
'[format_sql]': true # SQLμ κ°ννμ¬ λ³΄κΈ°μ’κ² μΆλ ₯
π pom.xml
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.1.1</version>
</dependency>
π BeanConfiguration
@Configuration
public class BeanConfiguration {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
public class PagingButtonInfo {
private int currentPage;
private int startPage;
private int endPage;
}
π Pagenation
public class Pagenation {
public static PagingButtonInfo getPagingButtonInfo(Page page) {
int currentPage = page.getNumber() + 1;
int defaultButtonCount = 10;
int startPage;
int endPage;
startPage = (int) (Math.ceil((double) currentPage / defaultButtonCount) - 1) * defaultButtonCount + 1;
endPage = startPage + defaultButtonCount - 1;
if(page.getTotalPages() < endPage)
endPage = page.getTotalPages();
if(page.getTotalPages() == 0 && endPage == 0)
endPage = startPage;
return new PagingButtonInfo(currentPage, startPage, endPage);
}
}
π Template (views)
π main.html
<h1 align="center">π± λ‘μ λ°±νμ π±</h1>
<div align="center">
<button onclick="location.href='/product/todays'">μ€λμ μν</button>
<button onclick="location.href='/product/list'">μ 체 μν</button>
<button onclick="location.href='/product/regist'">μν λ±λ‘</button>
<button onclick="location.href='/product/modify'">μν μμ </button>
<button onclick="location.href='/product/remove'">μν μμ </button>
</div>
π todays.html
<h1 align="center">π μ€λμ μν π</h1>
<table align="center" border="1">
<tr>
<th>μνλ²νΈ</th>
<th>μνλͺ
</th>
<th>μν ν맀κ°</th>
<th>λ±λ‘μΌ</th>
<th>μ μ‘°μ¬</th>
<th>ν맀μ¬λΆ</th>
</tr>
<tr th:each="product : ${ productList }">
<td th:text="${ product.productNo }"></td>
<td th:text="${ product.productName }"></td>
<td th:text="${ #numbers.formatInteger(product.productPrice, 3, 'COMMA') } + μ"></td>
<td th:text="${ product.date }"></td>
<td th:text="${ product.supplierName }"></td>
<td th:text="${ product.orderableStatus }"></td>
</tr>
</table>
π list.html
<h1 align="center">π€ All Product π€</h1>
<div class="flex">
<div align="left" class="searchBox">
<form action="/product/search" method="get">
<input type="text" placeholder="μνλͺ
μ
λ ₯ :)" name="keyword">
<input type="submit" value="κ²μ">
</form>
</div>
<div align="right" class="dateBox">
<form action="/product/after" method="get">
<input type="date" name="dateAfter">
<input type="submit" value="μ΄ν μν μ‘°ν">
</form>
</div>
</div>
<table align="center" border="1">
<tr>
<th>μνλ²νΈ</th>
<th>μνλͺ
</th>
<th>μν ν맀κ°</th>
<th>λ±λ‘μΌ</th>
<th>μ μ‘°μ¬</th>
<th>ν맀μ¬λΆ</th>
</tr>
<tr th:each="product : ${ productList }">
<td th:text="${ product.productNo }"></td>
<td th:text="${ product.productName }"></td>
<td th:text="${ #numbers.formatInteger(product.productPrice, 3, 'COMMA') } + μ"></td>
<td th:text="${ product.date }"></td>
<td th:text="${ product.supplierName }"></td>
<td th:text="${ product.orderableStatus }"></td>
</tr>
</table>
<div align="center" id="paging" >
<button th:onclick="'location.href=\'/product/list?page=' + @{${paging.startPage}} + '\''">ββ</button>
<button th:onclick="'location.href=\'/product/list?page=' + @{${paging.currentPage - 1}} + '\''"
th:disabled="${ productList.first }">β</button>
<th:block th:each="page : ${ #numbers.sequence(paging.startPage, paging.endPage)}">
<button th:onclick="'location.href=\'/product/list?page=' + @{${page}} + '\''"
th:text="${ page }"
th:disabled="${ paging.currentPage eq page }">
</button>
</th:block>
<button th:onclick="'location.href=\'/product/list?page=' + @{${paging.currentPage + 1}} + '\''"
th:disabled="${ productList.last }">βΆ</button>
<button th:onclick="'location.href=\'/product/list?page=' + @{${paging.endPage}} + '\''">βΆβΆ</button>
</div>
<script>
if(window.location.hash === '#success-regist' || window.location.hash === '#success-remove'){
alert('[[${message}]]');
}
</script>
π search.html
<h1 align="center" th:text="'π€ Products searched with ' + ${ keyword } + ' π€'"></h1>
<div class="flex">
<div align="left" class="searchBox">
<form action="/product/search" method="get">
<input type="text" placeholder="μνλͺ
μ
λ ₯ :)" name="keyword">
<input type="submit" value="κ²μ">
</form>
</div>
<div align="right" class="dateBox">
<form action="/product/after" method="get">
<input type="date" name="dateAfter">
<input type="submit" value="μ΄ν μν μ‘°ν">
</form>
</div>
</div>
<table align="center" border="1">
<tr>
<th>μνλ²νΈ</th>
<th>μνλͺ
</th>
<th>μν ν맀κ°</th>
<th>λ±λ‘μΌ</th>
<th>μ μ‘°μ¬</th>
<th>ν맀μ¬λΆ</th>
</tr>
<tr th:each="product : ${ productList }">
<td th:text="${ product.productNo }"></td>
<td th:text="${ product.productName }"></td>
<td th:text="${ #numbers.formatInteger(product.productPrice, 3, 'COMMA') } + μ"></td>
<td th:text="${ product.date }"></td>
<td th:text="${ product.supplierName }"></td>
<td th:text="${ product.orderableStatus }"></td>
</tr>
</table>
<div align="center" id="paging" >
<button th:onclick="'location.href=\'/product/search?keyword=' + @{${ keyword }} + '&page=' + @{${paging.startPage}} + '\''">ββ</button>
<button th:onclick="'location.href=\'/product/search?keyword=' + @{${ keyword }} + '&page=' + @{${paging.currentPage - 1}} + '\''"
th:disabled="${ productList.first }">β</button>
<th:block th:each="page : ${ #numbers.sequence(paging.startPage, paging.endPage)}">
<button th:onclick="'location.href=\'/product/search?keyword=' + @{${ keyword }} + '&page=' + @{${page}} + '\''"
th:text="${ page }"
th:disabled="${ paging.currentPage eq page }">
</button>
</th:block>
<button th:onclick="'location.href=\'/product/search?keyword=' + @{${ keyword }} + '&page=' + @{${paging.currentPage + 1}} + '\''"
th:disabled="${ productList.last }">βΆ</button>
<button th:onclick="'location.href=\'/product/search?keyword=' + @{${ keyword }} + '&page=' + @{${paging.endPage}} + '\''">βΆβΆ</button>
</div>
π after.html
<h1 align="center">π€ <span th:text="${ dateAfter }"></span> μ΄ν λ±λ‘λ μνλ€ π€</h1>
<div class="flex">
<div align="left" class="searchBox">
<form action="/product/search" method="get">
<input type="text" placeholder="μνλͺ
μ
λ ₯ :)" name="keyword">
<input type="submit" value="κ²μ">
</form>
</div>
<div align="right" class="dateBox">
<form action="/product/after" method="get">
<input type="date" name="dateAfter">
<input type="submit" value="μ΄ν μν μ‘°ν">
</form>
</div>
</div>
<table align="center" border="1">
<tr>
<th>μνλ²νΈ</th>
<th>μνλͺ
</th>
<th>μν ν맀κ°</th>
<th>λ±λ‘μΌ</th>
<th>μ μ‘°μ¬</th>
<th>ν맀μ¬λΆ</th>
</tr>
<tr th:each="product : ${ productList }">
<td th:text="${ product.productNo }"></td>
<td th:text="${ product.productName }"></td>
<td th:text="${ #numbers.formatInteger(product.productPrice, 3, 'COMMA') } + μ"></td>
<td th:text="${ product.date }"></td>
<td th:text="${ product.supplierName }"></td>
<td th:text="${ product.orderableStatus }"></td>
</tr>
</table>
<div align="center" id="paging" >
<button th:onclick="'location.href=\'/product/after?dateAfter=' + @{${ dateAfter }} + '&page=' + @{${paging.startPage}} + '\''">ββ</button>
<button th:onclick="'location.href=\'/product/after?dateAfter=' + @{${ dateAfter }} + '&page=' + @{${paging.currentPage - 1}} + '\''"
th:disabled="${ productList.first }">β</button>
<th:block th:each="page : ${ #numbers.sequence(paging.startPage, paging.endPage)}">
<button th:onclick="'location.href=\'/product/after?dateAfter=' + @{${ dateAfter }} + '&page=' + @{${page}} + '\''"
th:text="${ page }"
th:disabled="${ paging.currentPage eq page }">
</button>
</th:block>
<button th:onclick="'location.href=\'/product/after?dateAfter=' + @{${ dateAfter }} + '&page=' + @{${paging.currentPage + 1}} + '\''"
th:disabled="${ productList.last }">βΆ</button>
<button th:onclick="'location.href=\'/product/after?dateAfter=' + @{${ dateAfter }} + '&page=' + @{${paging.endPage}} + '\''">βΆβΆ</button>
</div>
π regist.html
<h3 align="center">μ κ· μν λ±λ‘</h3>
<form align="center" action="/product/regist" method="post">
<label>μνλͺ
: </label><input type="text" name="productName"><br>
<label>μν μκ° : </label><input type="number" name="productPrice"><br>
<label>μ μ‘°μ¬ : </label>
<select name="supplierName">
<option value="SAMSUNG">μΌμ±</option>
<option value="LG">LG</option>
<option value="APPLE">Apple</option>
<option value="PHILIPS">Philips</option>
<option value="DYSON">Dyson</option>
<option value="DAEWOO">λμ°</option>
<option value="HYUNDAI">νλ</option>
</select><br>
<label>ν맀 μν : </label>
<select name="orderableStatus">
<option value="Y">ν맀κ°λ₯</option>
<option value="N">ν맀λΆκ°</option>
</select><br>
<input type="submit" value="λ±λ‘">
</form>
π modify.html
<h3 align="center">μν μμ </h3>
<form align="center" action="/product/modify" method="post">
<label>μμ ν μν μ ν : </label>
<select name="productNo" id="productNo"></select><br>
<label>μνλͺ
: </label>
<input type="text" name="productName"><br>
<label>μν μκ° : </label>
<input type="number" name="productPrice"><br>
<label>μ μ‘°μ¬ : </label>
<select name="supplierName">
<option value="SAMSUNG">μΌμ±</option>
<option value="LG">LG</option>
<option value="APPLE">Apple</option>
<option value="PHILIPS">Philips</option>
<option value="DYSON">Dyson</option>
<option value="DAEWOO">λμ°</option>
<option value="HYUNDAI">νλ</option>
</select><br>
<label>ν맀 μν : </label>
<select name="orderableStatus">
<option value="Y">ν맀κ°λ₯</option>
<option value="N">ν맀λΆκ°</option>
</select><br>
<input type="submit" value="μμ ">
</form>
<script>
$(function(){
$.ajax({
url : '/product/product',
success : function(data) {
console.log(data);
let html = '';
for(let index in data) {
html += `<option value='${data[index].productNo}'>[${data[index].productNo}] ${data[index].productName}</option>`;
}
document.querySelector("#productNo").insertAdjacentHTML('beforeend', html);
},
error : function(xhr) {
console.log(xhr);
}
});
})
</script>
π modified.html
<h1 align="center">π€ μμ λ μν νμΈ π€</h1>
<table align="center" border="1">
<tr>
<th>μνλ²νΈ</th>
<th>μνλͺ
</th>
<th>μν ν맀κ°</th>
<th>λ±λ‘μΌ</th>
<th>μ μ‘°μ¬</th>
<th>ν맀μ¬λΆ</th>
</tr>
<tr>
<td th:text="${ product.productNo }"></td>
<td th:text="${ product.productName }"></td>
<td th:text="${ #numbers.formatInteger(product.productPrice, 3, 'COMMA') } + μ"></td>
<td th:text="${ product.date }"></td>
<td th:text="${ product.supplierName }"></td>
<td th:text="${ product.orderableStatus }"></td>
</tr>
</table>
<script>
if(window.location.hash === '#success-modify'){
alert('[[${message}]]');
}
</script>
π remove.html
<h3 align="center">μν μμ </h3>
<form align="center" action="/product/remove" method="post">
<label>μμ ν μν μ ν : </label>
<select name="productNo" id="productNo"></select><br>
<input type="submit" value="μμ ">
</form>
<script>
$(function(){
$.ajax({
url : '/product/product',
success : function(data) {
console.log(data);
let html = '';
for(let index in data) {
html += `<option value='${data[index].productNo}'>${data[index].productName}</option>`;
}
document.querySelector("#productNo").insertAdjacentHTML('beforeend', html);
},
error : function(xhr) {
console.log(xhr);
}
});
})
</script>
π Entity
π Product
@Entity(name="product")
@Table(name="TBL_PRODUCT")
@SequenceGenerator(
name="PRODUCT_SEQUENCE_GENERATOR",
sequenceName="SEQ_PRODUCT_NO",
initialValue=1,
allocationSize=1
)
public class Product {
@Id
@Column(name="PRODUCT_NO")
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="PRODUCT_SEQUENCE_GENERATOR")
private int productNo;
@Column(name="PRODUCT_NAME", nullable=false)
private String productName;
@Column(name="PRODUCT_PRICE", nullable=false)
private int productPrice;
@Column(name="RELEASE_DATE", nullable=false)
private Date date;
@Column(name="SUPPLIER_NAME", nullable=false)
@Enumerated(EnumType.STRING)
private SupplierType supplierName;
@Column(name="ORDERABLE_STATUS", nullable=false)
private String orderableStatus;
}
π SupplierType
public enum SupplierType {
SAMSUNG, LG, APPLE, PHILIPS, DYSON, DAEWOO, HYUNDAI
}
π DTO
π ProductDTO
public class ProductDTO {
private int productNo;
private String productName;
private int productPrice;
private Date date;
private SupplierType supplierName;
private String orderableStatus;
}
π Controller
π MainController
@Controller
public class MainController {
@GetMapping(value = {"/", "/main"})
public String main() {
return "main/main";
}
}
π ProductController
@Slf4j
@Controller
@RequestMapping("/product")
public class ProductController {
private final ProductService productService;
public ProductController (ProductService productService) {
this.productService = productService;
}
@GetMapping("/todays")
public String todaysPage(Model model) {
List<ProductDTO> productList = productService.findProductList();
Collections.shuffle(productList);
List<ProductDTO> todaysProductlist = productList.subList(0, 3);
log.info("todaysProductlist : {}", todaysProductlist);
model.addAttribute("productList", todaysProductlist);
return null;
}
@GetMapping("/list")
public String listPage(@PageableDefault Pageable pageable, Model model) {
Page<ProductDTO> productList = productService.findProductList(pageable);
PagingButtonInfo paging = Pagenation.getPagingButtonInfo(productList);
model.addAttribute("paging", paging);
model.addAttribute("productList", productList);
return "product/list";
}
@GetMapping("/search")
public String searchProduct(@PageableDefault Pageable pageable, @RequestParam(name="keyword") String keyword, Model model) {
log.info(keyword);
Page<ProductDTO> productList = productService.searchProductList(pageable, keyword);
PagingButtonInfo paging = Pagenation.getPagingButtonInfo(productList);
model.addAttribute("keyword", keyword);
model.addAttribute("paging", paging);
model.addAttribute("productList", productList);
return "product/search";
}
@GetMapping("/after")
public String searchDateAfterProduct(@PageableDefault Pageable pageable, @RequestParam(name="dateAfter") Date dateAfter, Model model) {
Page<ProductDTO> productList = productService.searchDateAfterProductList(pageable, dateAfter);
PagingButtonInfo paging = Pagenation.getPagingButtonInfo(productList);
model.addAttribute("dateAfter", dateAfter);
model.addAttribute("paging", paging);
model.addAttribute("productList", productList);
return "product/after";
}
@GetMapping("/regist")
public void registPage() {}
@PostMapping("/regist")
public String registNewProduct(ProductDTO newProduct, RedirectAttributes rttr) {
productService.saveNewProduct(newProduct);
rttr.addFlashAttribute("message", "μν λ±λ‘ μ±κ³΅! λ±λ‘λ μνμ νμΈν΄λ³΄μΈμ π€©");
return "redirect:/product/list#success-regist";
}
@GetMapping("/modify")
public void modifyPage() {}
@GetMapping(value="product", produces="application/json; charset=UTF-8")
@ResponseBody
public List<ProductDTO> findProductList() {
return productService.findProductList();
}
@PostMapping("/modify")
public String modifyProduct(ProductDTO product, RedirectAttributes rttr) {
productService.modifyProduct(product);
rttr.addFlashAttribute("message", "μν μμ μ±κ³΅! μμ λ μνμ νμΈνμΈμ π");
return "redirect:/product/" + product.getProductNo() + "#success-modify";
}
@GetMapping("/{productNo}")
public String modifiedPage(@PathVariable int productNo, Model model) {
ProductDTO product = productService.findProductByCode(productNo);
model.addAttribute("product", product);
return "/product/modified";
}
@GetMapping("/remove")
public void removePage() {}
@PostMapping("/remove")
public String removeProduct(ProductDTO product, RedirectAttributes rttr) {
productService.deleteProduct(product);
rttr.addFlashAttribute("message", "μν μμ μ±κ³΅! π");
return "redirect:/product/list#success-remove";
}
}
π Service
π ProductService
@Slf4j
@Service
public class ProductService {
private final ProductRepository productRepository;
private final ModelMapper modelMapper;
public ProductService(ProductRepository productRepository, ModelMapper modelMapper) {
this.productRepository = productRepository;
this.modelMapper = modelMapper;
}
public List<ProductDTO> findProductList() {
List<Product> productList = productRepository.findAll(Sort.by("productNo"));
return productList.stream().map(menu -> modelMapper.map(menu, ProductDTO.class)).collect(Collectors.toList());
}
public Page<ProductDTO> findProductList(Pageable pageable) {
pageable = PageRequest.of(pageable.getPageNumber() <= 0 ? 0 : pageable.getPageNumber() -1,
pageable.getPageSize(),
Sort.by("productNo").descending());
Page<Product> productList = productRepository.findAll(pageable);
return productList.map(product -> modelMapper.map(product, ProductDTO.class));
}
public Page<ProductDTO> searchProductList(Pageable pageable, String keyword) {
pageable = PageRequest.of(pageable.getPageNumber() <= 0 ? 0 : pageable.getPageNumber() -1,
pageable.getPageSize(),
Sort.by("productNo").descending());
Page<Product> productList = productRepository.findProductByContaining(pageable, keyword);
log.info("productList : {}", productList);
return productList.map(menu -> modelMapper.map(menu, ProductDTO.class));
}
public Page<ProductDTO> searchDateAfterProductList(Pageable pageable, Date dateAfter) {
pageable = PageRequest.of(pageable.getPageNumber() <= 0 ? 0 : pageable.getPageNumber() -1,
pageable.getPageSize(),
Sort.by("date").descending());
Page<Product> productList = productRepository.findByDateAfter(dateAfter, pageable);
log.info("productList : {}", productList);
return productList.map(menu -> modelMapper.map(menu, ProductDTO.class));
}
@Transactional
public void saveNewProduct(ProductDTO newProduct) {
newProduct.setDate(new java.sql.Date(System.currentTimeMillis()));
int charge = (int) (newProduct.getProductPrice() * 0.1);
int tax = (int) (newProduct.getProductPrice() * 0.05);
newProduct.setProductPrice(newProduct.getProductPrice() + charge + tax);
productRepository.save(modelMapper.map(newProduct, Product.class));
}
@Transactional
public void modifyProduct(ProductDTO product) {
Product foundProduct = productRepository.findById(product.getProductNo()).orElseThrow(IllegalArgumentException::new);
int charge = (int) (product.getProductPrice() * 0.1);
int tax = (int) (product.getProductPrice() * 0.05);
foundProduct.setProductName(product.getProductName());
foundProduct.setProductPrice(product.getProductPrice() + charge + tax);
foundProduct.setSupplierName(product.getSupplierName());
foundProduct.setOrderableStatus(product.getOrderableStatus());
}
public ProductDTO findProductByCode(int productNo) {
Product product = productRepository.findById(productNo).orElseThrow(IllegalArgumentException::new);
return modelMapper.map(product, ProductDTO.class);
}
@Transactional
public void deleteProduct(ProductDTO product) {
productRepository.deleteById(product.getProductNo());
}
}
π Repository
π ProductRepository
public interface ProductRepository extends JpaRepository<Product, Integer> {
@Query("SELECT p FROM product p WHERE p.productName LIKE %:keyword%")
public Page<Product> findProductByContaining(Pageable pageable, String keyword);
Page<Product> findByDateAfter(Date dateAfter, Pageable pageable);
}