데이터베이스 연동
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로 가져오는 과정이 먼저 필요하다.
'개발 관련 공부 > 스프링부트 핵심 가이드' 카테고리의 다른 글
07 테스트 코드 작성하기(1) (1) | 2023.11.12 |
---|---|
06 데이터베이스 연동(3) (0) | 2023.10.28 |
06 데이터베이스 연동(1) (0) | 2023.10.03 |
03 개발환경 구성~05 API를 작성하는 다양한 방법 (0) | 2023.10.02 |
02 개발에 앞서 알면 좋은 기초 지식 (0) | 2023.10.01 |
댓글