본문 바로가기
개발 관련 공부/스프링부트 핵심 가이드

06 데이터베이스 연동(2)

by 슴새 2023. 10. 14.
반응형

 

데이터베이스 연동

pom.xml에 마리아db, jpa 추가하고 application.properties에 db 연결 정보를 추가한다.

spring.datasource.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/springboot
spring.datasource.username=flature
spring.datasource.password=aroundhub12#

spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

spring.jpa.show-sql=true는 생성한 쿼리문을 출력하는 옵션이다.

 

ddl-auto 선택사항은 아래와 같다.

- create: 기존 테이블 지우고 새로 생성

- update: 변경된 스키마 갱신, 데이터는 유지

- validate: 스키마를 건드리지 않음. 변경된게 있으면 에러

- none: ddl-auto 기능을 사용하지 않는다.

 

개발환경: 주로 update나 create

운영환경: 주로 validate나 none 

 

엔티티 설계

jpa를 사용하면 테이블 생성 쿼리문을 작성할 필요가 없다.

@Entity 어노테이션이 붙은 객체를 보고 jpa가 테이블을 알아서 만들어준다.

 

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
@ToString(exclude = "name")
@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 LocalDateTime createdAt;

    private LocalDateTime updatedAt;

}

사용 예시는 위와 같다.

 

엔티티에서 사용되는 기본 어노테이션들은 아래와 같다.

  • @Entity 엔티티임을 명시
  • @Table  클래스의 이름과 테이블의 이름을 다르게 하고 싶으면 사용. 내경험상 그냥 클래스명이랑 맞추는게 낫다..
  • @Id 테이블의 기본값 역할. 반드시 필요
  • @GeneratedValue 일반적으로 @Id와 함께 사용
  • @Column 칼럼과 매핑. 없어도 되지만 따로 설정할게 있는 경우 명시
  • @Transient 엔티티 클래스엔 있지만 db에선 필요없는 경우 붙인다.

리포지토리 인터페이스 설계

product 엔티티에 해당하는 db 테이블을 조작하고자 할때 리포지토리가 필요하다.

public interface ProductRepository extends JpaRepository<Product, Long> {

}

<엔티티명, @Id의 타입> 으로 상속받으면 된다.

단순  crud는 JpaRepository가 제공하는 기본 메서드를 이용하여 호출할 수 있다.

 

생성규칙은 아래와 같다.

  • FindeBy : SQL문의 where 절 역할을 수행하는 구문이다. findBy 뒤에 엔티티의 필드값을 입력해서 사용한다.
    • 예) findByName(String name)
  • AND, OR : 조건을 여러 개 설정하기 위해 사용한다.
    • 예) findByNameAndEmail(String name, String email)
  • Like / NotLike : SQL문의 like와 동일한 기능을 수행하며, 특정 문자를 포함하는지 여부를 조건으로 추가한다. 비슷한 키워드로 Containing, Contains, isContaing이 있다.
  • StartsWith / StartingWith : 특정 키워드로 시작하는 문자열 조건을 설정한다.
  • EndsWith / EndingWith : 특정 키워드로 끝나는 문자열 조건을 설정한다.
  • IsNull / IsNotNull : 레코드 값이 Null 이거나 Null이 나닌 값을 검색한다.
  • True / False : Boolean 타입의 레코드를 검색할 때 사용한다.
  • Before / After : 시간을 기준으로 값을 검색한다.
  • LessThan / GreaterThan : 특정 값(숫자)을 기준으로 대소 비교를 할 때 사용한다.
  • Between : 두 값(숫자) 사이의 데이터를 조회한다.
  • OrdeBy : SQL 문에서 order by와 동일한 기능을 수행한다.
    • 예) 가격순으로 이름 조회를 수행한다면 List<Product> findByNameOrderByPriceAsc(String name);와 같이 작성한다.
  • countBy : SQL 문의 count와 동일한 기능을 수행하며, 결괏값의 개수(count)를 추출한다.

DAO 설계

스프링 데이터 JPA에서 DAO의 역할은 레포지토리가 수행한다. (둘의 역할이 비슷)

규모가 큰 JPA 프로젝트에서는 기존 스프링 레거시처럼 DAO 인터페이스를 선언하고

그 인터페이스를 implements 한 DAOImpl을 선언하고 DAOImpl안에 리포지토리를 선언해서 리포지토리의 insert 함수문 등을 호출하는데....

그냥 ServiceImpl 클래스에서 리포지토리를 선언하고 db 에 접근...

하면 비지니스 로직을 수행하면서 db 작업도 하는 것이기 때문에 기능이 분리되었다고 보기 힘들다고한다.

근데 토이프로젝트 등에선 그렇게 복잡하지 않기 때문에 그냥 서비스에서 바로 호출해도 될 것 같다.

 

예제를 보도록 하자.

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;

}
@Component
public class ProductDAOImpl implements ProductDAO {

    private ProductRepository productRepository;

    @Autowired
    public ProductDAOImpl(ProductRepository productRepository) {
        this.productRepository = productRepository;
    }

    // 예제 6.11
    @Override
    public Product insertProduct(Product product) {
        Product savedProduct = productRepository.save(product);

        return savedProduct;
    }

    // 예제 6.12
    @Override
    public Product selectProduct(Long number) {
        Product selectedProduct = productRepository.getById(number);

        return selectedProduct;
    }

    // 예제 6.15
    @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;
    }

    // 예제 6.17
    @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();
        }
    }
}

@Component는 스프링이 빈을 관리하도록 하기 위해 붙인다.

맨날 @Controller, @Service 같은걸 많이 사용해서 거의 잊고 있던 존재임..

 

jpa 처음할때도 update가 좀 더 생소했던 기억이 있는데,

find()로 객체를 가져오면 영속성 컨텍스트가 추가된다.

이렇게 영속성 컨텍스트가 유지되는 상황에서 객체의 값을 변경하고 다시 save()를 호출하면 jpa는 해당 객체를 업뎃하는 쿼리문을 수행한다. 변경을 알아서 눈치깔 수 있는게 영속성 컨텍스트 때문인것 같다..

 

delete도 비슷하게 영속성 컨텍때문에 find로 가져오는 과정이 먼저 필요하다.

 

반응형

댓글