Entity를 save한 후에 해당 엔디티를 find하면 transaction이 끝나기전에 flush()되는 이유
우선 save() 호출 후 findByName() 호출하는 부분부터 디버깅을 해보았다. 주요 클래스들만 추려보면..
1. ConcreteSqmSelectQueryPlan
public ConcreteSqmSelectQueryPlan(SqmSelectStatement<?> sqm, String hql, DomainParameterXref domainParameterXref, Class<R> resultType, TupleMetadata tupleMetadata, QueryOptions queryOptions) {
this.sqm = sqm;
this.domainParameterXref = domainParameterXref;
this.rowTransformer = determineRowTransformer(sqm, resultType, tupleMetadata, queryOptions);
UniqueSemantic uniqueSemantic;
if (sqm.producesUniqueResults() && !AppliedGraphs.containsCollectionFetches(queryOptions)) {
uniqueSemantic = UniqueSemantic.NONE;
} else {
uniqueSemantic = UniqueSemantic.ALLOW;
}
this.executeQueryInterpreter = (resultsConsumer, executionContext, sqmInterpretation, jdbcParameterBindings) -> {
SharedSessionContractImplementor session = executionContext.getSession();
JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect();
Object var10;
try {
RegistrationHandler subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler(session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.selectStatement, JdbcParametersList.empty(), jdbcParameterBindings);
session.autoFlushIfRequired(jdbcSelect.getAffectedTableNames(), true);
var10 = session.getFactory().getJdbcServices().getJdbcSelectExecutor().executeQuery(jdbcSelect, jdbcParameterBindings, listInterpreterExecutionContext(hql, executionContext, jdbcSelect, subSelectFetchKeyHandler), this.rowTransformer, (Class)null, (sql) -> {
return executionContext.getSession().getJdbcCoordinator().getStatementPreparer().prepareQueryStatement(sql, false, (ScrollMode)null);
}, resultsConsumer);
} finally {
domainParameterXref.clearExpansions();
}
return var10;
};
this.listInterpreter = (unused, executionContext, sqmInterpretation, jdbcParameterBindings) -> {
SharedSessionContractImplementor session = executionContext.getSession();
JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect();
List var11;
try {
RegistrationHandler subSelectFetchKeyHandler = SubselectFetch.createRegistrationHandler(session.getPersistenceContext().getBatchFetchQueue(), sqmInterpretation.selectStatement, JdbcParametersList.empty(), jdbcParameterBindings);
//breakPoint
session.autoFlushIfRequired(jdbcSelect.getAffectedTableNames(), true);
var11 = session.getFactory().getJdbcServices().getJdbcSelectExecutor().list(jdbcSelect, jdbcParameterBindings, listInterpreterExecutionContext(hql, executionContext, jdbcSelect, subSelectFetchKeyHandler), this.rowTransformer, uniqueSemantic);
} finally {
domainParameterXref.clearExpansions();
}
return var11;
};
this.scrollInterpreter = (scrollMode, executionContext, sqmInterpretation, jdbcParameterBindings) -> {
SharedSessionContractImplementor session = executionContext.getSession();
JdbcOperationQuerySelect jdbcSelect = sqmInterpretation.getJdbcSelect();
ScrollableResultsImplementor var9;
try {
JdbcSelectExecutor jdbcSelectExecutor = session.getFactory().getJdbcServices().getJdbcSelectExecutor();
session.autoFlushIfRequired(jdbcSelect.getAffectedTableNames(), true);
var9 = jdbcSelectExecutor.scroll(jdbcSelect, scrollMode, jdbcParameterBindings, new SqmJdbcExecutionContextAdapter(executionContext, jdbcSelect), this.rowTransformer);
} finally {
domainParameterXref.clearExpansions();
}
return var9;
};
}
2. SessionImpl
public boolean autoFlushIfRequired(Set<String> querySpaces, boolean skipPreFlush) throws HibernateException {
this.checkOpen();
if (!this.isTransactionInProgress()) {
return false;
} else {
AutoFlushEvent event = new AutoFlushEvent(querySpaces, skipPreFlush, this);
this.fastSessionServices.eventListenerGroup_AUTO_FLUSH.fireEventOnEachListener(event, AutoFlushEventListener::onAutoFlush);
return event.isFlushRequired();
}
}
3. DefaultAutoFlushEventListener
public void onAutoFlush(AutoFlushEvent event) throws HibernateException {
EventSource source = event.getSession();
SessionEventListenerManager eventListenerManager = source.getEventListenerManager();
EventManager eventManager = source.getEventManager();
HibernateMonitoringEvent partialFlushEvent = eventManager.beginPartialFlushEvent();
try {
eventListenerManager.partialFlushStart();
if (this.flushMightBeNeeded(source)) {
ActionQueue actionQueue = source.getActionQueue();
EventSource session = event.getSession();
PersistenceContext persistenceContext = session.getPersistenceContextInternal();
if (!event.isSkipPreFlush()) {
this.preFlush(session, persistenceContext);
}
int oldSize = actionQueue.numberOfCollectionRemovals();
this.flushEverythingToExecutions(event, persistenceContext, session);
if (this.flushIsReallyNeeded(event, source)) {
LOG.trace("Need to execute flush");
event.setFlushRequired(true);
HibernateMonitoringEvent flushEvent = eventManager.beginFlushEvent();
try {
this.performExecutions(source);
//flush
this.postFlush(source);
this.postPostFlush(source);
} finally {
eventManager.completeFlushEvent(flushEvent, event, true);
}
StatisticsImplementor statistics = source.getFactory().getStatistics();
if (statistics.isStatisticsEnabled()) {
statistics.flush();
}
} else {
LOG.trace("No need to execute flush");
event.setFlushRequired(false);
actionQueue.clearFromFlushNeededCheck(oldSize);
}
}
} finally {
eventManager.completePartialFlushEvent(partialFlushEvent, event);
eventListenerManager.partialFlushEnd(event.getNumberOfEntitiesProcessed(), event.getNumberOfEntitiesProcessed());
}
}
- JPA Default FlushMode는 `AUTO`이다.
- 조회 쿼리 실행전에 autoFlushIfRequired이 필요한지 확인한다. 이때 autoFlushIfRequired 인자로 getAffectedTableNames 테이블 이름을 넘긴다.
- 트랜잭션이 isTransactionInProgress이면 AutoFlushEvent 이벤트를 발생한다
- flushIsReallyNeeded 메서드에서 FlushMode가 `ALWAYS`이거나 ActionQueue(flush 되기 전까지 action을 저장하고 있는)의 테이블과 getAffectedTableNames로 넘어온 테이블을 비교한다. 이떄 flush되지 않은 테이블에 데이터를 조회하는 경우 먼저 해당 action을 flush 한다.
- 따라서 transaction이 끝나기전에 flush가 된다.
FlushMode란..
Hibernate에 FlushMode라는 설정값이 있었고 JPA에서는 `COMMIT`, `AUTO`만 사용가능하다고 명시되어 있었다.
| Hibernate FlushMode | JPA FlushModeType | Interpretation |
| MANUAL | Never flush automatically | |
| COMMIT | COMMIT | Flush before transaction commit |
| AUTO | AUTO | Flush before transaction commit, and before execution of a query whose results might be affected by modifications held in memory |
| ALWAYS | Flush before transaction commit, and before execution of every query |
스프링에서 flushMode 설정하는 방법은 application.yml 파일 또는 application.properties 파일에 추가
spring.jpa.properties.org.hibernate.flushMode=COMMIT
demo
demo.service
@Transactional
public Member test1() {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
Member member = new Member(1L, "test", "test@gmail.com", passwordEncoder.encode("123"));
return mainRepository.save(member);
}
@Transactional()
public String test6() {
Member member = mainRepository.findByEmail("123");
if (member == null) {
member = test1();
}
log.info("before findByEmail");
Member member1 = mainRepository.findByEmail("test@gmail.com");
log.info("after findByEmail");
return member.toString();
}
spring.jpa.properties.org.hibernate.flushMode=COMMIT 적용 전(Default AUTO)
Hibernate:
select
m1_0.id,
m1_0.email,
m1_0.name,
m1_0.password
from
member m1_0
where
m1_0.email=?
Hibernate:
select
m1_0.id,
m1_0.email,
m1_0.name,
m1_0.password
from
member m1_0
where
m1_0.id=?
Hibernate:
select
next value for member_seq
INFO 26636 --- [demo] [nio-8080-exec-1] : before findByEmail
Hibernate:
insert
into
member
(email, name, password, id)
values
(?, ?, ?, ?)
Hibernate:
select
m1_0.id,
m1_0.email,
m1_0.name,
m1_0.password
from
member m1_0
where
m1_0.email=?
INFO 26636 --- [demo] [nio-8080-exec-1] : after findByEmail
spring.jpa.properties.org.hibernate.flushMode=COMMIT 적용 후.. 트랜잭션 종료 시점(=commit 시점)에 flush
Hibernate:
select
m1_0.id,
m1_0.email,
m1_0.name,
m1_0.password
from
member m1_0
where
m1_0.email=?
Hibernate:
select
m1_0.id,
m1_0.email,
m1_0.name,
m1_0.password
from
member m1_0
where
m1_0.id=?
Hibernate:
select
next value for member_seq
INFO 26772 --- [demo] [nio-8080-exec-1] : before findByEmail
Hibernate:
select
m1_0.id,
m1_0.email,
m1_0.name,
m1_0.password
from
member m1_0
where
m1_0.email=?
INFO 26772 --- [demo] [nio-8080-exec-1] : after findByEmail
Hibernate:
insert
into
member
(email, name, password, id)
values
(?, ?, ?, ?)
출처
An Introduction to Hibernate 6 (jboss.org)
ActionQueue (Hibernate JavaDocs) (jboss.org)
'Others' 카테고리의 다른 글
| Redis, Prometheus, Grafana 모니터링 (0) | 2025.10.31 |
|---|---|
| mybatis sql 로그 pretty하게 남기기 (4) | 2025.07.31 |
| Redis + Sentine (0) | 2025.03.28 |
| 개발자 커뮤니티 QNA 답변하면서 공부/찾은 내용(#3) (0) | 2024.10.10 |
| 개발자 커뮤니티 QNA 답변하면서 공부/찾은 내용(#1) (0) | 2024.08.23 |