-라이브러리 설정
-위와 같은 과정이 끝나면 Swagger 의존성을 pom.xml에 추가
작성해놓았던 소스코드(config/SwaggerConfiguration.java)와 리소스 파일(logback-spring.xml)을 가져옴
*SwaggerConfiguration 파일을 그대로 복사하면 api() 메서드 내의 basePackage가 일치하지 않아 오류가 남
->아래와 같이 코드를 부분적으로 수정
@Configuration
@EnableSwagger2
public class SwaggerConfiguration {
@Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.springboot.jpa"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Spring Boot Open API Test with Swagger")
.description("설명 부분")
.version("1.0.0")
.build();
}
}
바뀐 부분: com.springboot.api -> com.springboot.jpa
spring.datasource.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/springboot
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
-ddl-auto에서 사용할 수 있는 옵션&설명
운영환경에서 create, create-drop, update 기능은 사용하지 않음
-> 데이터베이스의 데이터 삭제 가능성과 실수로 객체의 정보가 변경되었을 때 운영 환경의 데이터베이스 정보까지 변 경될 수 있는 위험이 있기 때문
-엔티티
: JPA에서 데이터베이스의 테이블에 대응하는 클래스
-데이터베이스 테이블
-Product.java의 엔티티 클래스
package com.springboot.jpa.data.entity;
import javax.persistence.*;
import java.time.LocalDataTime;
@Entity
@Table(name="product")
public class Product{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long number;
@Column(nullable=false)
private String name;
@Column(nullable=false)
private Integer price;
private LocalDataTime createdAt;
private LocalDataTime updatedAt;
}
@Entity
@Table
@Id
@GeneratedValue
-@GeneratedValue에서 값 생성 방식
1. GeneratedValue를 사용하지 않는 방식
2. AUTO
3. IDENTITY
4. SEQUENCE
5. TABLE
@Column
@Transient
-리포지토리: Spring Data JPA가 제공하는 인터페이스
public interface ProductRepository extends JpaRepository<Product, Long>{
}
-엔티티에 대한 인터페이스를 생성, JpaRepository를 상속받음
@NoRepositoryBean
public interface JpaRepository<T, ID>extends PagingAndSortingRepository<T, ID>, //T는 엔티티 유형, ID는 엔티티의 기본 키 유형
QueryByExampleExecutor<T>{
List<T> findAll();
List<T> findAll(Sort sort);
List<T> findAllById(Iterable<ID> ids);
<S extends T> List<S> saveAll(Iterable<S> entities);
void flush();
<S extends T> S saveAndFlush(S entity);
<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);
@Deprecated
default void deleteInBatch(Iterable<T> entities){
this.deleteAllInBatch(entities);
}
void deleteAllInBatch(Iterable<T> entities);
void deleteAllByIdBatch(Iterable<ID> ids);
void deleteAllInBatch();
@Deprecated
T getOne(ID id);
T getById(ID id);
<S extends T> List<S> findAll(Example<S> example);
<S extends T> List<S> findAll(Example<S> example, Sort sort);
}
-조회 메서드(find)에 조건으로 붙일 수 있는 기능들
DAO와 리포지토리는 역할이 비슷함.
리포지토리는 Spring Data JPA에서 제공하는 기능이기 때문에 리포지토리라는 개념을 사용하지 않고 DAO 객체로 데이터베이스에 접근함.
=> data.dao.impl 구조로 패키지 생성 후 ProductDAO 인터페이스와 ProductDAOImpl 클래스를 각각 생성
-ProductDAO 인터페이스 작성
package com.springboot.jpa.data.dao;
import com.springboot.jpa.data.data.entity.Product;
public interface ProductDAO{
Product insertProduct(Product product);
Product selectProduct(Long number);
Product updateProductName(Long number, String name) throws Exception;
void deleteProduct(Long number) throws Exception;
}
-ProductDAOImpl 인터페이스 구현체 클래스 작성
@Component
public class ProductDAOImpl implements ProductDAO{
private final ProductRepository productRepository;
@Autowired
public ProductDAOImpl(ProductRepository productRepository){
this.productRepository=productRepository;
}
@Override
public Product insertProduct(Product product){
return null;
}
@Override
public Product selectProduct(Long number){
return null;
}
@Override
public Product updateProductName(Long number, String name) throws Exception{
return null;
}
@Override
public void deleteProduct(Long number) throws Exception{
}
}
-insertProduct() 메서드 작성
@Override
public Product insertProduct(Product product){
Product savedProduct=productRepository.save(product);
return savedProduct;
}
-조회메서드 selectProduct() 메서드 작성
@Override
public Product selectProduct(Long number){
Product selectedProduct=productRepository.getById(number);
return selectedProduct;
}
getById()
findById()
-업데이트 메서드 작성
@Override
public Product updateProductName(Long number, String name) throws Exception{
Optional<Product> selectedProduct=productRepository.findById(number);
Product updatedProduct;
if (selectedProduct.isPresent()){
Product product=selectedProduct.get();
product.setName(name);
product.setUpdatedAt(LocalDateTime.now());
updatedProduct=productRepository.save(product);
}else{
throw new Exception();
}
return updatedProduct;
}
-삭제 메서드 작성
@Override
public void deleteProduct(Long number) throws Exception{
Optional<Product> selectedProduct=productRepository.findById(number);
if (selectedProduct.isPresent()){
Product product=selectedProduct.get();
productRepository.delete(product);
}else{
throw new Exception();
}
}
-ProductDto 클래스 작성
public class ProductDto{
private String name;
private int price;
private int stock;
public ProductDto(String name, int price, int stock){
this.name=name;
this.price=price;
this.stock=stock;
}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public int getPrice(){
return price;
}
public void setPrice(int price){
this.price=price;
}
public int getStock(){
return stock;
}
public void setStock(int stock){
this.stock=stock;
}
}
-ProductResponseDto 클래스 작성
public class ProductResponseDto{
private Long number;
private String name;
private int price;
private int stock;
public ProductResponseDto() {}
public ProductResponseDto(Long number, String name, int price, int stock){
this.number=number;
this.name=name;
this.price=price;
this.stock=stock;
}
public Long getNumber(){
return number;
}
public void setNumber(Long number){
this.number=number;
}
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
public int getPrice(){
return price;
}
public void setPrice(int price){
this.price=price;
}
public int setStock(){
return stock;
}
public void setStock(int stock){
this.stock=stock;
}
}
-ProductService 인터페이스 설계
public interface ProductService{
ProductResponseDto getProduct(Long number);
ProductResponseDto saveProduct(ProductDto productDto);
ProductResponseDto changeProductName(Long number, String name) throws Exception;
void deleteProduct(Long number) throws Exception;
}
-서비스 인터페이스 구현체 클래스
@Service
public class ProductServiceImpl implements ProductService{
private final ProductDAO productDAO;
@Autowired
public ProductServiceImpl(ProductDAO productDAO){
this.productDAO=productDAO;
}
@Override
public ProductResponseDto getProduct(Long number){
return null;
}
@Override
public ProductResponseDto saveProduct(ProductDto productDto){
return null;
}
@Override
public ProductResponseDto changeProductName(Long number, String name) throws Exception{
return null;
}
@Override
public void deleteProduct(Long number) throws Exception{
}
}
-getProduct() 메서드 구현
@Override
public ProductResponseDto getProduct(Long number){
Product product=productDAO.selectProduct(number);
ProductResponseDto productResponseDto=new ProductResponseDto();
productResponseDto.setNumber(product.getNumber());
productResponseDto.setName(product.getName());
productResponseDto.setPrice(product.getPrice());
productResponseDto.setStock(product.getStock());
return productResponseDto;
}
-saveProduct() 메서드 작성
@Override
public ProductResponseDto saveProduct(ProductDto productDto){
Product product=new Product();
product.setName(productDto.getName());
product.setName(product.getName());
product.setPrice(product.getPrice());
product.setStock(product.getStock());
product.setCreatedAt(LocalDateTime.now());
product.setUpdatedAt(LocalDateTime.now());
Product savedProduct=productDAO.insertProduct(product);
ProductResponseDto productResponseDto=new ProductResponseDto();
productResponseDto.setNumber(savedProduct.getNumber());
productResponseDto.setName(savedProduct.getName());
productResponseDto.setPrice(savedProduct.getPrice());
productResponseDto.setStock(savedProduct.getStock());
return productResponseDto;
}
-changeProductName() 메서드 구현
@Override
public ProductResponseDto changeProductName(Long number, String name) throws Exception{
Product changedProduct=productDAO.updateProductName(number, name);
ProductResponseDto productResponseDto=new ProductResponseDto();
productResponseDto.setNumber(changedProduct.getNumber());
productResponseDto.setName(changedProduct.getName());
productResponseDto.setName(changedProduct.getPrice());
productResponseDto.setName(changedProduct.getStock());
return productResponseDto;
}
-deleteProduct() 메서드 구현
@Override
public void deleteProduct(Long number) throws Exception{
productDAO.deleteProduct(number);
}
-ProductController 클래스 작성
@RestController
@RequestMapping("/product")
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService){
this.productService=productService;
}
@GetMapping()
public ResponseEntity<ProductResponseDto> getProduct(@RequestParam Long number){
ProductResponseDto productResponseDto=productService.getProduct(number);
return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
}
@PostMapping()
public ResponseEntity<ProductResponseDto> createProduct(@RequestBody ProductDto productDto){
ProductResponseDto productResponseDto=productService.saveProduct(productDto);
return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
}
@PutMapping()
public ResponseEntity<ProductResponseDto> changeProductName(
@RequestBody ChangeProductNameDto changeProductNameDto) throws Exception{
ProductResponseDto productResponseDto=productService.changeProductName(
changeProductNameDto.getNumber(),
changeProductNameDto.getName());
return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
}
@DeleteMapping()
public ResponseEntity<String> deleteProduct(@RequestParam Long number) throws Exception{
productService.deleteProduct(number);
return ResponseEntity.status(HttpStatus.OK).body("정상적으로 삭제되었습니다.");
}
}
-ChangeProductNameDto 클래스
package com.springboot.jpa.data.dto;
public class ChangeProductNameDto {
private Long number;
private String name;
public ChangeProductNameDto(Long number, String name){
this.number=number;
this.name=name;
}
public ChangeProductNameDto(){
}
public String getNumber(){
return this.name;
}
public void setName(String name){
this.name=name;
}
}
Hibernate:
insert
into
product
(create_at, name, price, stock, updated_at)
values
(?, ?, ?, ?, ?)
=>HeidiSQL 데이터베이스에 데이터가 추가됨
Hibernate:
select
product0_.number as number1_0_0_,
product0_.created_at as created_2_0_0),
product0_.name as name3_0_0_,
product0_.price as price4_0_0_,
product0_.stock as stock5_0_0_,
product0_.updated_at as updated_6_0_0_
from
product product0_
where
product0_.number=?
Hibernate:
select
product0_.number as number1_0_0_,
product0_.created_at as created_2_0_0),
product0_.name as name3_0_0_,
product0_.price as price4_0_0_,
product0_.stock as stock5_0_0_,
product0_.updated_at as updated_6_0_0_
from
product product0_
where
product0_.number=?
Hibernate:
update
product
set
created_at=?,
name=?,
price=?,
stock=?,
updated_at=?
where
number=?
-상품정보 삭제
Hibernate:
select
product0_.number as number1_0_0_,
product0_.created_at as created_2_0_0),
product0_.name as name3_0_0_,
product0_.price as price4_0_0_,
product0_.stock as stock5_0_0_,
product0_.updated_at as updated_6_0_0_
from
product product0_
where
product0_.number=?
Hibernate:
delete
from
product
where
number=?
-롬복의 장점
롬복 설치
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
롬복 적용
-Product 엔티티 클래스에 롬복 적용
package com.springboot.jpa.data.entity;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name="product")
public class Product {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long number;
@Column(nullable=false)
private String name;
@Column(nullable=false)
private Integer price;
@Column(nullable=false)
private Integer stock;
private LocalDataTime createdAt;
private LocalDataTime updatedAt;
}
롬복의 주요 어노테이션
-@Getter/@Setter
public String getName(){
return this.name;
}
public Integer getPrice(){
return this.price;
}
public Integer getStock(){
return this.stock;
}
public LocalDateTime getCreatedAt(){
return this.createdAt;
}
public LocalDateTime getUpdatedAt(){
return this.updatedAt;
}
public void setNumber(Long number){
this.number=number;
}
public void setName(Long name){
this.name=name;
}
public void setPrice(Long Price){
this.price=price;
}
public void setStock(Long stock){
this.stock=stock;
}
public void setCreatedAt(LocalDateTime createdAt){
this.createdAt=createdAt;
}
public void setUpdatedAt(LocalDateTime updatedAt){
this.updatedAt=updatedAt;
}
-생성자 자동 생성 어노테이션
-@ToString
@ToString(exclude="name")
@Table(name="product")
public class Product{
}
=>exclude 속성 활용한 코드
-@EqualsAndHashCode
@Entity
@EqualsAndHashCode(callSuper=true)
@Table(name="product")
public class Product extends BaseEntity{
}
=>callSuper 속성 활용한 코드
-@Data
Quiz.
1. 엔티티 관련 어노테이션으로 해당 클래스가 엔티티임을 명시하기 위한 어노테이션은?
답: @Entity
2. 리포지토리 메서드의 생성 규칙에서 ( countBy ) 는 결괏값의 개수를 추출하는 기능을 수행한다.
3.데이터베이스에 접근하기 위한 로직을 관리하기 위한 객체는?
답: DAO
4. 앞서 ProductServiceImpl에서 구현했던 저장 메서드에 해당하는 메서드는?
답: saveProduct()
5. 데이터 클래스를 생성할 때 반복적으로 사용하는 getter/setter 같은 메서드를 어노테이션으로 대체하는 기능을 제공하는 라이브러리는?
답: 롬복(lombok)
6. 롬복에서 생성자 자동 생성 어노테이션 중 매개변수가 없는 생성자를 자동 생성하는 어노테이션은?
답: NoArgsConstructor
7. 롬복의 어노테이션 중 @Getter/@Setter, @RequiredArgsConstructor, @ToString, @EqualsAndHashCode를 모두 포괄하는 어노테이션은?
답: @Data
8. 다음 코드는 ProductController 클래스의 일부이다. 빈칸에 들어갈 코드는?
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService){
this.productService=productService;
}
@GetMapping()
public ResponseEntity<ProductResponseDto> getProduct(@RequestParam Long number){
ProductResponseDto productResponseDto=productService.getProduct(number);
return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
}
@PostMapping()
public ResponseEntity<ProductResponseDto> createProduct(@RequestBody ProductDto productDto){
ProductResponseDto productResponseDto=productService.saveProduct(productDto);
return ResponseEntity.status(HttpStatus.OK).body(productResponseDto);
}
}
9. 다음은 pom.xml 파일의 롬복 의존성을 주입하는 코드의 일부이다. 빈칸에 들어갈 코드는?
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
[출처] 장정우, 『스프링 부트 핵심가이드 스프링 부트를 활용한 애플리케이션 개발 실무』, 위키북스(2022), p104-158.
ⓒjiwon
[스프링3] 8장. Spring Data JPA 활용 (0) | 2023.11.24 |
---|---|
[스프링 3] 07 테스트 코드 작성하기 (0) | 2023.11.17 |
[스프링3] 6장. 데이터베이스 연동 - 1 (0) | 2023.11.03 |
[스프링3] 5장. API를 작성하는 다양한 방법 (0) | 2023.11.03 |
[스프링3] 4장. 스프링 부트 애플리케이션 개발하기 (1) | 2023.10.13 |