Skip to content

Commit 0da6682

Browse files
committed
feat: Spring Data JPA - 새로운 Entity 판별
1 parent 268e093 commit 0da6682

File tree

1 file changed

+212
-0
lines changed

1 file changed

+212
-0
lines changed
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
---
2+
layout: post
3+
title: "Spring Data JPA - 새로운 Entity 판별"
4+
description: "How Does Spring Data JPA Determine if an Entity is New?"
5+
excerpt: "Spring Data JPA 에서 Entity 가 새로운 것인지 판단하는 방법에 대해서 알아보자."
6+
category: Study
7+
comments: true
8+
---
9+
10+
<div id ="notice--info">
11+
12+
<p style='margin-top:1em;'>
13+
<b>🐱 Meow, meow </b>
14+
</p>
15+
Spring Data JPA 를 사용하다보면 save() 메서드 호출 시 내부적으로 persist() 를 호출할지, merge() 를 호출할지 결정하게 된다. <br>
16+
이 결정은 해당 Entity 가 새로운 Entity 인지 여부에 따라 이루어지는데, Spring Data JPA 는 Entity 가 새로운지 어떻게 판단하는지 알아보자.
17+
<p style='margin-top:1em;'/>
18+
19+
</div>
20+
21+
22+
## 신규 Entity 판단 방식
23+
24+
---
25+
26+
Spring Data JPA 는 내부적으로 `JpaEntityInformation``isNew(T entity)` 메서드를 호출해서 판단한다.
27+
28+
<pre class="prettyprint lang-java">
29+
@Override
30+
public boolean isNew(T entity) {
31+
if (versionAttribute.isEmpty()
32+
|| versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
33+
return super.isNew(entity);
34+
}
35+
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
36+
return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
37+
}
38+
</pre>
39+
40+
1) @Version 필드가 있다면 → null 여부로 판단
41+
2) @Version 필드가 없다면 → @Id 필드가 null 이거나, primitive 타입인 경우 0인지 확인
42+
43+
<div id="notice--warning">
44+
45+
🏷️ 즉, ID 가 null 이면 <b> 신규 Entity </b> 로 간주하여 <b> persist() </b> 가 호출된다.
46+
47+
</div>
48+
49+
<br>
50+
51+
### 직접 ID 를 지정한 경우의 동작
52+
53+
---
54+
55+
ID 를 직접 지정하면 JPA 는 해당 Entity 가 이미 존재하는 것으로 판단하여 `merge()` 를 호출한다.
56+
하지만, Database 에는 존재하지 않는 경우 다음과 같은 문제가 발생하는데,
57+
* SELECT 쿼리로 존재 여부 확인
58+
* 실제 INSERT 가 아닌 **UPDATE** 시도
59+
* <span style="color:red"> → 실패하거나 잘못된 데이터 상태 유발!! </span>
60+
61+
이를 해결하기 위한 방법으로는 `Persistable<T>` 인터페이스를 구현하면 된다.
62+
63+
<pre class="prettyprint lang-java">
64+
// User.class
65+
66+
@Entity
67+
@Table(name = "users")
68+
@EntityListeners(UserEntityListener.class)
69+
public class User implements Persistable&#60;String&#62; {
70+
71+
@Id
72+
private String id;
73+
74+
private String name;
75+
76+
private boolean isNew = true;
77+
78+
protected User() {
79+
80+
}
81+
82+
public User(String id, String name) {
83+
this.id = id;
84+
this.name = name;
85+
}
86+
87+
@Override
88+
public String getId() {
89+
return this.id;
90+
}
91+
92+
public void setId(String id) {
93+
this.id = id;
94+
}
95+
96+
public String getName() {
97+
return this.name;
98+
}
99+
100+
@Override
101+
public boolean isNew() {
102+
return this.isNew;
103+
}
104+
105+
public void setIsNew(boolean isNew) {
106+
this.isNew = isNew;
107+
}
108+
109+
}
110+
111+
// UserEntityListener.class
112+
113+
public class UserEntityListener {
114+
@PostPersist
115+
@PostLoad
116+
public void setNotNew(User user) {
117+
System.out.println("@PostPersist/@PostLoad called");
118+
user.setIsNew(false);
119+
}
120+
121+
}
122+
</pre>
123+
124+
<br>
125+
126+
### persist() vs merge()
127+
128+
---
129+
130+
|구분|persist()|merge()|
131+
|--|--|--|
132+
|동작|새로운 Entity 를 영속성 컨텍스트에 등록|준영속 객체를 병합하여 관리|
133+
|SELECT 쿼리||✅ 먼저 조회 후 merge|
134+
|ID 필요 여부|||
135+
|성능|빠름 (직접 INSERT)|느릴수 있다 (SELECT + UPDATE)|
136+
137+
<div id="notice--warning">
138+
139+
🏷️ 신규 객체를 merge() 로 처리하면 불필요한 SELECT 쿼리가 발생하고 성능 저하 가능성이 존재한다.
140+
141+
</div>
142+
143+
<br>
144+
145+
### 신규 Entity 판단이 중요한 이유는 무엇일까?
146+
147+
---
148+
149+
Spring Data JPA 의 `SimpleJpaRepository``save()` 에서 다음과 같이 동작한다.
150+
151+
<pre class="prettyprint lang-java">
152+
@Transactional
153+
public &#60;S extends T&#62; S save(S entity) {
154+
if (entityInformation.isNew(entity)) {
155+
entityManager.persist(entity); // INSERT
156+
} else {
157+
return entityManager.merge(entity); // SELECT → UPDATE
158+
}
159+
}
160+
</pre>
161+
162+
ID 를 직접 설정했지만 `isNew()` 는 false 가 되어, `merge()` 를 호출하게 되고,
163+
Database 에는 해당 ID 가 존재하지 않지만, 신규 Entity 임에도 불구하고,
164+
`SELECT``UPDATE` 를 하게 되어(Database 조회) <span style="color:red"> 실패 또는 데이터 무결성 오류 </span> 가 발생할 수 있고, 비효율적이다.
165+
166+
<div id="notice--warning">
167+
168+
🏷️ 정확한 isNew() 제어는 성능, 정합성, 쿼리 효율성 측면에서 매우 중요하다.
169+
170+
</div>
171+
172+
<br>
173+
174+
### 정리
175+
176+
---
177+
178+
|상황| 처리방식 |
179+
|--|------------------------------------------|
180+
|ID 없거나 null → 신규 Entity | persist() |
181+
|ID가 존재하지만 실제 DB 에는 없음 | merge() 호출 → 실패 가능성 / 비효율 |
182+
|ID 를 직접 설정한 신규 Entity| Persistable&#60;T&#62; + isNew() 로 명시 필요 |
183+
184+
185+
<br><br>
186+
187+
188+
<div id="notice--success">
189+
190+
<p style='margin-top:1em;'>
191+
<b> 📗 요약 </b>
192+
</p>
193+
🖐 Spring Data JPA 는 내부적으로 isNew() 를 통해서 신규 Entity 여부를 판단한다. <br>
194+
🖐 ID 는 존재하지만 실제 DB 에 없는 경우 SELECT + UPDATE 후 merge 를 하므로 정합성이 떨어질 수 있고, 비효율적이다. <br>
195+
🖐 ID 를 직접 설정했을 경우는 Persistable&#60;T&#62; + isNew() 로 명시하는 것이 필요하다. <br>
196+
197+
<p style='margin-top:1em;' />
198+
199+
</div>
200+
201+
202+
<br><br>
203+
204+
205+
## Reference
206+
207+
---
208+
209+
* [Spring Docs - JpaEntityInformation](https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/support/JpaEntityInformation.html)
210+
* [Spring Docs - entity-persistence](https://docs.spring.io/spring-data/jpa/reference/jpa/entity-persistence.html)
211+
212+
<br><br>

0 commit comments

Comments
 (0)