(JPA) Persistable
π μμ± λ°°κ²½
Spring Data JPA
λ₯Ό μ¬μ©νλ©΄μ λ€μν primary key
μμ± μ λ΅μ΄ μλλ°, μ΄λ° μ λ΅λ€μ μ¬μ©νμ§ μκ³ μ§μ ID
κ°μ μ§μ ν΄μ£Όλ λ°©μμ μ¬μ©νλ κ²½μ°λ μ’
μ’
μλλ° μ§μ ID
λ₯Ό μ§μ ν΄μ μ¬μ©νλ κ²½μ° λ°μν μ μλ μν©μ λν μ£Όμμ λ° κ°μ λ°©λ²μ λν΄ κ°λ΅νκ² μ 리ν΄λ³΄μ νλ€.
Spring Data JPA
λ₯Ό μ¬μ©νλ©΄μ ID
κ°μ μ£Όλ‘ μ§μ μ§μ ν λ νμλ μ£Όλ‘ UUID
λ Snowflake
λ±μ νμμ μ¬μ©νκ³ μλ€. μ΄ λκ°μ§ λ°©μ μΈμλ μ§μ ID
λ₯Ό μ§μ ν΄μ£Όλ κ²½μ°μλ λͺ¨λ λ°μν μ μλ μν©μ΄λ, μ°Έκ³ ν΄λ³΄λ©΄ μ’μκ² κ°λ€.
π μ 3μ€ μμ½
Spring Data JPA
μμID
λ₯Ό μ§μ μ§μ νλ©΄ μλμΉ μμSELECT
μΏΌλ¦¬κ° μΆκ°λ‘ λ°μν¨.Persistable
μΈν°νμ΄μ€ ꡬνμΌλ‘isNew()
λ‘μ§μ μ§μ μ μ΄νμ¬ λΆνμν 쿼리 μ κ±° κ°λ₯.BaseEntity
μPersistable
μ ꡬννλ©΄ λͺ¨λ μν°ν°μμ μΌκ΄μ± μκ² μ²λ¦¬ κ°λ₯.
ποΈ μμ μν©
νΉμ μλΉμ€μμ User
λ°μ΄ν°λ₯Ό μ μ₯ν λ Sequence
λ AutoIncrement
λ₯Ό νμ©νμ¬ μλ³μ(ID) κ°μ μ§μ νλκ² μλλΌ μ¬λ¬ μ΄μ λ‘ μΈν΄ νμ¬μμ μ§μ ν μλ³μ μ§μ λ°©μμ μ¬μ©ν΄μΌ νλ€κ³ κ°μ ν΄λ³΄μ.
μμ΄λ μμ± μμ
1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
@Table(name = "users")
class UserEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0L,
@Column(nullable = false)
val username: String,
@Column(nullable = false)
val password: String
)
interface UserRepository : JpaRepository<UserEntity, Long>
μμ λ₯Ό μν΄ κ°λ¨νκ² UserEntity
λ₯Ό μμ κ°μ΄ λ§λ€μλ€. νμ΅ν λ λ§μ΄ μ¬μ©νλ μλ³μ μμ± μ λ΅μΈ GenerationType.IDENTITY
λ₯Ό μ€μ ν λͺ¨μ΅μ΄λ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@DataJpaTest
class PersistableTest {
@Autowired
private lateinit var entityManager: TestEntityManager
@Autowired
private lateinit var userRepository: UserRepository
@Test
@DisplayName("μμ΄λ μμ±μ μμ νμλ")
fun generatedValueTest() {
val stats: Statistics =
entityManager.entityManager
.unwrap(Session::class.java)
.sessionFactory
.statistics.also {
it.isStatisticsEnabled = true
it.clear()
}
val entity = UserEntity(username = "test_user", password = "1234")
userRepository.save(entity)
userRepository.flush()
val queryCount: Long = stats.prepareStatementCount
println("Query Count: $queryCount")
Assertions.assertThat(queryCount).isGreaterThanOrEqualTo(1)
}
}
κ°λ¨ν ν
μ€νΈ μ½λλ₯Ό ν΅ν΄ μμ΄λ μμ±μ μμν μν©μμ save
λ₯Ό νμλ query
κ° λͺλ² λ°μνλμ§ μμ보μ.
1
2
3
4
5
6
7
8
Hibernate:
insert
into
users
(password, username, id)
values
(?, ?, default)
Query Count: 1
λ‘κ·Έλ₯Ό μ΄ν΄λ³΄λ©΄ insert
μΏΌλ¦¬λ§ λ± νλ² λ°μνκ±Έ λ³Ό μ μλ€.
λκ° Spring Data JPA
λ₯Ό νμ©ν΄μ νμ΅ν λ μμ΄λ μμ±μ μμνλ μμΌλ‘ νμ΅μ νκΈ° λλ¬Έμ μ΄ λΆλΆμ λν΄μλ
λ§μ λΆλ€μ΄ μκ³ μλ λ΄μ©μΌκ² κ°λ€. κ·ΈλΌ μ΄λ²μλ μμ΄λ μμ±μ μμνλ μ λ΅μ μ¬μ©νμ§ μκ³ , μ§μ μ€μ ν΄ μ£Όλ λ°©μμ μ¬μ©ν΄λ³΄μ.
μμ΄λ μ§μ μμ±
1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
@Table(name = "users")
class UserEntity(
@Id
// @GeneratedValue(strategy = GenerationType.IDENTITY) ν΄λΉ λΆλΆμ μ£Όμ μ²λ¦¬ λλ μμ
val id: Long = 0L,
@Column(nullable = false)
val username: String,
@Column(nullable = false)
val password: String
)
interface UserRepository : JpaRepository<UserEntity, Long>
μλ³μ μμ±μ μμνλ λ°©μμμ μμ± μ λ΅μ λνλ΄λ Annotation
μ μ£Όμ μ²λ¦¬ν λͺ¨μ΅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@DataJpaTest
class PersistableTest {
@Autowired
private lateinit var entityManager: TestEntityManager
@Autowired
private lateinit var userRepository: UserRepository
@Test
@DisplayName("Persistable ꡬννμ§ μμμλ")
fun nonPersistableTest() {
val stats: Statistics =
entityManager.entityManager
.unwrap(Session::class.java)
.sessionFactory
.statistics.also {
it.isStatisticsEnabled = true
it.clear()
}
// UserEntity() λ₯Ό μμ±ν΄ μ€ λ ID κ°μ μ§μ μ€μ ν΄μ£Όκ³ μλ λͺ¨μ΅
val entity = UserEntity(id = 1L, username = "test_user", password = "1234")
userRepository.save(entity)
userRepository.flush()
val queryCount: Long = stats.prepareStatementCount
println("Query Count: $queryCount")
Assertions.assertThat(queryCount).isGreaterThanOrEqualTo(2)
}
}
μ΄λ²μλ UserEntity
μ μλ³μλ₯Ό μ§μ μ€μ ν΄λ³΄μ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Hibernate:
select
ue1_0.id,
ue1_0.password,
ue1_0.username
from
users ue1_0
where
ue1_0.id=?
Hibernate:
insert
into
users
(password, username, id)
values
(?, ?, ?)
Query Count: 2
λ‘κ·Έλ₯Ό μ΄ν΄λ³΄λ©΄, μΏΌλ¦¬κ° 2λ² λ°μνκ±Έ λ³Ό μ μλ€. insert
쿼리 μΈμ select
μΏΌλ¦¬κ° μΆκ°λ‘ λ°μν λͺ¨μ΅.
insert
μΏΌλ¦¬κ° νλ²λ§ λ°μν κ±°λΌ μκ°νλλ°, λ¬κΈμμ΄ select
μΏΌλ¦¬κ° μΆκ°λ‘ λ°μν λͺ¨μ΅μ΄λ€.
λ¨Όμ μ μ΄λ° μν©μ΄ λ°μνλμ§ κ°λ¨νκ² μμλ³΄κ³ λμ΄κ°μ.
μ°μ μ΄ λΆλΆμ μ΄ν΄νλ €λ©΄ Spring Data JPA
μμ save()
λ©μλκ° λ΄λΆμ μΌλ‘ μ΄λ»κ² λμνμ§λ μ΄ν΄λ₯Ό ν΄μΌ νλ€. Spring Data JPA
μμ save()
λ©μλλ₯Ό μ§μ ꡬννλκ² μλλΌ κ΅¬ν λμ΄ μλκ±Έ κ·Έλλ‘ μ¬μ©νλ€λ©΄ JpaRepositoryImplementation
λ₯Ό ꡬννκ³ μλ ꡬνμ²΄μΈ SimpleJpaRepository
μ save()
λ©μλλ₯Ό μ¬μ©νκ²
λλλ° ν΄λΉ λ©μλλ μλμ κ°μ΄ μμ±λμ΄ μλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
private final EntityManager entityManager;
// ...
@Override
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, ENTITY_MUST_NOT_BE_NULL);
if (entityInformation.isNew(entity)) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}
// ...
}
μ¬κΈ°μ μ°λ¦¬λ 3κ°μ§λ₯Ό μ€μ μ μΌλ‘ λ΄μΌ νλ€.
entityInformante.isNew(entity)
entityManager.persist(entity)
entityManager.merge(entity)
μ°μ 첫λ²μ§Έ entityInformante.isNew(entity)
μ μ΄ν΄λ³΄μ.
isNew()
λ©μλμ κ²½μ°λ JpaEntityInformationSupport
λ₯Ό μμνκ³ μλ
JpaMetamodelEntityInformation
μ μλ isNew()
λ©μλλ₯Ό μ¬μ©νκ³ μλλ°
ν΄λΉ λ©μλλ μλμ κ°μ΄ μμ±λμ΄ μλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class JpaMetamodelEntityInformation<T, ID> extends JpaEntityInformationSupport<T, ID> {
// ...
@Override
public boolean isNew(T entity) {
if (versionAttribute.isEmpty()
|| versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
return super.isNew(entity);
}
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
}
// ...
}
λ¨Όμ μ¬κΈ°μ versionAttribute.isEmpty()
μΈμ§ νμΈμ νκ² λλλ°
μ΄κ±΄ @Version
μ μ μΈν νλκ° μλμ§ νμΈνλ λΆλΆμ΄λ€. ν΄λΉ λ΄μ©μ Optimistic Lock
κ³Ό κ΄λ ¨λ λ΄μ©μ΄λΌ
μμΈν λ΄μ©μ μ°μ μ λμ΄κ°μ. UserEntity
μ @Version
μ μ μΈν νλκ° μμΌλ isEnpty()
λ true
λ₯Ό λ°ννκ² λλ€. ||(or)
쑰건μ΄λ€ 보λ, if
λ¬Έ λ΄λΆλ‘ λ€μ΄κ°κ² λλ€.
if
λ¬Έ λ΄λΆμμ super.isNew(entity)
λ₯Ό νΈμΆνκ² λλλ°, μ΄ κ²½μ°μλ EntityInformation
μ ꡬννκ³ μλ
ꡬνμ²΄μΈ AbstractEntityInformation
μ μλ isNew()
λ©μλλ₯Ό νΈμΆνκ² λλ€.
ν΄λΉ λ©μλλ μλμ κ°μ΄ μμ±λμ΄ μλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class AbstractEntityInformation<T, ID> implements EntityInformation<T, ID> {
// ...
@Override
public boolean isNew(T entity) {
ID id = getId(entity); // 1. id κ°μ κ°μ§κ³ μ¨λ€.
Class<ID> idType = getIdType();
if (!idType.isPrimitive()) { // 2. id κ°μ΄ primitive νμ
μΈμ§ reference νμ
μΈμ§ νμΈ
return id == null;
}
if (id instanceof Number n) {. // 3. id κ°μ΄ Number instance μΈμ§ νμΈ
return n.longValue() == 0L; // 4. id κ°μ΄ 0 μΈμ§ νμΈ
}
throw new IllegalArgumentException(String.format("Unsupported primitive id type %s", idType));
}
// ...
}
ν΄λΉ λ©μλμμ entity
μ id
κ°μ κ°μ Έμ μ¬λ¬ 쑰건μ λ§λμ§ νμΈνλλ° μ°λ¦¬κ° μ§μ ν΄μ€ κ°(1L)μ κ°μ§κ³ νμΈν΄λ³΄μ.
- μ°λ¦¬κ° μ§μ ν΄μ€ κ°
1L
μ λ°ν UserEntity
μid
λlong
μΌλ‘primitive
νμ- μ¬κΈ°μ
id
μ νμ μ΄ μLong
μ΄ μλκ³long
μΈμ§λkotlin
μμ μ μΈνLong
νμ μ΄java
λ‘ λ³νλλ©΄μ μ΄λ»κ² νμ μ΄ λ³κ²½λλμ§ μκ³ μμ΄μΌ μ΄ν΄κ° λλ λΆλΆμ΄λ€. - κ°λ¨νκ² νμΈν΄ λ³΄κ³ μΆλ€λ©΄
intellij
μkotlin
μjava
λ‘decompile
νλ κΈ°λ₯μ μ¬μ©ν΄λ³΄λ©΄ νμΈν΄ λ³Ό μ μλ€. - λΉ λ₯΄κ² μ€λͺ
ν΄λ³΄μλ©΄
kotlin
μμjava
λ‘ λ³ν λ λtype
μ κ²½μ°λvar/val
μΈμ§nullable
μΈμ§ μ λ°λΌ νμ μ΄ κ²°μ λλλ°Non-nullable Long(val id: Long = 0L)
μ κ²½μ°λjvm
μμtype
μ΅μ νλ‘ μΈν΄long
μΌλ‘ λ³νλκ² λλ€.
- μ¬κΈ°μ
id
μ κ°μ1L
λ‘long
νμ μ΄κΈ° λλ¬ΈμNumber
μ μΈμ€ν΄μ€κ° λ§λ€.- μ΄ λΆλΆ λλ¬Έμ μ¬κΈ°κΉμ§ κΈ΄ μ¬μ μ νκ² λμλλ°, μ¬κΈ°μ
id
μ κ°μ νμΈνλλ°, μ§κΈid
μ κ°μ1L
λ‘0
μ΄ μλκΈ° λλ¬Έμfalse
λ₯Ό λ°ννκ² λλ€.
λ§μΉ¨λ΄ μ°λ¦¬λ isNew()
λ©μλκ° μ΄λ€ κ°μ λ°ννλμ§ νμΈνμλ€. κ·ΈλΌ λ€μ save()
λ©μλλ‘ λμκ°λ³΄μ.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
@Transactional
public <S extends T> S save(S entity) {
Assert.notNull(entity, ENTITY_MUST_NOT_BE_NULL);
if (entityInformation.isNew(entity)) {
entityManager.persist(entity);
return entity;
} else {
return entityManager.merge(entity);
}
}
save()
λ©μλλ₯Ό 보면 isNew()
κ° false
μΌλ merge
λ₯Ό νΈμΆνλλ‘ λμ΄ μλ€.
persist
μ merge
μ μ°¨μ΄μ μ κ°λ¨νκ² μ€λͺ
ν΄μ insert
μ select -> insert / update
μ΄ μ°¨μ΄κ° μλ€.
- persist: insert 쿼리 λμ
- merge: select 쿼리 μ΄ν ν΄λΉ μλ³μμ λν κ°μ΄ μλ€λ©΄ update λ₯Ό μλ€λ©΄ insert λ₯Ό νλλ‘ λμ
μ λκ°μ§μ μ°¨μ΄μ λλ¬Έμ νμ¬ UserEntity
μ id
κ°μ μ§μ μ§μ ν΄μ€¬μλ select
μΏΌλ¦¬κ° μΆκ°μ μΌλ‘ λ°μνκ² λ μ΄μ λ€.
μ¬μ€ μ°λ¦¬κ° λ°λΌλ λμμ λ¨μ insert
μΈλ°, λ΄λΆμ μΌλ‘ μκ°λ³΄λ€ 볡μ‘ν λ‘μ§μ΄ λμκ°κ³ μμΌλ©΄μ, μ½κ°μ λΆνμνλ€
μκ°λ μ μλ μΏΌλ¦¬μΈ select
μΏΌλ¦¬κ° νλ² λ λ°μνλ€λ λΆλΆμμ μ΄ λΆλΆμ κ°μ μν¬ μ μλ λ°©λ²μ μμμ§ μ°Ύμ보μ.
β»οΈ κ°μ λ°©μ
Spring Data JPA
μμ id
λ₯Ό μ§μ μ§μ νμλ select
μΏΌλ¦¬κ° λ°μνμ§ μλλ‘ κ°μ λ°©μμ λν΄ μμ보μ.
save()
λ©μλλ₯Ό μ§μ ꡬννμ¬ μ¬μ©νλ€.Spring Data JPA
λ₯Ό μ¬μ©νμ§ μλλ€.μ?Persistable
μ μ§μ ꡬννλ€.
νμλ 3κ°μ§ μ λ λ°©λ²μ μκ°ν΄λ³΄κ³ μ μ©μμΌ λ΄€λ€.(νΉμ κ°μ ν μ μλ λ°©λ²μ΄ λ μλ€λ©΄ 곡μ ν΄μ£ΌμΈμ!)
μ°μ 첫λ²μ§Έ λ°©λ².
save()
λ©μλλ₯Ό Spring Data JPA
μμ ꡬνλμ΄ μλ λ©μλλ₯Ό μ¬μ©νλκ² μλλΌ λ΄κ° μ§μ ꡬνν΄μ μ¬μ©νλ€!
μ¬μ€ μ λ¬Έμ λ save()
λ©μλμμλ§ λ°μνλ λ¬Έμ λ μλλ€. delete
λ update
μμλ λ¬Έμ κ° λ°μ ν μλ μλ€. κ·Έλ¦¬κ³ λ§€λ² save()
λ₯Ό μ§μ ꡬννλ건 λ²κ±°λ‘μ΄ μΌμ΄λ€.
λλ²μ§Έ λ°©λ².
Spring Data JPA
λ₯Ό μ¬μ©νμ§ μλλ€!
κ·Έλ λ€. Spring
μμ DB
λ₯Ό λ€λ£°λ Spring Data JPA
λ§ μλκ² μλλ€,
κ·Έλ₯ JPA
λ₯Ό μ¬μ©ν΄λ λκ³ , JDBC Template
μ μ¬μ©ν΄λ λκ³ Mybatis
λ₯Ό μ¬μ©ν΄λ λλ€.
λ€λ§, μ΄ λ¬Έμ λλ¬Έμ μ΄λ―Έ νλ‘μ νΈμ μ μ© λμ΄ μκ³ , μ μ©νκΈ°λ‘ν κΈ°μ μ체λ₯Ό λ°κΎΈλ건 μ‘°κΈ μκ°ν΄λ³Ό μ¬μ§κ° μλ€.
save
, update
, delete
μ λλ§ JDBC
λ₯Ό μ¬μ©ν΄λ λκΈ΄ νλ€. μ΄ λΆλΆμ μ΄λμ λ μ·¨ν₯ μ°¨μ΄κ° μμκ² κ°λ€.
μΈλ²μ§Έ λ°©λ².
save()
μμ μ¬μ©λλ λ©μλ λ€μ λ΄κ° μ§μ ꡬννμ¬ μ¬μ©νλλ‘ νλ€.
μ΄ λ°©λ²μ μ΄μΌκΈ° νκΈ° μν΄ μ§κΈκΉμ§ λ§μ κ³Όμ μ μ€λͺ
νλ€κ³ ν΄λ 무방νλ€.
μ¬μ€ μ΄κ±Έ μλνκ±΄μ§ μ λͺ¨λ₯΄κ² μΌλ, Persistable
μ μ§μ ꡬννμ¬ μ¬μ©ν μ μλ€.
Persistable
μ μλμ κ°μ΄ μ μΈλμ΄ μλ interface
λ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Persistable<ID> {
/**
* Returns the id of the entity.
*
* @return the id. Can be {@literal null}.
*/
@Nullable
ID getId();
/**
* Returns if the {@code Persistable} is new or was persisted already.
*
* @return if {@literal true} the object is new.
*/
boolean isNew();
}
μ΄κ±Έ UserEntity
μμ ꡬνμ νκ² λλ©΄ μλμ κ°μ΄ ꡬνν μ μλλ°.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Entity
@Table(name = "users")
class UserEntity(
@Id
// @GeneratedValue(strategy = GenerationType.IDENTITY) ν΄λΉ λΆλΆμ μ£Όμ μ²λ¦¬ λλ μμ
val id: Long = 0L,
@Column(nullable = false)
val username: String,
@Column(nullable = false)
val password: String
) : Persistable<Long> {
@Transient
private var isNew = true
override fun isNew(): Boolean = isNew
override fun getId(): Long? = id
@PrePersist
@PostLoad
private fun markNotNew() {
isNew = false
}
}
interface UserRepository : JpaRepository<UserEntity, Long>
μ΄λ κ² κ΅¬νμ νκ² λλ©΄ SimpleJpaRepository
μμ save()
λ©μλλ₯Ό νΈμΆ νμλ isNew()
λ©μλλ₯Ό μ¬μ©νκ² λλλ°
AbstractEntityInformation
μ isNew()
λ©μλλ₯Ό νΈμΆνλκ² μλλΌ
JpaPersistableEntityInformation
μ isNew()
λ©μλλ₯Ό νΈμΆνκ² λλ€.
μ JpaPersistableEntityInformation
μ μ¬μ©νλμ§λ μ’ λ λ΄λΆμ μΌλ‘ λ΄μΌ νλλ° κ°λ¨νκ² μ€λͺ
νμλ©΄
Persistable
μ ꡬννμ§ μμ μν°ν°μ κ²½μ°λ κΈ°λ³Έ ꡬνμ²΄μΈ AbstractEntityInformation
μ νΈμΆνκ² λκ³
ꡬνν κ²½μ°μλ JpaPersistableEntityInformation
κ° νΈμΆ λλλ‘ Spring Data JPA
λ΄λΆ λ‘μ§μ΄ μμ±λμ΄ μλ€κ³ μκ°νλ©΄ λλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class JpaPersistableEntityInformation<T extends Persistable<ID>, ID>
extends JpaMetamodelEntityInformation<T, ID> {
/**
* Creates a new {@link JpaPersistableEntityInformation} for the given domain class and {@link Metamodel}.
*
* @param domainClass must not be {@literal null}.
* @param metamodel must not be {@literal null}.
* @param persistenceUnitUtil must not be {@literal null}.
*/
public JpaPersistableEntityInformation(Class<T> domainClass, Metamodel metamodel,
PersistenceUnitUtil persistenceUnitUtil) {
super(domainClass, metamodel, persistenceUnitUtil);
}
@Override
public boolean isNew(T entity) {
return entity.isNew();
}
@Nullable
@Override
public ID getId(T entity) {
return entity.getId();
}
}
μ¬κΈ°μ entity.isNew()
λ₯Ό νΈμΆνκ³ return
νλλ° μ΄κ² λ°λ‘ Persistable
μ isNew()
λ©μλλ€.
μ°λ¦° Persistable
μ UserEntity
μμ ꡬννκ³ μκΈ° λλ¬Έμ μ°λ¦¬κ° ꡬνν isNew()
λ©μλλ₯Ό νΈμΆνμ¬ μ¬μ©νκ² λλ€.
κ·ΈλΌ UserEntity
μμ ꡬνν Persistable
κ΄λ ¨ λ©μλμ markNotNew
λ©μλμ λν΄ μμ보μ.
νλμ© μ€λͺ νμλ©΄
isNew
:true
,false
μ κ°μ μ μ₯νκ³ μλ νλ‘νΌν°isNew()
:isNew
νλ‘νΌν°μ κ°μ λ°ννλ λ©μλ, μ¬κΈ°μtrue
λ₯Ό λ°ννλ©΄insert
λ₯Όfalse
λ₯Ό λ°ννλ©΄update
κ° λμνκ² λλ€.getId()
: νμ¬ μν°ν°μ μλ³μ(id) λ₯Ό λ°ννλ λ©μλmarkNotNew()
: μ΄κ² μ λ§ μ€μν λ©μλμΈλ°, μ΄κ±΄isNew
νλ‘νΌν°μ κ°μ μ€μ ν΄ μ£Όλ λ©μλλ‘isNew
κ° νμ κ°μ κ°μ λ°ννκ² λλ©΄insert
λupdate
μ€ νκ°μ§ λμλ§ νκ² λκΈ° λλ¬Έμ κ·Έκ±Έ λ°©μ§ νκΈ° μν λ©μλ
λμμ νλ¦μ λ°λΌ μ΄ν΄λ³΄μλ©΄
UserEntity
μμ± ->isNew : true
userRepository.save(enttiy)
νΈμΆJpaPersistableEntityInformation.isNew(entity)
νΈμΆisNew() β true β persist() (INSERT μν)
->isNew : true
INSERT μ§μ μ @PrePersist β markNotNew() νΈμΆ
->isNew : false
μ΄ν DB μ‘°ν μ @PostLoad β markNotNew() νΈμΆ
->isNew : false
4λ² λμ μ΄ν isNew
κ° false
λ‘ λ³κ²½λκΈ° λλ¬Έμ κ·Έλ¦¬κ³ μ΄ν DB
μμ μ‘°ν μ isNew
κ° false
λ‘ λ³κ²½ λκΈ° λλ¬Έμ μ΅μ΄μλ§ insert
κ° λ°μνκ³ μ΄νμλ update
κ° λ°μνκ² λλ€.
μ΄λ κ² κ΅¬ννκ³ ν μ€νΈ μ½νΈλ₯Ό ν΅ν΄ μ§μ μΏΌλ¦¬κ° μ΄λ»κ² λ°μλλμ§ μ΄ν΄λ³΄μ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@DataJpaTest
class PersistableTest {
@Autowired
private lateinit var entityManager: TestEntityManager
@Autowired
private lateinit var userRepository: UserRepository
@Test
@DisplayName("Persistable ꡬν νμλ")
fun persistableTest() {
val stats: Statistics =
entityManager.entityManager
.unwrap(Session::class.java)
.sessionFactory
.statistics.also {
it.isStatisticsEnabled = true
it.clear()
}
// UserEntity() λ₯Ό μμ±ν΄ μ€ λ ID κ°μ μ§μ μ€μ ν΄μ£Όκ³ μλ λͺ¨μ΅
val entity = UserEntity(id = 1L, username = "test_user", password = "1234")
userRepository.save(entity)
userRepository.flush()
val queryCount: Long = stats.prepareStatementCount
println("Query Count: $queryCount")
Assertions.assertThat(queryCount).isGreaterThanOrEqualTo(1)
}
}
ν
μ€νΈ μ½λλ Persistable
μ ꡬννμ§ μμμλ μμ±ν μ½λμ Assertions
λΆλΆ λ§ μ μΈνκ³ λμΌνλ€.
1
2
3
4
5
6
7
8
Hibernate:
insert
into
users
(password, username, id)
values
(?, ?, ?)
Query Count: 1
λ‘κ·Έλ₯Ό νμΈν΄ 보면 Persistable
μ ꡬννμ§ μμμλ λ°μνλ select
κ° μ¬λΌμ§κ±Έ λ³Ό μ μλ€.
μΈλ²μ§Έ λ°©λ²μΈ βPersistable
μ μ§μ ꡬνβ μ νμ©νκ² λλ©΄ μ‘°κΈμ 볡μ‘νμ§λ§ Spring Data JPA
μ κΈ°λ₯μ λͺ¨λ νμ©ν μ μμΌλ©΄μ λΆνμν 쿼리 λ°μμ μ€μΌ μ μλκ² λλ€.
3κ°μ§ λ°©λ²λ€ μ€ μ λ΅μ μλ€. μν©μ΄λ μ·¨ν₯μ λ§κ² λ°©λ²μ μ ννκ³ νμ©νλ©΄ μ’μκ² κ°λ€.
μ μ°Έκ³ λ‘ βUserEntity
μΈμ λ€λ₯Έ μ λ§μ Entity
κ° μλλ° μ΄κ±Έ λͺ¨λ ꡬνν΄μ μ¬μ©ν΄μΌ νλμ?β λΌλ
μ§λ¬Έμ΄ μμ μ μλλ°, @MappedSuperclass
λ₯Ό μ¬μ©νμ¬ BaseEntity
λ₯Ό λ§λ€κ³ κ·Έκ±Έ μμ λ°λ ννλ‘ κ΅¬ννλ©΄ λͺ¨λ Entity
μ μ§μ ꡬνν νμκ° μμ΄μ§κ² λλ€.
κ°λ¨νκ² μ¬μ΄λ νλ‘μ νΈμμ μ¬μ©μ€μΈ BaseEntity
μ μ½λλ₯Ό 보면 μλμ κ°λ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@MappedSuperclass
internal abstract class BaseEntity() : Persistable<Long> {
@Id
val id: Long = SnowflakeIdCreator.nextId()
@Column(nullable = false)
val createdAt: Instant = Instant.now()
@Column(nullable = false)
var updatedAt: Instant = Instant.now()
@Column(nullable = false)
val createdBy: Long = 0L
@Column(nullable = false)
var updatedBy: Long = 0L
@Transient
private var isNew = true
override fun isNew(): Boolean = isNew
override fun getId(): Long? = id
@PrePersist
@PostLoad
private fun markNotNew() {
isNew = false
}
}
π‘ λ§λ¬΄λ¦¬
μ¬μ€ μ΄ κΈμ Persistable
μ ꡬν λ°©λ²μ μ€λͺ
νλ κΈ μ΄κΈ°λ νμ§λ§, νμ΅ν λ μ¬μ©νλ λ°©μλ€μ΄ μλ λ€λ₯Έ λ°©μμΌλ‘ Spring Data JPA
λ₯Ό μ¬μ©νμλ κ³ λ €ν λΆλΆμ΄ μλ€λ λ΄μ©μ ν¨κ» μλ €μ£Όκ³ μΆμ΄ μμ±ν κΈμ΄λ€.
λ¨μν Select
쿼리 νλκ° λ λ°μνκ² λκ° ν° λ¬Έμ λκ³ ν μ μκ² μ§λ§, λ³κ±° μλλΌκ³ κ·Έλ₯ λμ΄κ°κΈ° 보λ€λ
μ λ΄ μλμ λ€λ₯Έ λμμ νκ² λκ±΄μ§ νμΈν΄λ³΄κ³ νμ
νλ μ΅κ΄μ΄ κ°λκ² μ€μνκ² κ°λ€.
μΆκ°λ‘ κ·Έ νμμ μ΄λ»κ² νλ©΄ κ°μ μν¬ μ μμμ§λ ν¨κ» μ΄ν΄λ³΄λ©΄ λλμ± μ’μκ² κ°λ€.
μ€λλ§μ Spring Data JPA
λ΄λΆ μ½λλ₯Ό λλ²κΉ
λͺ¨λλ‘ νλμ© λ°λΌκ°λ³΄λ©΄μ μ΄ν΄λ΄€λλ°,
μμ μ΄λ λ¬λΌμ§ μ½λλ€μ΄ μλκ±Έ μμ μμλ€. μ λ§ λΉ λ₯΄κ² λ³ννλ κΈ°μ νΉμ±μ λͺ¨λ κ±Έ νμ
νκ³ μμμ μμ§λ§
μ’
μ’
λ΄λΆ λ‘μ§μ΄ μ΄λ»κ² λμ΄ μλμ§ μ΄ν΄λ³΄λ μ΅κ΄μ κ°λκ²λ μ’μκ² κ°λ€.(μΌλ¨ λλΆν°)