Spring Boot (9) JPA 기본 기능 실습하기

넙데데맨·2022년 8월 8일
0

클래스 구성

Entity

DB에 테이블에 대응하는 클래스

@Entity

해당 클래스가 엔티티임 명시하며 테이블과 매핑된다.

@Table

클래스 이름과 테이블의 이름을 다르게 지정할 시 사용

@Id

기본키 역할 사용할 칼럼(필드) 설정

@GeneratedValue

기본키 자동 생성방법 결정

@Column

엔티티 클래스 필드는 자동으로 칼럼 매핑
필드에 설정을 더할 때 사용

Repository

엔티티가 생성한 데이터베이스에 접근하는 데 사용
Spring Data JPA가 제공하는 인터페이스
JpaRepository를 상속받는다.

DAO

데이터베이스에 접근하기 위한 로직 관리 객체
인터페이스 - 구현체 방식으로 구현
Repository와 비슷한 역할
Repository의 메소드를 사용해 원하는 데로 로직 관리

DTO

단순 데이터 교환용 객체
빌더 패턴을 사용해 생성자 사용 시 유연성 확보 가능

Service

DAO, Repository를 통해 핵심 기능을 전달하도록 구성

Controller

클라이언트로부터 요청을 받아 해당 요청에 대해 적절한 메서드를 호출해서 결과값을 받는다.

구현

Entity

@Entity
@Table(name="product")
public class Product { // 엔티티 객체 메소드
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long number;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private Integer price;
    @Column(nullable = false)
    private Integer stock;
    private LocalDateTime createAt;
    private LocalDateTime updateAt;

    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 Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }

    public Integer getStock() {
        return stock;
    }

    public void setStock(Integer stock) {
        this.stock = stock;
    }

    public LocalDateTime getCreateAt() {
        return createAt;
    }

    public void setCreateAt(LocalDateTime createAt) {
        this.createAt = createAt;
    }

    public LocalDateTime getUpdateAt() {
        return updateAt;
    }

    public void setUpdateAt(LocalDateTime updateAt) {
        this.updateAt = updateAt;
    }
}

MySQL 사용 시 Boolean 타입이 없기 때문에 @Column 타입의 columnDefinition 값을 "TINYINT"로 설정해줘야 한다.

MySQL의 datetime 타입을 @Temporal을 사용해 java.util.Date 타입으로 매핑해서 사용해야한다.

@Column(name="open_status", columnDefinition = "TINYINT", length=1)
@Temporal(TemporalType.TIMESTAMP)
private Date open_date = null;  

Repository

public interface ProductRepository extends JpaRepository<Product,Long> { // JpaRepository 메소드 제공
}

DAO

@Component
public class ProductDAOImpl implements ProductDAO {
    ProductRepository productRepository;
    @Autowired
    public ProductDAOImpl(ProductRepository productRepository){
        this.productRepository = productRepository;
    }
    
    @Override
    public Product insertProduct(Product product) {
        Product savedProduct = productRepository.save(product);
        return savedProduct;
    }

    @Override
    public Product selectProduct(Long number) {
        Product selectedProduct = productRepository.getReferenceById(number); // 해당 엔티티로 리턴
        return selectedProduct;
    }

    @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.setUpdateAt(LocalDateTime.now());

            updatedProduct = productRepository.save(product);
        }else {
            throw new Exception();
        }
        return updatedProduct;
    }
    // getById 데이터 존재하지 않을 시 EntityNotFoundException 발생
    // findById는 영속성 컨텍스트에 값이 없다면 실제 DB에서 데이터 조회
    @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();
        }
    }
}

DTO

ProductDto

public class ProductDto {
    private String name;
    private int price;
    private int stock;
}

ProductResponseDto

public class ProductResponseDto {
    private Long number;
    private String name;
    private int price;
    private int stock;
}

ChangeProductNameDto

public class ChangeProductNameDto {
    private Long number;
    private String name;
}

Service

@Service
public class ProductServiceImpl implements ProductService {
    private final ProductDAO productDAO;

    @Autowired
    public ProductServiceImpl(ProductDAO productDAO){
        this.productDAO = productDAO;
    }
    @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;
    }

    @Override
    public void saveProduct(ProductDto productDto) {
        Product product = new Product();
        product.setName(productDto.getName());
        product.setPrice(productDto.getPrice());
        product.setStock(productDto.getStock());
        product.setCreateAt(LocalDateTime.now());
        product.setUpdateAt(LocalDateTime.now());

        Product savedProduct = productDAO.insertProduct(product);

    }

    @Override
    public void changeProductName(Long number, String name) throws Exception {
        Product changedProduct = productDAO.updateProductName(number, name);

    }

    @Override
    public void deleteProduct(Long number) throws Exception {
        productDAO.deleteProduct(number);
    }
}

Controller

@RestController
@RequestMapping("/product")
public class ProductController {
    private final ProductService productService;
    @Autowired
    public ProductController(ProductService productService){
        this.productService = productService;
    }

    @GetMapping()
    public ResponseEntity<ProductResponseDto> getProduct(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(Long number) throws Exception{
        productService.deleteProduct(number);
        return ResponseEntity.status(HttpStatus.OK).body("정상 삭제되었습니다.");
    }
}

알게 된 점

  1. DI를 당연시 하지말자! @Autowired 까먹지 말기 (30분 날렸다.)
  2. Spring Data JPA가 스프링이 만들어서 제공하는 컴포넌트 이기 때문에 @Repository를 생략해도 JPA 관련 예외를 모두 스프링 예외로 변환해서 제공한다.
  3. 위의 이유로 JpaRepository 상속 시 따로 어노테이션을 달아주지 않아도 된다!
profile
차근차근

1개의 댓글

comment-user-thumbnail
2022년 8월 9일

2022.08.10 수정완료

답글 달기