[์น๊ฐ๋ฐ์ ๋ด, Spring] 4์ฃผ์ฐจ ๊ฐ๋ฐ์ผ์ง(2) - <๋๋ง์ ์ ๋ ์ต> ์๋ฒ
[์น๊ฐ๋ฐ์ ๋ด, Spring] 4์ฃผ์ฐจ ๊ฐ๋ฐ์ผ์ง(1) - ๋ค์ด๋ฒ ์ผํ API ์ด์ฉํ๊ธฐ
์ด์ ํฌ์คํ ์์ <๋๋ง์ ์ ๋ ์ต>๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ค๊ณํ๋ค.
๊ธฐ๋ฅ | Method | URL | return |
ํค์๋๋ก ์ํ ๊ฒ์ํ๊ณ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ชฉ๋ก์ผ๋ก ๋ณด์ฌ์ฃผ๊ธฐ | GET | /api/search?query=๊ฒ์์ด | List<ItemDto> |
๊ด์ฌ ์ํ ๋ฑ๋กํ๊ธฐ | POST | /api/products | Product |
๊ด์ฌ ์ํ ์กฐํํ๊ธฐ | GET | /api/products | List<Product> |
๊ด์ฌ ์ํ์ ๊ด์ฌ ๊ฐ๊ฒฉ ๋ฑ๋กํ๊ณ , ์ํ ๊ฐ๊ฒฉ์ด ๋ ๋ฎ์ ๊ฒฝ์ฐ ํ์ํ๊ธฐ | PUT | /api/products/{id} | id |
1. Controller
- ProductRestController: ๊ด์ฌ ์ํ ๊ด๋ จ ์ปจํธ๋กค๋ฌ
- SearchRequestContorller: ๊ฒ์ ๊ด๋ จ ์ปจํธ๋กค๋ฌ
2. Service
- ProductService: ๊ด์ฌ ์ํ ๊ฐ๊ฒฉ ๋ณ๊ฒฝ
3. Repository
- Product: ๊ด์ฌ ์ํ ํ ์ด๋ธ
- ProductRepository: ๊ด์ฌ ์ํ ์กฐํ, ์ ์ฅ
- ProductRequestDto: ๊ด์ฌ ์ํ ๋ฑ๋กํ๊ธฐ
- ProductMypriceRequestDto: ๊ด์ฌ ๊ฐ๊ฒฉ ๋ณ๊ฒฝํ๊ธฐ
- ItemDto: ๊ฒ์ ๊ฒฐ๊ณผ ์ฃผ๊ณ ๋ฐ๊ธฐ
์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ด์ฌ ์ํ ์กฐํํ๊ธฐ -> ๊ด์ฌ ์ํ ์ ์ฅ ํ๊ธฐ -> ๊ด์ฌ ์ํ ๊ฐ๊ฒฉ ์ค์ ํ๊ธฐ ์์๋๋ก ๊ธฐ๋ฅ์ ๊ตฌํํด๋ดค๋ค.
<๋๋ง์ ์ ๋ ์ต> ์๋ฒ
๐ก Product Repository
์ฐ์ ๊ด์ฌ ์ํ ํ ์ด๋ธ์ธ Product ํด๋์ค๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค. ํ์ง๋ง ๊ทธ์ ์ Product๊ฐ ์์๋ฐ์ Timestamped ํด๋์ค๋ฅผ ๋ง๋ค์ด๋ณด์~~
@Getter
@MappedSuperclass // ๋ฉค๋ฒ ๋ณ์๊ฐ ์ปฌ๋ผ์ด ๋๋๋ก ํจ
@EntityListeners(AuditingEntityListener.class) // ๋ณ๊ฒฝ๋์์ ๋ ์๋์ผ๋ก ๊ธฐ๋ก
public abstract class Timestamped {
@CreatedDate // ์ต์ด ์์ฑ ์์
private LocalDateTime createdAt;
@LastModifiedDate // ๋ง์ง๋ง ๋ณ๊ฒฝ ์์
private LocalDateTime modifiedAt;
}
์ด๋ AuditingEntityListener๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด Application์๊ฒ ์๋ ค์ค์ผ ํ๋ค.
@EnableJpaAuditing // ์๊ฐ ์๋ ๋ณ๊ฒฝ์ด ๊ฐ๋ฅํ๋๋ก ํจ
@SpringBootApplication
public class Week04Application {
public static void main(String[] args) {
SpringApplication.run(Week04Application.class, args);
}
}
์ด์ ์ฐ์ผ๋ก Product ํด๋์ค๋ฅผ ๋ง๋ค์,, ๋ฐ๋ชจ๋ฅผ ๋ณด๋ฉด ์ ์ ์๋ฏ์ด ๊ด์ฌ ์ํ ํ ์ด๋ธ์ ์ ์ฅํด์ผ ํ ๋ฐ์ดํฐ๋ title, image, link, lprice, myprice์ด๋ค.
@Getter // get ํจ์๋ฅผ ์ผ๊ด์ ์ผ๋ก ๋ง๋ค์ด์ค๋๋ค.
@NoArgsConstructor // ๊ธฐ๋ณธ ์์ฑ์๋ฅผ ๋ง๋ค์ด์ค๋๋ค.
@Entity // DB ํ
์ด๋ธ ์ญํ
public class Product extends Timestamped{
// ID์๋ ์์ฑ ๋ฐ ์ฆ๊ฐ
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private Long id;
@Column(nullable = false)
private String title;
@Column(nullable = false)
private String image;
@Column(nullable = false)
private String link;
@Column(nullable = false)
private int lprice;
@Column(nullable = false)
private int myprice;
}
Product ํ ์ด๋ธ์ ๋ง๋ค์์ผ๋ Repository๋ ํธ๋ค๋ฅ ๋ง๋ ๋ค.
public interface ProductRepository extends JpaRepository<Product, Long> {
}
๐ก Product Controller
์ด์ ๊ด์ฌ ์ํ์ ๊ดํ ์์ฒญ์ ์ฒ๋ฆฌํ ์ปจํธ๋กค๋ฌ๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค. ์ฐ์ ์ ์ผ ๋ง๋งํด ๋ณด์ด๋ "๊ด์ฌ ์ํ ์กฐํํ๊ธฐ" api๋ถํฐ ๋ง๋ค์ด๋ณด์. productRepsitory์์ findAll์ ํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค. ๋ง๋ค๊ณ ๋์ ARC๋ก ์ด์ง์ฟต ์๋์ ํ์ธํ์..^^
@RequiredArgsConstructor // final๋ก ์ ์ธ๋ ๋ฉค๋ฒ ๋ณ์๋ฅผ ์๋์ผ๋ก ์์ฑ
@RestController // JSON์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์์ ์ ์ธ
public class ProductRestController {
private final ProductRepository productRepository;
// ๊ด์ฌ ์ํ ์กฐํํ๊ธฐ
@GetMapping("/api/products")
public List<Product> getProducts() {
return productRepository.findAll();
}
}
๋ค์์ "๊ด์ฌ ์ํ ๋ฑ๋กํ๊ธฐ"์ด๋ค. Product๋ฅผ ์๋ก ์์ฑํ๊ธฐ ์ํด์ DTO๋ฅผ ์ฌ์ฉํ๋ ๊ฒ ์ข๋ค. Product์์ id๋ฅผ ์ ์ธํ ์นผ๋ผ ๊ฐ๋ค์ ๋ฉค๋ฒ ๋ณ์๋ก ๊ฐ๋ ProductRequestDto๋ฅผ ๋ง๋ ๋ค.
@Getter
public class ProductRequestDto {
private String title;
private String link;
private String image;
private int lprice;
}
ProductRequestDto๋ฅผ ๋ง๋ค์์ผ๋ ์ด์ฉํด์ฃผ๋ ๊ฒ ์ธ์ง์์ ! Product ํด๋์ค์ ProductRequestDto๋ฅผ ์ด์ฉํ๋ ์์ฑ์๋ฅผ ์ถ๊ฐํ๋ค.
// ๊ด์ฌ ์ํ ์์ฑ ์ ์ด์ฉํจ
public Product(ProductRequestDto requestDto) {
this.title = requestDto.getTitle();
this.image = requestDto.getImage();
this.link = requestDto.getLink();
this.lprice = requestDto.getLprice();
this.myprice = 0;
}
์ด์ ProductRestController์์ ProductRequestDto๋ฅผ ์ด์ฉํด Product๋ฅผ ์์ฑํ ์ ์๋ค. ์ผ๋ฅธ ProductRestController์ ๊ด์ฌ ์ํ ๋ฑ๋ก api๋ฅผ ์ถ๊ฐํ๋ฌ ๊ฐ์^^*
// ๊ด์ฌ ์ํ ๋ฑ๋ก
@PostMapping("/api/products")
public Product createProduct(@RequestBodProductRequestDto requestDto) {
Product product = new Product(requestDto);
productRepository.save(product);
return product;
}
๐ก Product Service
๋๋์ด "๊ด์ฌ ์ํ์ ๊ด์ฌ ๊ฐ๊ฒฉ ๋ฑ๋กํ๊ณ , ์ํ ๊ฐ๊ฒฉ์ด ๋ ๋ฎ์ ๊ฒฝ์ฐ ํ์ํ๊ธฐ" ๊ธฐ๋ฅ ํ๋๋ง ๋จ์๋ค. ๊ด์ฌ ๊ฐ๊ฒฉ์ ๋ฑ๋กํ๋ ค๋ฉด ์ด ๋ํ DTO๋ฅผ ์ด์ฉํด์ผ ๋๋ค. ProductMypriceRequestDto๋ฅผ ๋ง๋ค์ด์ myprice๋ฅผ ๊ฐ๋๋ก ํ๋ค.
@Getter
public class ProductMypriceRequestDto {
private int myprice;
}
Product์์ ProductMypriceRequestDto๋ฅผ ์ด์ฉํด์ ๋ด๊ฐ ์ค์ ํ ๊ฐ๊ฒฉ ์ ๋ณด๋ฅผ update ํ๋๋ก ํจ์๋ฅผ ์ถ๊ฐํด์ค์ผ ํ๋ค. ์ด ํจ์๋ ๊ด์ฌ ๊ฐ๊ฒฉ์ ๋ณ๊ฒฝํ ๋ ์ด์ฉํ ๊ฒ์ด๋ค.
// ๊ด์ฌ ๊ฐ๊ฒฉ ๋ณ๊ฒฝ ์ ์ด์ฉํจ
public void update(ProductMypriceRequestDto requestDto) {
this.myprice = requestDto.getMyprice();
}
๊ด์ฌ ์ํ ๊ฐ๊ฒฉ์ ๋ณ๊ฒฝํ๋ ์๋น์ค๋ฅผ ๋ง๋ค์ด์ค์ผ ํ๋ค. ๋ง๋ค๊ฒ ์ ๋ง ๋์์์ด ๋์์ ์ฌ์ฌ ์ง์น๋ค... ํ์ง๋ง ์ผ๋ง ์ ๋จ์์ด..! ProductService๋ ๊ด์ฌ ์ํ์ id์ ProductMypriceRequestDto๋ฅผ ๋ฐ์์ ํด๋น ์ํ์ myprice ์ ๋ณด๋ฅผ update ํ๋ค. ๋ฌผ๋ก ์์ธ ์ฒ๋ฆฌ๋ ๋นผ๋จน์ง ์๊ณ ํด์ค์ผ ํ๋ค.
@RequiredArgsConstructor // final๋ก ์ ์ธ๋ ๋ฉค๋ฒ ๋ณ์๋ฅผ ์๋์ผ๋ก ์์ฑ
@Service // ์๋น์ค์์ ์ ์ธ
public class ProductService {
private final ProductRepository productRepository;
@Transactional // ๋ฉ์๋ ๋์์ด SQL ์ฟผ๋ฆฌ๋ฌธ์์ ์ ์ธ
public Long update(Long id, ProductMypriceRequestDto requestDto) {
Product product = productRepository.findById(id).orElseThrow(
() -> new NullPointerException("ํด๋น ์์ด๋๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค.")
);
product.update(requestDto);
return id;
}
}
๋ฝ๋์ค๋ก ์ค์ผ์ฅด๋ฌ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ค. ๋ฌผ๋ก ์๊ฐํด๋ณด๋ฉด ํ์ํ ๊ธฐ๋ฅ์ด๋ค. ์ํ ๊ฐ๊ฒฉ์ ์ธ์ ๋ ๋ณ๊ฒฝ๋ ์ ์์ผ๋๊น..! ๋ฐ๋ผ์ ๋งค์ผ ์๋ฒฝ 1์์ ๊ด์ฌ ์ํ์ title๋ก ๊ฒ์ํด์ lprice๋ฅผ ์ ๋ฐ์ดํธํ๋ ์ค์ผ์ฅด๋ฌ๋ฅผ ๋ง๋ค์๋ค.
Scheduler ํด๋์ค๋ utils ํจํค์ง์ ์์ฑํ๋ค.
@RequiredArgsConstructor // final ๋ฉค๋ฒ ๋ณ์๋ฅผ ์๋์ผ๋ก ์์ฑ
@Component // ์คํ๋ง์ด ํ์ ์ ์๋์ผ๋ก ์์ฑํ๋ ํด๋์ค ๋ชฉ๋ก์ ์ถ๊ฐ
public class Scheduler {
private final ProductRepository productRepository;
private final ProductService productService;
private final NaverShopSearch naverShopSearch;
// ์ด, ๋ถ, ์, ์ผ, ์, ์ฃผ ์์
@Scheduled(cron = "0 0 1 * * *")
public void updatePrice() throws InterruptedException {
System.out.println("๊ฐ๊ฒฉ ์
๋ฐ์ดํธ ์คํ");
// ์ ์ฅ๋ ๋ชจ๋ ๊ด์ฌ์ํ์ ์กฐํ
List<Product> productList = productRepository.findAll();
for (int i=0; i<productList.size(); i++) {
// naver ์ ํ์ผ๋ก 1์ด์ ํ ์ํ ์ฉ ์กฐํ
TimeUnit.SECONDS.sleep(1);
Product p = productList.get(i);
// i ๋ฒ์งธ ๊ด์ฌ ์ํ์ ์ ๋ชฉ์ผ๋ก ๊ฒ์์ ์คํํฉ๋๋ค.
String resultString = naverShopSearch.search(title);
List<ItemDto> itemDtoList = naverShopSearch.fromJSONtoItems(resultString);
ItemDto itemDto = itemDtoList.get(0);
// ๊ด์ฌ ์ํ ์ ๋ณด๋ฅผ ์
๋ฐ์ดํธ
Long id = p.getId();
productService.updateBySearch(id, itemDto);
}
}
}
์ ๋ณด๋ฉด ๋ง์ง๋ง์ ProductService์ updateBySearch ํจ์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์ ์ ์๋ค. ํ์ง๋ง ๋ ์ด๋ฐ ๊ฑธ ๋ง๋ ์ ์ด ์๋ค! ๊ทธ๋ผ ์ด๋ป๊ฒ ํ๋๋? ์๋น์ค์ ๊ฐ์ ๋ง๋ค์ด์ค์ผ ํ๋ค,,,
@Transactional // ๋ฉ์๋ ๋์์ด SQL ์ฟผ๋ฆฌ๋ฌธ์์ ์ ์ธ
public Long updateBySearch(Long id, ItemDto itemDto) {
Product product = productRepository.findById(id).orElseThrow(
() -> new NullPointerException("ํด๋น ์์ด๋๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค.")
);
product.updateByItemDto(itemDto);
return id;
}
๋ง์ง๋ง์ผ๋ก ์คํ๋ง ๋ถํธ์์ ์ค์ผ์ค๋ฌ๊ฐ ์๋ํ๋๋ก Application์ @EnableScheduling๋ฅผ ์ถ๊ฐํด์ผ ํ๋ค.
@EnableScheduling // ์คํ๋ง ๋ถํธ์์ ์ค์ผ์ค๋ฌ๊ฐ ์๋ํ๊ฒ ํจ
@EnableJpaAuditing
@SpringBootApplication
public class Week04Application {
public static void main(String[] args) {
SpringApplication.run(Week04Application.class, args);
}
}
๐ก ๋ง๋ฌด๋ฆฌ
์ด๋ ๊ฒ <๋๋ง์ ์ ๋ ์ต> ๋ฐฑ์๋ ๊ฐ๋ฐ์ด ๋๋ฌ๋ค!!!๐๐ ํ๋ฐํธ์๋์ html, css์ ajax๋ก ์์ฒญ์ ๋ณด๋ด๋ js ํ์ผ๋ค์ ์๋ ๊นํ์์ ํ์ธํ ์ ์๋ค.
https://github.com/mjmjmj9840/Sparta-Spring-Basic/tree/main/week04
3์ฃผ์ฐจ ๊ฐ์๊น์ง๋ ์ผ๋ ๋ฒ๋ ๋ฐ๋ผ ํ์๋๋ฐ ์ด๋ฒ ์ฃผ์ฐจ๋ถํฐ๋ ๊ฐ์๋ฅผ ๋ฃ๊ธฐ ์ ์ ๋จผ์ ์ฝ๋ฉํด๋ณด๋ ์์ผ๋ก ํ์ตํ๋ค. ๋ถ๋ช ํ ์ด์ ์ ๋น์ทํ ์ฝ๋๋ฅผ ์งฐ์๋๋ฐ๋ ์ ๊ธฐ์ต์ด ์ ๋์ ๋ฉํ ๊ตฌ๋ฆฌ๊ฐ ๋ ๊ธฐ๋ถ์ด์๋ค. ํ์ง๋ง ์ฒ์๋ถํฐ ์ํ ์๋ ์๋ ๋ฒ~ ๋คํํ ์ฒ์๋ถํฐ ๋๊น์ง ์ฝ๋๋ฅผ ์ง์ง ๋ชปํด๋ ์ด์ ์ฃผ์ฐจ ์ฝ๋๋ฅผ ์ด๋ฒ ์ฃผ์ฐจ์ ๋ง๊ฒ ์์ฉ์ ํ ์ ์์๋ค..^^ ์์ง ์๋ผ๋ฐ๊ฐ๋ฝ ํ๋ ๋ด๊ทผ ์์ค์ด์ง๋ง ์คํ๋ง์ด ์์ฒญ ๊ฐํธํ ํ๋ ์์ํฌ๋ผ๋ ๊ธฐ์ด์ด ๋ฒ์จ ๋๊ปด์ง๋ค.
์ฐธ๊ณ ์๋ฃ: ์คํ๋ฅดํ์ฝ๋ฉํด๋ฝ ์น๊ฐ๋ฐ์ ๋ด, Spring 4์ฃผ์ฐจ ๊ฐ์์๋ฃ