@PostMapping("/products")
public ProductResponseDto createProduct(@RequestBody ProductRequestDto requestDto){
return productService.createProduct(requestDto);
}
엔티티는 데이터베이스 테이블에 매핑되는 객체이다. 이 코드에서 Product 클래스가 해당된다. 엔티티 클래스는 @Entity 어노테이션을 통해 정의되며, 데이터베이스의 각 행과 일치하는 필드들을 포함한다.
JPA에서는 Entity Manager가 엔티티를 관리하고, Persistence Context를 유지한다. Persistence Context는 엔티티의 생명 주기를 추적하고 엔티티와 데이터베이스 간 변경 사항을 관리한다.
EntityManager는 데이터베이스 트랜잭션 단위에서 엔티티를 생성, 수정, 삭제 등의 작업을 수행하고, 영속성 컨텍스트를 통해 데이터베이스와 통신한다.
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin(); // 트랜잭션 시작
// 고객 정보 업데이트 작업 수행
Customer customer = entityManager.find(Customer.class, customerId);
customer.setName("새로운 이름");
entityManager.merge(customer);
// 주문 정보 업데이트 작업 수행
Order order = entityManager.find(Order.class, orderId);
order.setStatus("배송중");
entityManager.merge(order);
transaction.commit(); // 트랜잭션 커밋
} catch (Exception e) {
if (transaction != null) {
transaction.rollback(); // 예외 발생 시 롤백
}
e.printStackTrace();
} finally {
entityManager.close();
}
persist(entity): 새로운 엔티티를 영속성 컨텍스트에 추가한다. 이 메서드는 새로운 엔티티를 데이터베이스에 저장하기 위해 사용된다. 그러나 이 메서드 호출 후에도 트랜잭션이나 커밋이 이루어지기 전까지는 실제 데이터베이스에 저장되지 않는다.
merge(entity): 준영속(detached) 상태의 엔티티를 영속성 컨텍스트에 병합한다. 이는 영속성 컨텍스트에 이미 존재하는 엔티티와 데이터베이스에서 가져온 엔티티를 병합하거나, 영속성 컨텍스트에 존재하지 않는 엔티티를 새로 영속성 컨텍스트에 추가할 때 사용된다.
find(entityClass, primaryKey): 주어진 엔티티 클래스와 기본 키(primary key)에 해당하는 엔티티를 찾는다.
remove(entity): 영속성 컨텍스트에서 엔티티를 제거하고 데이터베이스에서 삭제될 수 있도록 표시한다. 그러나 실제 삭제는 트랜잭션 커밋 시에 이루어진다.
flush(): 영속성 컨텍스트의 변경사항을 데이터베이스에 즉시 반영한다. 이는 영속성 컨텍스트의 변경사항을 데이터베이스에 동기화하는데 사용된다.
clear(): 영속성 컨텍스트의 모든 엔티티를 제거하여 영속성 컨텍스트를 초기화한다.
detach(entity): 영속성 컨텍스트에서 엔티티를 분리하여 준영속 상태로 만든다. 이는 해당 엔티티를 영속성 컨텍스트의 관리 밖으로 빼고 싶을 때 사용된다.
이 메서드들은 JPA의 EntityManager 인터페이스를 통해 제공되며, 엔티티의 관리, 영속성 컨텍스트에서의 작업, 데이터베이스와의 상호작용 등을 돕는다.
- flush(): 이 메서드는 영속성 컨텍스트의 변경 내용을 데이터베이스에 즉시 동기화한다. 즉, 영속성 컨텍스트에 있는 엔티티의 변경 사항을 데이터베이스에 반영하는데 트랜잭션이 커밋되지 않은 상태라면 변경 사항은 실제 데이터베이스에 반영되지 않을 수 있다. flush()를 호출한다고 해서 변경 사항이 바로 영구적으로 적용되는 것은 아니다. 변경 사항을 데이터베이스에 보내지만 트랜잭션이나 커밋이 이루어지기 전까지는 롤백될 수 있다.
- commit(): 이 메서드는 트랜잭션의 모든 변경 사항을 데이터베이스에 영구적으로 적용한다. 트랜잭션의 모든 작업이 성공적으로 수행되었고, 데이터베이스에 영구적으로 저장되어야 할 때 commit()이 호출된다. 이렇게 하면 트랜잭션이 완료되고, 데이터베이스에 있는 변경 사항이 영구적으로 적용된다.
CRUD 작업:
JpaRepository를 확장한 인터페이스는 CRUD 작업을 위한 메서드들을 상속받는다. (save(), findById(), delete(), 등)
productRepository.save(new Product(requestDto))는 새로운 Product 엔티티를 영속성 컨텍스트에 저장하고, 이후 JPA는 이를 데이터베이스에 적절한 SQL 쿼리로 변환하여 데이터베이스에 저장한다.
productRepository.findById(id)와 같은 메서드는 데이터베이스에서 엔티티를 검색한다.
SQL 쿼리 생성:
JpaRepository는 메서드 이름 규칙을 통해 쿼리를 자동으로 생성할 수 있다. 예를 들어, 메서드 이름에 findBy를 사용하면 해당 필드를 기반으로 검색 쿼리를 생성한다.
복잡한 쿼리가 필요한 경우에는 @Query 어노테이션을 사용하여 직접 SQL 쿼리를 작성할 수 있다.
Spring Data JPA에서 제공하는 JpaRepository 인터페이스는 기본적인 CRUD(Create, Read, Update, Delete) 기능을 제공하는 여러 메서드들을 상속받아 사용할 수 있다. 일반적으로 사용되는 메서드들은 다음과 같다
save(S entity): 엔티티를 저장하거나 업데이트한다. 새로운 엔티티인 경우 저장하고, 이미 존재하는 엔티티인 경우에는 업데이트한다.
findOne(ID primaryKey): 주어진 기본 키에 해당하는 엔티티를 반환한다.
findById(ID primaryKey): 주어진 기본 키에 해당하는 엔티티를 Optional로 반환한다.
findAll(): 모든 엔티티를 조회한다.
delete(S entity): 주어진 엔티티를 삭제한다.
deleteById(ID primaryKey): 주어진 기본 키에 해당하는 엔티티를 삭제한다.
existsById(ID primaryKey): 주어진 기본 키에 해당하는 엔티티가 존재하는지 확인한다.
count(): 저장된 엔티티의 총 개수를 반환한다.
flush(): 영속성 컨텍스트의 변경 사항을 데이터베이스에 즉시 반영한다.
JpaRepository는 메서드 이름 규칙을 따라 쿼리를 자동으로 생성한다.
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String category;
private double price;
// Getters, setters, constructors 등 생략
}
메서드 이름으로 쿼리 생성:
조건 연산자 사용:
JpaRepository는 Spring의 트랜잭션 관리 기능과 함께 동작한다. @Transactional 어노테이션을 이용하여 트랜잭션 범위를 설정할 수 있고, 여러 개의 CRUD 작업을 하나의 트랜잭션으로 묶어 데이터베이스 일관성을 유지할 수 있다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
@Transactional
public void updateProduct(Long productId, String newName) {
Product product = productRepository.findById(productId).orElse(null);
if (product != null) {
product.setName(newName);
productRepository.save(product);
}
}
}
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository productRepository;
public ProductResponseDto createProduct(ProductRequestDto requestDto) {
Product product = productRepository.save(new Product(requestDto));
return new ProductResponseDto(product);
}
}
코드에서는 @Transactional 어노테이션이 보이지 않으므로 해당 createProduct 메서드는 트랜잭션으로 감싸여 있지 않다.
일반적으로 Spring Data JPA의 save() 메서드는 이미 내부적으로 트랜잭션을 가지고 있다. save() 메서드는 엔티티를 저장하거나 업데이트할 때 내부적으로 트랜잭션을 시작하고 종료한다. 이는 각각의 save() 메서드 호출이 트랜잭션으로 감싸여 데이터베이스 작업을 수행하기 때문에, 해당 메서드 자체에 @Transactional 어노테이션을 추가할 필요가 없다.
save() 메서드와 findById() 메서드는 이미 Spring Data JPA에서 내부적으로 트랜잭션을 가지고 있다.
@Transactional 어노테이션을 명시하는 것은 코드를 이해하기 쉽게 만들어주며, 트랜잭션을 사용한다는 점을 명확하게 나타내어 주는 것이 좋다.