들어가며
안녕하세요~! ㅎㅎ
JPA 를 사용하면서 개발하던 도중에, 필요한 컬럼들에 대해서만 쿼리를 만들면 db 에 performance 성능향상이 이루어지지 않을까? 하는 의문심에 JPA 의 query statement 의 생성방식에 대하여 공부하고 있었는데요.
왜냐하면 불필요한 컬럼까지 update 하는 경우, index 걸린 column 같은 경우에는 성능에 악영향이 있기 때문입니다.
그래서 document 를 찾고 찾다보니 JPA 에서는 update / insert statement 를 만들어줄 때
statement 자체를 caching 하여 항상 일관된 query 문을 제공한다고 하는데요.
오늘은 JPA Hibernate 를 사용하면서 의문이 들었던 Query statement caching 이 어떻게 이루어지는지를 알아보겠습니다.
코드 들어가보기
도대체 어디서 Jdbc Statement 를 만들어줄까?
하여 코드를 찾아보니 AbstractEntityPersister 라는 클래스에서 scan 된 entity 객체의 property 들을 읽어와 만들어주고 있었습니다.
// doLateInit()
for ( int j = 0; j < joinSpan; j++ ) {
sqlInsertStrings[j] = customSQLInsert[j] == null
? generateInsertString( getPropertyInsertability(), j )
: substituteBrackets( customSQLInsert[j] );
sqlUpdateStrings[j] = customSQLUpdate[j] == null
? generateUpdateString( getPropertyUpdateability(), j, false )
: substituteBrackets( customSQLUpdate[j] );
sqlLazyUpdateStrings[j] = customSQLUpdate[j] == null
? generateUpdateString( getNonLazyPropertyUpdateability(), j, false )
: substituteBrackets( customSQLUpdate[j] );
sqlDeleteStrings[j] = customSQLDelete[j] == null
? generateDeleteString( j, true )
: substituteBrackets( customSQLDelete[j] );
sqlDeleteNoVersionCheckStrings[j] = customSQLDelete[j] == null
? generateDeleteString( j, false )
: substituteBrackets( customSQLDelete[j] ); //TODO: oops, fix!
}
코드를 보시면 지연초기화를 통해서 sql 에 대한 statement 를 만들어주는 것을 볼 수 있습니다.
핵심은 바로 generateInsertString / generateUpdateString 인데요. property 에 있는 insertability / updateability 를 통해 만드는 것으로 이해를 할 수 있습니다.
그래서 내부 함수를 다시 들어가보면 아래와 같은 코드가 나타나게 됩니다
if ( includeProperty[ index ] ) {
insert.addColumns(
getPropertyColumnNames( index ),
propertyColumnInsertable[index ],
propertyColumnWriters[index]
);
}
즉 JPA Entity Column 에 매핑된 정보를 기반으로 진행하게 된다는 것을 알 수 있네요.
한번 예시와 함께 같이 이해해볼까요?
@Entity
@Table(name = "student")
class Student(
@Id
@GeneratedValue(strategy = GenerationType.Identity)
val id: Long? = null,
@Column
val grade: String,
@Column(updatable = false)
val name: String,
@Column(insertable = false, updatable = false)
val address: String? = null
)
위에 작성된 entity 는 실제로는 말이 안되는 entity 이지만 ㅎㅎ
@Column 어노테이션에서의 insertable 과 updatable 의 default 설정은 true 입니다.
정말 query statement 문이 다르게 생성되는지를 확인하기 위하여
name 에 대한 Column 설정과 address 에 대한 Column 설정을 각기 다르게 가져가보겠습니다.
grade 는 모두 true,
address 는 update 와 insert 가 모두 false,
name 은 update 만 false 로 인지한 것을 볼 수 있네요.
그러면 각 ability 특성에 따라 query statement 가 생각한대로 생성되었는지 확인해보러 가보겠습니다.
정말로 insertable 이 true 인 grade 와 name 만 들어간 것을 볼 수 있네요
grade column 만 updatable true 이기에 grade column 만 들어가게 되었군요!
즉, JPA Entity 상으로 Column 어노테이션에 insertable / updatable 마킹만 제대로 잘 해놓으면 쿼리 최적화를 이루어 낼 수 있습니다.
Dynamic Update
하지만 변경된 column 에 대해서 update query 를 만들도록 하는 어노테이션이 있으니 그것이 바로 @dynamicUpdate 입니다
그렇다면 그냥 간단하게 @DynamicUpdate 사용하면 되지 왜 힘들게 updatable 를 마킹해야 할까요?
DynamicUpdate 어노테이션은 변경된 값을 감지하여 매번 query statement 문을 다시 생성하기 때문에 비용이 증가합니다.
그래서 application 성능에 안좋은 영향을 미칠수가 있다는 단점 또한 존재하죠.
따라서 엔티티에 낙관적락(Optimistic Lock) 을 사용하기 위한
@OptimisticLocking 어노테이션을 사용할 때가 아니라면 굳이 사용하지 않는 것이 좋겠죠?
public enum OptimisticLockType {
/**
* Perform no optimistic locking.
*/
NONE,
/**
* Perform optimistic locking using a dedicated version column.
*
* @see jakarta.persistence.Version
*/
VERSION,
/**
* Perform optimistic locking based on *dirty* fields as part of an expanded WHERE clause restriction for the
* UPDATE/DELETE SQL statement.
*/
DIRTY,
/**
* Perform optimistic locking based on *all* fields as part of an expanded WHERE clause restriction for the
* UPDATE/DELETE SQL statement.
*/
ALL
}
부가 설명을 해드리면 OptimisticLocking 을 사용할 때 DIRTY 와 ALL 의 경우
update 문에 들어가는 column 들을 활용해야 하기 때문에 꼭 @DynamicUpdate 를 사용해야 하는 제약이 걸리기 때문입니다.
오늘은 문서와 코드를 보면서 이해하는 시간을 가졌네요.
이제 배웠던 내용을 정리하면서 마치도록 하겠습니다.!
결론
- JPA Hibernate 에서는 Column 어노테이션에 명시된 insertable updatable 값에 따라 jdbc query statement 문을 caching한다
- insertable 과 updatable 의 default 값은 true 이다
- DynamicUpdate 는 변경된 컬럼만 update 문을 작성하도록 하지만, Column 어노테이션에 잘 명시하면 굳이 사용할 필요가 없다
- Column 어노테이션에 insertable updatable 을 마킹하면 관리차원에서도 편리하고, query statement caching 에도 유용하다
- DynamicUpdate 는 OptimisticLocking 을 사용할 때만 고려하도록 하자
insert 와 update 문이 어떻게 만들어지고 어떻게 활용하면 좋을지에 대한 정리였습니다~! ^-^
참고
Hibernate document
@DynamicUpdate with spring
'Developer > Spring' 카테고리의 다른 글
Spring 에서 Transactional 을 사용할 때 Exception 이 발생하는 상황에 주의하자 (1) | 2023.06.11 |
---|---|
Spring WebFlux 에서는 어떻게 Kotlin Coroutine 을 지원하고 있을까? ( feat. Context ) (3) | 2023.05.27 |
[Test] 효과적인 Integration Test 를 위한 Spring Test Context 를 구성해보자 (2) | 2022.09.24 |
[Kotlin/Feign] Apache Http Client5 에 대해 알아보자 (0) | 2021.11.06 |
[JPA] DataSource 를 연결하는 방법 & RoutingDataSource 설정 (2) | 2021.06.19 |