Save()에서의 Id 생성방법
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