이전글에서는 이론적인 부분을 다루어 보았다. 이번에는 Spring Boot와 MariaDB를 연결해 보도록 할 것이다.
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://localhost:3306/springboot
spring.datasource.username=your username
spring.datasource.password=your password
spring.jpa.hibernate.ddl-auto=create
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
❗️마지막 세줄은 하이버네이트를 사용할 때 활성화할 수 있는 선택사항이다.
- spring.jpa.hibernate.ddl-auto : 데이터베이스를 자동으로 조작하는 옵션
- create : 애플리케이션이 가동되고 SesseionFactory가 실행될 때 기존 테이블을 지우고 새로 생성
- create-drop : create와 동일한 기능을 수행하지만 애플리케이션을 종료하는 시점에 테이블을 지움
- update : SessionFactory가 실행될 때 객체를 검사해서 변경된 스키마를 갱신, 기존의 저장된 데이터는 유지
- validate : update처럼 객체를 검사하지만 스키마는 거드리지 않음. 검사 과정에서 데이터베이스의 테이블 정보와 객체의 정보가 다르면 에러가 발생
- none : ddl-auto 기능을 사용하지 않음
- spring.jpa.show-sql : 로그에 하이버네이트가 생성한 쿼리문을 출력하는 옵션
- spring.jpa.properties.hibernate.format_sql : 로그에 출력한 쿼리문을 사람이 보기좋게 포메팅할 수 있음
data.entity 패키지에 생성해 주었다.
import jakarta.persistence.*;
import java.time.LocalDateTime;
@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;
@Column(nullable = false)
private Integer stock;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getter, Setter 메서드 생략
}
Spring Data JPA는 JpaRepository를 기반으로 더욱 쉽게 데이터베이스를 사용할 수 있는 아키텍처를 제공한다.
엔티티를 데이터베이스의 테이블과 구조를 생성하는데 사용했다면 레포지토리는 엔티티가 생성한 데이터베이스에 접근하는 데 사용된다.
data.repository 패키지에 생성한다. 접근하려는 테이블과 매핑되는 엔티티에 대한 인터페이스를 생성하고, JpaRepository를 상속받는다.
package com.database.databasebookstudy.data.repository;
import com.database.databasebookstudy.data.entity.Product;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ProductRepository extends JpaRepository<Product, Long> {
}
- JpaRepository를 상속받을 때 대상 엔티티와 기본값 타입을 지정해야 한다.
- 처음에 만든 엔티티를 사용하기 위해서는 위 코드에서 처럼 대상 텐티티를 Product로 설정하고 해당 엔티티의 @Id필드 타입인 Long을 설정하면 된다.
- JpaRepository를 상속받으면 별도의 메서드 구현 없이도 많은 기능을 제공한다.

메서드에 이름을 붙일 때는 첫 단어를 제외한 이후 단어들의 첫 글자를 대무자로 설정해야 JPA에서 정상적으로 인식하고 쿼리를 자동으로 만들어 준다.
package com.database.databasebookstudy.data.dao;
import com.database.databasebookstudy.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;
}
일반적으로 데이터베이스에 접근하는 메서드는 리턴 값으로 데이터 객체를 전달한다. 이때 데이터 객체를 전달할 때 엔티티 객체로 전달할지, DTO객체로 전달할지에 대해서는 개발자마다 다르고 회사나 부서마다 다르다고 한다. 이 부분은 내 소속에 따라 유동적으로 적용하면 될 것 같다.
package com.database.databasebookstudy.data.dao.impl;
// import 길이상 생략
@Component
public class ProductDAOImpl implements ProductDAO {
private final 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) {
Optional<Product> selectedProduct = productRepository.findById(number);
if (selectedProduct.isPresent()){
return selectedProduct.get();
}else {
return null;
}
}
레포지토리 단건 조회
위 코드에서는 .findById()를 사용했지만 .getById()를 사용해도 된다.
- .getById()
- EntityManager의 getReference() 메서드를 호출하고 데이터가 존재하지 않는다면 EntityNotFoundException이 발생한다.
- .findById()
- EntitManager의 find()메서드를 호출하고 영속성 컨텍스트의 캐시에서 값을 조회한 후 영속성 컨텍스트에 값이 존재하지 않는다면 실제 데이터베이스에서 데이터를 조회한다. 리턴값으로 Optional객체를 전달한다.
@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;
}
JPA는 값을 갱신할 때 update라는 키워드를 사용하지 않는다.
영속성 커택스트를 활용해 값을 갱신하는데, 데이터베이스에서 값을 가져오면 가져온 객체가 영속성 컨텍스트에 추가되고 유지되는 상황에서 객체의 값을 변경하고 다시 save()를 실행하면 JPA에서는 더티체크(Dirty Check)라고 하는 변경감지를 수행한다.
변경이 감지되면 레코드를 업데이트하는 쿼리가 실해된다.
@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();
}
}
}
레코드를 삭제하려고 할때는 레코드와 매핑된 영속 객체를 여속성 컨텍스트에 가져와야 한다. findById() 메서드를 통해 객체를 가져오고 delete()메서드를 통해 해당 객체를 삭제한다.
우선 양이 많아 이번 글은 DAO 인터페이스와 구현체를 작성까지만 진행해보았다. 자세한 내용을 담고 싶어서 코드 전체를 담고 설명을 하고있다. 너무 양이 방대해지는것 같기도 하고 좋은 글인지 모르겠어서 일단 이번 챕터까지만 이런식으로 작성하고 방법을 바꿔봐야 할 것 같다.
이번 글에서는 DAO의 필요성에 대해서 알게되었다. 평소 항상 DTO만을 사용해서 객체를 전달했었는데 의존성을 낮추기위해 DAO를 사용해야 한다는 점은 오늘 처음 알게 되었다. 또한 바로 구현을 하기 보다는 인터페이스-구현체 구조를 사용해서 구현한다는 점도 신기했다. 앞으로 진행할 작은 프로젝트들은 DAO를 사용하여 구현해야겠다는 점도 느꼈다. 앞에서 공부했던 영속성 컨텍스트 개념이 update, delete 메서드를 구현할 때 필요했던 점도 신기했다.
다음 글에서는 컨트롤러와 비즈니스 로직을 짜보는 시간을 가져보려한다.