[JPA] 변경 감지와 @Transactional
문제 상황
회원 가입 이후 회원 정보 수정의 기능을 개발하고 있던 중, 수정을 시도한 정보가 제대로 저장되지 않는 문제가 발생했다.
회원 이름을 "유저" 에서 "다른유저"로 변경해도 변경이 되지 않는 것이었다.
현재 프로젝트는 SpringBoot를, DB는 MySQL - JPA를 사용하며, Repository 클래스는 JpaRepository를 사용하고 있었다.
문제 원인을 탐색하던 중, 혹시나 하는 마음에 Service의 값 수정 메서드에 @Transactional을 붙여주자 문제가 해결되는 것을 확인했다.
왜 저장, 삭제 로직과 다르게 값의 수정에 대해서는 Service 계층의 메서드에 @Transactional 어노테이션을 붙여야 정상 동작 하는 것일까??
문제점
저장, 삭제 로직과 수정 로직의 차이점은 "JpaRepository"의 메서드를 사용하냐, JPA의 변경 감지를 사용하냐의 차이였다.
아래는 나의 Service 계층의 저장, 수정, 삭제 로직만을 가져온 코드이다.
@Service
public class UserService {
@Autowired
UserDAO userDAO;
public Long saveUser(ServiceUser user) throws DuplicatedUserIdException {
if(userDAO.existsByUserId(user.getId()))
throw new DuplicatedUserIdException();
DBUser dbUser = userDAO.save(ServiceUserMapper.getDBUser(user));
return dbUser.getIdx();
}
public void modify(long idx, ServiceUser user) throws NoSuchUserException, CantModifyFieldException {
DBUser dbUser = findDbUser(idx);
if(!dbUser.getUserId().equals(user.getId())) {
throw new CantModifyFieldException();
}
dbUser.setUserName(user.getUserName());
dbUser.setPassword(user.getPassword());
}
public void delete(long idx) throws NoSuchUserException {
DBUser dbUser = findDbUser(idx);
userDAO.delete(dbUser);
}
}
유저를 저장, 삭제하는 로직은 각각 JpaRepository.save(), JpaRepository.delete() 메서드를 사용하고 있다.
그리고, 아래는 JpaRepository의 구현체인 SimpleJpaRepository의 save() 메서드이다.
@Transactional
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Entity must not be null.");
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
보는 바와 같이 이미 구현부에서 @Transactional 어노테이션을 사용하고 있는 것을 확인할 수 있다.
반면, 수정에서는 JpaRepository의 메서드를 조회 상황에서밖에 사용하지 않고 있다.
변경 감지와 트랜젝션
변경 감지라 함은 말 그대로, "영속성 컨텍스트가 관리하고 있는 엔티티의 값이 변경되었는지를 확인 후, 변경 내용을 반영 하는 기능"이다.
그런데, 영속성 컨텍스트가 관리하고 있는 값의 변경 감지는 트랜잭션의 커밋때 수행된다.
위에서 작성해 둔 나의 Service.modify() 메서드는 하나의 트랜잭션으로 작성되어 있지 않았기 때문에 변경된 값이 정상적으로 커밋되지 않았고, 정상 동작을 기대하기 어려웠던 것이다.
변경 감지를 통한 값의 수정 정상 동작을 위해 @Transactional 어노테이션으로 Service.modify() 메서드를 하나의 트랜잭션으로 만들어 주자.