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());
        }

    }

 

  1. JPA Default FlushMode는 `AUTO`이다.
  2. 조회 쿼리 실행전에 autoFlushIfRequired이 필요한지 확인한다. 이때 autoFlushIfRequired 인자로 getAffectedTableNames 테이블 이름을 넘긴다.
  3. 트랜잭션이 isTransactionInProgress이면 AutoFlushEvent 이벤트를 발생한다
  4. flushIsReallyNeeded 메서드에서 FlushMode가 `ALWAYS`이거나 ActionQueue(flush 되기 전까지 action을 저장하고 있는)의 테이블과 getAffectedTableNames로 넘어온 테이블을 비교한다. 이떄 flush되지 않은 테이블에 데이터를 조회하는 경우 먼저 해당 action을 flush 한다.
  5. 따라서 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)

 

+ Recent posts