'만들면서 배우는 클린 아키텍처' 를 읽은 적이 있다. 이 책에서는 지금까지 당연하게 사용한 @Entity 애너테이션이 붙어있는 클래스를 엔티티로 사용하지 않고, @Entity 애너테이션이 붙은 클래스와 @Entity 애너테이션이 붙지 않은 클래스를 각각 정의 후 @Entity 애너테이션이 붙어 있지 않은 클래스를 엔티티로 사용하였다.
JPA 를 처음 배울때 아래 코드처럼 동일 트랜잭션 안에서 동일한 아이디에 대해 findById()
를 통해 여러번 조회 하더라도 반환받은 엔티티의 참조값은 항상 동일하다는 내용을 배운 이후로 막연하게 영속성 컨텍스트에 존재하는 엔티티를 변경해야만 업데이트가 될 것이라고 생각했다.
Entity entity1 = repository.findById(1L);
Entity entity2 = repository.findById(1L);
assertThat(entity1).isSameAs(entity2);
그래서 책에서 처럼 동일한 아이디에 해당하는 @Entity 애너테이션이 붙은 객체를 신규로 생성하여 repository.save()
를 호출하면 select 쿼리 이후에 update 쿼리가 발생할 것이라고 생각했다.
그래서 아래와 같이 엔티티를 만들고, 테스트를 진행해 보았다.
엔티티 정의
@Entity
@Getter
@Table(name = "jpa_entity")
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class JpaEntity {
@Id
@EqualsAndHashCode.Include
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "col1")
private String col1;
@Column(name = "col2")
private String col2;
public JpaEntity(String col1, String col2) {
this.col1 = col1;
this.col2 = col2;
}
public void setCol1(String col1) {
this.col1 = col1;
}
public void setCol2(String col2) {
this.col2 = col2;
}
public JpaEntity copy() {
JpaEntity entity = new JpaEntity();
entity.id = this.id;
entity.col1 = this.col1;
entity.col2 = this.col2;
return entity;
}
}
업데이트 테스트
@DataJpaTest
class JpaEntityRepositoryTest {
@Autowired
JpaEntityRepository repository;
@Autowired
EntityManager entityManager;
@Test
@Commit
void test1() {
final JpaEntity entity = new JpaEntity("col1", "col2");
final JpaEntity save = repository.save(entity);
entityManager.flush();
entityManager.clear();
final JpaEntity found = repository.findById(save.getId()).get();
final JpaEntity copy = found.copy();
assertThat(found.getId()).isEqualTo(copy.getId());
assertThat(found.getCol1()).isEqualTo(copy.getCol1());
assertThat(found.getCol2()).isEqualTo(copy.getCol2());
copy.setCol1("col111");
copy.setCol2("col222");
repository.save(copy);
entityManager.flush();
entityManager.clear();
final JpaEntity updated = repository.findById(save.getId()).get();
assertThat(updated.getCol1()).isEqualTo(copy.getCol1());
assertThat(updated.getCol2()).isEqualTo(copy.getCol2());
}
}
테스트를 해보면 아래와 같이 별도의 select 쿼리가 발생하지 않고 update 쿼리만 발생하는 것을 확인할 수 있다.
DEBUG 9494 --- [ Test worker] org.hibernate.SQL : insert into jpa_entity (id, col1, col2) values (null, ?, ?)
DEBUG 9494 --- [ Test worker] org.hibernate.SQL : select jpaentity0_.id as id1_1_0_, jpaentity0_.col1 as col2_1_0_, jpaentity0_.col2 as col3_1_0_ from jpa_entity jpaentity0_ where jpaentity0_.id=?
DEBUG 9494 --- [ Test worker] org.hibernate.SQL : update jpa_entity set col1=?, col2=? where id=?
DEBUG 9494 --- [ Test worker] org.hibernate.SQL : select jpaentity0_.id as id1_1_0_, jpaentity0_.col1 as col2_1_0_, jpaentity0_.col2 as col3_1_0_ from jpa_entity jpaentity0_ where jpaentity0_.id=?
하지만 위와 동일한 테스트를 트랜잭션이 분리된 상태로 실행해보면 select 쿼리 이후 update 쿼리가 발생하게 된다.
테스트
@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
void test2() {
final JpaEntity entity = new JpaEntity("col1", "col2");
final JpaEntity save = repository.save(entity);
final JpaEntity found = repository.findById(save.getId()).get();
final JpaEntity copy = found.copy();
assertThat(found.getId()).isEqualTo(copy.getId());
assertThat(found.getCol1()).isEqualTo(copy.getCol1());
assertThat(found.getCol2()).isEqualTo(copy.getCol2());
copy.setCol1("col111");
copy.setCol2("col222");
repository.save(copy);
final JpaEntity updated = repository.findById(save.getId()).get();
assertThat(updated.getCol1()).isEqualTo(copy.getCol1());
assertThat(updated.getCol2()).isEqualTo(copy.getCol2());
}
쿼리
DEBUG 10424 --- [ Test worker] org.hibernate.SQL : insert into jpa_entity (id, col1, col2) values (null, ?, ?)
DEBUG 10424 --- [ Test worker] org.hibernate.SQL : select jpaentity0_.id as id1_1_0_, jpaentity0_.col1 as col2_1_0_, jpaentity0_.col2 as col3_1_0_ from jpa_entity jpaentity0_ where jpaentity0_.id=?
DEBUG 10424 --- [ Test worker] org.hibernate.SQL : select jpaentity0_.id as id1_1_0_, jpaentity0_.col1 as col2_1_0_, jpaentity0_.col2 as col3_1_0_ from jpa_entity jpaentity0_ where jpaentity0_.id=?
DEBUG 10424 --- [ Test worker] org.hibernate.SQL : update jpa_entity set col1=?, col2=? where id=?
DEBUG 10424 --- [ Test worker] org.hibernate.SQL : select jpaentity0_.id as id1_1_0_, jpaentity0_.col1 as col2_1_0_, jpaentity0_.col2 as col3_1_0_ from jpa_entity jpaentity0_ where jpaentity0_.id=?
테스트 해본 결과 동일한 트랜잭션이 유지된다면 책에서 처럼 엔티티를 @Entity 애너테이션이 붙지 않은 클래스와 @Entity 애너테이션이 붙은 클래스로 나누더라도 기존처럼 @Entity 애너테이션이 붙은 클래스를 엔티티로 사용할때와 동일하게 동작하게 된다.