Save()에서의 Id 생성방법

이효진

Updated:

Intro

Spring Data Jpa에서 엔티티를 생성하고 save()할때 id를 직접 주입하게되면 어떻게 될까?

Spring Data JPA?

  • 지루하게 반복되는 CRUD문제를 해결
  • 개발자는 인터페이스만 작성
  • Spring Data Jpa가 구현객체를 동적으로 생성해 주입한다.

인터페이스 기능

public interface MemberRepository extends JpaRepository<Member, Long> {
  • 공통 CRUD 제공
  • 제네릭은 <엔티티, 식별자타입> 로 설정
  • findAll(), findOne(), save(), delete() 등 사용 가능

save() 사용 시 주의 할점

예시
Member 테이블


@Entity
@Data
public class Member {
	
	@Id @GeneratedValue
	@Column(name = "Member_id")
	private Long id;
	private String name;
    

}

테스트 코드

@Test
	public void testSaveMember() {
		Member Member = new Member(2L,"Member1");
		Member newMember = MemberRepository.save(Member);
        
		System.out.println(Member);
		System.out.println(newMember);
		assertThat(newMember.getId()).isEqualTo(Member.getId());
	}

member의 id를 2로 세팅해서 저장을 해보았다.

결과


//merge에 대한 쿼리문 start
    select
        Member0_.Member_id as Member_id1_2_0_,
        Member0_.name as name2_2_0_ 
    from
        persons Member0_ 
    where
        Member0_.Member_id=?
                   : 
    call next value for hibernate_sequence
                       : 
    insert 
    into
        persons
        (name, Member_id) 
    values
        (?, ?)

//merge에 대한 쿼리문 end

Member(id=2, name=Member1)
Member(id=1, name=Member1)

expected: 2L
 but was: 1L, mergedContextConfiguration

위에서Id가 2인 Member를 저장했지만 전달받은 객체의 id는 1이다.

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);
		}
	}

isNew()


@Transient // DATAJPA-622
	@Override
	public boolean isNew() {
		return null == getId();
	}

save()의 실제구현된 코드를 살펴 보면 isNew()를 사용해 id의 null여부를 체크하여서 persist를 할지 merge를 할지 결정한다.

  • 위의 예제 코드를 보면 id가 2로 세팅되어있어서 isNew()에 걸리지 않는다.
  • 따라서 em.merge()가 실행되는데 이때 id를 1로 가져온다.

    select
        Member0_.Member_id as Member_id1_2_0_,
        Member0_.name as name2_2_0_ 
    from
        persons Member0_ 
    where
        Member0_.Member_id=?
                   : 
    call next value for hibernate_sequence
                       : 
    insert 
    into
        persons
        (name, Member_id) 
    values
        (?, ?)


  • merge()에 대한 동작은 우선 다음과 같이 2L인 Member를 조회한다.
  • 하지만 Member가 조회되지 않기 떄문에 처음 저장하는 객체로 인식하고 insert를 발생 시킨다.
  • 그래서 결과가 다르게 나오므로 주의해야한다.

해결방법

@GeneratedValue를 사용하면 문제가 없겠지만 위의 예시처럼 id를 직접넣는경우에는 Persistable인터페이스를 직접 구현하면 된다.


@Entity
@Data
@EntityListeners(AuditingEntityListener.class)
public class Member implements Persistable<Long>{ 
	
	@Id @GeneratedValue
	private Long id;
	private String name;
	
	@CreatedDate
	private LocalDateTime createdDate; //persist전에 값세팅


	@Override
	public boolean isNew() {
		// 새로운 객체여부 구별
		return createdDate == null;
	}

}

  • Auditing을 사용하기 위해 @EventListeners추가
    • Audiging이란?
    • 변경날짜와 생성날짜같이 엔티티마다 반복적으로 들어가는 필드 값을 자동으로 넣어주는 기능을 한다.
  • Id필드하나로는 새로생성된 엔티티인지 구별이 어려워 날짜필드 추가
  • isNew() 메서드를 Override 해서 날짜가 null일 경우 새로운 엔티티임을 구분

결과


2022-01-25 22:02:18.983 DEBUG 5660 --- [           main] org.hibernate.SQL                        : 
    insert 
    into
        Member
        (created_date, name, id) 
    values
        (?, ?, ?)
Member(id=2, name=username, createdDate=2022-01-25T22:02:18.782)
Member(id=2, name=username, createdDate=2022-01-25T22:02:18.782) 

Leave a comment