본문 바로가기

JPA

JPA 에서 엔티티 조회 후 동일한 아이디를 가진 엔티티를 생성하여 저장하면 어떻게 될까?

'만들면서 배우는 클린 아키텍처' 를 읽은 적이 있다. 이 책에서는 지금까지 당연하게 사용한 @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 애너테이션이 붙은 클래스를 엔티티로 사용할때와 동일하게 동작하게 된다.