[JPA] dirty checking 더티체킹 간단 이해
JPA의 더티 체킹은 엔터티의 상태 변화를 감지하고,
이 변화를 데이터베이스에 자동으로 반영하는 기능이다.
따라서, JPA를 사용하면 엔터티의 데이터를 변경한 후
별도로 update 메서드를 호출할 필요가 없다.
JPA의 영속성 컨텍스트가 관리하는 엔터티에 대해 변경이 감지되면, 해당 트랜잭션이 커밋될 때 변경 사항을 데이터베이스에 반영하는 것이다.
예시로
EntityManager를 사용한 순수JPA와
JpaRepository 인터페이스를 사용한 Spring Data JPA
이렇게 2가지를 기준으로 비교해보았다.
EntityManager를 사용한 순수JPA
아래 코드를 보면
findById로 단건 조회를 한다. 단건조회 한 값은 Hotel 엔터티에 담는다.
requestDto의 값을 단건조회 시에 담았던 Hotel 엔터티에 셋팅한다.
그리고 Hotel 엔터티의 값을 사용하여 Repository의 update메서드를 호출하여 DB에 담는다.
DB에 잘 담은 값을 Hotel 엔터티로 받아서
HotelResponseDto형식으로 컨트롤러에 전달하는 코드이다.
이렇게 Repository에 내가 만든 update()메서드를 통해서 DB에 수정 값이 담기게 되는 것을 알 수 있다.
@Service
public class HotelService {
private final HotelRepository hotelRepository;
public HotelResponseDto modifyHotel(int hotelId, HotelRequestDto requestDto) {
Hotel hotel = hotelRepository.findById(hotelId)
.orElseThrow(() -> new EntityNotFoundException("Hotel not found with id " + hotelId));
modifyStringIfNotNull(requestDto.getName(), hotel::setName);
modifyStringIfNotNull(requestDto.getType(), hotel::setType);
modifyStringIfNotNull(requestDto.getAddress(), hotel::setAddress);
modifyStringIfNotNull(requestDto.getContact(), hotel::setContact);
modifyStringIfNotNull(requestDto.getEmail(), hotel::setEmail);
modifyStringIfNotNull(requestDto.getDescription(), hotel::setDescription);
modifyIntIfNotZero(requestDto.getStar(), hotel::setStar);
modifyIntIfNotZero(requestDto.getMinPrice(), hotel::setMinPrice);
modifyIntIfNotZero(requestDto.getMaxPrice(), hotel::setMaxPrice);
modifyIntIfNotZero(requestDto.getRooms(), hotel::setRooms);
Hotel update = hotelJpaRepository.update(hotelId, hotel);
return new HotelResponseDto(update);
}
}
이번에는
JpaRepository 인터페이스를 사용한 Spring Data JPA
예시이다.
아래 코드를 보면
먼저 해당 서비스 클래스에서 @Transactional 어노테이션이 필요하다.
엔터티의 상태가 변경되었기 때문에
트랜잭션이 종료될 때(modifyHotel() 실행이 끝나고 @Transactional 어노테이션이 종료될 때),
JPA는 더티 체킹을 통해 이 변경을 감지하고,
해당 엔터티를 DB에 자동으로 update한다.
만약 @Transactional 어노테이션이 적용되어 있지 않다면, 더티 체킹이 동작하지 않을 수 있다.
따라서 해당 서비스 메서드나 서비스 클래스에 @Transactional 어노테이션이 적용되어 있는지 확인하는 것은 중요하다.
DB에 자동으로 update가 되니,
자연스럽게 Hotel update = hotelJpaRepository.update(hotelId, hotel); 이 부분은 필요없게 된다.
그리고 Hotel 엔터티에 요청값이 셋팅 되었으니, return 부분에서도 그대로 매개변수값으로 사용이 가능하다.
@Service
@Transactional //더티체킹을 위해 필요.
public class HotelService {
private final HotelJpaRepository hotelJpaRepository;
public HotelResponseDto modifyHotel(int hotelId, HotelRequestDto requestDto) {
Hotel hotel = hotelJpaRepository.findById(hotelId)
.orElseThrow(() -> new EntityNotFoundException("Hotel not found with id " + hotelId));
modifyStringIfNotNull(requestDto.getName(), hotel::setName);
modifyStringIfNotNull(requestDto.getType(), hotel::setType);
modifyStringIfNotNull(requestDto.getAddress(), hotel::setAddress);
modifyStringIfNotNull(requestDto.getContact(), hotel::setContact);
modifyStringIfNotNull(requestDto.getEmail(), hotel::setEmail);
modifyStringIfNotNull(requestDto.getDescription(), hotel::setDescription);
modifyIntIfNotZero(requestDto.getStar(), hotel::setStar);
modifyIntIfNotZero(requestDto.getMinPrice(), hotel::setMinPrice);
modifyIntIfNotZero(requestDto.getMaxPrice(), hotel::setMaxPrice);
modifyIntIfNotZero(requestDto.getRooms(), hotel::setRooms);
//JpaRepository를 상속받은 Repository로 할 때는 더티체킹이 되어서 사용할 필요가 없다.
// Hotel update = hotelJpaRepository.update(hotelId, hotel);
return new HotelResponseDto(hotel);
}
}