[TOC]
因事務(wù)導(dǎo)致hibernate延遲加載出現(xiàn)no session異常
文章來源: 臨窗旋墨的博客
本人對hibernate 的使用不是特別的熟悉,這里只是記錄一次幫助同事解決異常排查的過程.
項(xiàng)目中的spring版本為4.1.6
貼出的源碼的spring版本為5.1.9
1 項(xiàng)目技術(shù)框架
spring + springmvc + hibernate + freemarker
2 異常的產(chǎn)生現(xiàn)象
controller中直接調(diào)用serviceA中的方法A 頁面可正常渲染;
controller調(diào)用serviceB中的方法B, 方法B中調(diào)用serviceA中的方法A,渲染頁面的時(shí)候報(bào)異常:no session
3 問題追蹤
- 根據(jù)現(xiàn)象,最直觀的原因是session已經(jīng)關(guān)閉, 然后獲取懶加載對象的屬性的時(shí)候,沒有獲取到session;
- 直接調(diào)用無錯(cuò), 但是經(jīng)過另外一個(gè)service調(diào)用后就no session, 說明兩個(gè)session可能并不是一個(gè)session
3.1 確認(rèn)是否是一個(gè)session的調(diào)試:結(jié)果不是一個(gè)session;
本項(xiàng)目開啟了OpenSessionInViewFilter
過濾器,
- 斷點(diǎn)跟蹤OpenSessionInViewFilter中的open的Session, 記錄sesson的hashcode-->code1;
- 進(jìn)入serviceB, 通過獲取
sessionFactory.getCurrentSession()
, 記錄session的hashcode, 等于code1, 表示此處使用的session是filter中打開的session; - 從serviceB進(jìn)入serviceA,獲取當(dāng)前session,獲得的hashcode值與
code1
不一致;
3.2 為什么不是一個(gè)session: 因?yàn)椴辉谝粋€(gè)事物中
- 本項(xiàng)目的事務(wù)在配置文件中由切面配置;
- 由于命名方式的問題,
- 入口方法B不要求在事物中運(yùn)行( propagation="SUPPORTS")
- 方法A需要在一個(gè)事物中運(yùn)行((propagation="REQUIRED"))
- 導(dǎo)致進(jìn)入方法B后不需要事務(wù),但是B調(diào)用的A方法在事務(wù)中運(yùn)行
關(guān)于事務(wù)的傳播性,小伙伴可自行復(fù)習(xí);
3.3 解決問題:修改方法名,保證在一個(gè)事務(wù)中
修改方法名后, 保證兩個(gè)方法在一個(gè)事務(wù)中,再次驗(yàn)證, 兩者session一致, 延遲加載的屬性可正常加載, 在OpenSessionInViewFilter的finally代碼塊中亦可正常關(guān)閉;
4 再問:openSession和getCurrentSession的區(qū)別
- 采用getCurrentSession()創(chuàng)建的session在commit或rollback時(shí)會自動(dòng)關(guān)閉州弟,
- 采用openSession()創(chuàng)建的session必須手動(dòng)關(guān)閉
- 采用getCurrentSession()創(chuàng)建的session會綁定到當(dāng)前線程中,
- 采用openSession() 創(chuàng)建的session則不會.所以以上的session是一直都是一個(gè)session.
本問題中因?yàn)榉椒ˋ中
session
(getCurrentSession())在事務(wù)中運(yùn)行, 所以在commit之后被關(guān)閉掉了,造成延遲加載事務(wù)失敗;
5 三問: 為什么開啟新事物中的session和當(dāng)前線程中的session不一致
5.1 spring如何管理hibernate的session
查看配置文件中關(guān)于sessionFactory的配置為LocalSessionFactoryBean
LocalSessionFactoryBean` implements FactoryBean<SessionFactory>.......
SessionFactory.getCurrentSession源碼追蹤
SessionFactoryImpl#getCurrentSession
public Session getCurrentSession() throws HibernateException {
if ( currentSessionContext == null ) {
throw new HibernateException( "No CurrentSessionContext configured!" );
}
return currentSessionContext.currentSession();
}
SpringSessionContext#currentSession
public Session currentSession() throws HibernateException {
/*
*以sessionFactory為key去當(dāng)前線程中獲取session(此處不再展開源碼), 可能是在 OpenSessionInViewFilter 中存入線程的, 參見 OpenSessionInViewFilter 的openSessIon代碼
*/
Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
if (value instanceof Session) {
return (Session) value;
}
else if (value instanceof SessionHolder) {
// hibernate事務(wù)管理器
SessionHolder sessionHolder = (SessionHolder) value;
Session session = sessionHolder.getSession();
if (!sessionHolder.isSynchronizedWithTransaction() &&
TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, false));
sessionHolder.setSynchronizedWithTransaction(true);
// Switch to FlushMode.AUTO, as we have to assume a thread-bound Session
// with FlushMode.MANUAL, which needs to allow flushing within the transaction.
FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
if (flushMode.equals(FlushMode.MANUAL) &&
!TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.AUTO);
sessionHolder.setPreviousFlushMode(flushMode);
}
}
return session;
}
else if (value instanceof EntityManagerHolder) {
// JpaTransactionManager
return ((EntityManagerHolder) value).getEntityManager().unwrap(Session.class);
}
if (this.transactionManager != null && this.jtaSessionContext != null) {
try {
if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
Session session = this.jtaSessionContext.currentSession();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
new SpringFlushSynchronization(session));
}
return session;
}
}
catch (SystemException ex) {
throw new HibernateException("JTA TransactionManager found but status check failed", ex);
}
}
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Session session = this.sessionFactory.openSession();
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
session.setFlushMode(FlushMode.MANUAL);
}
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.registerSynchronization(
new SpringSessionSynchronization(sessionHolder, this.sessionFactory, true));
TransactionSynchronizationManager.bindResource(this.sessionFactory, sessionHolder);
sessionHolder.setSynchronizedWithTransaction(true);
return session;
}
else {
throw new HibernateException("Could not obtain transaction-synchronized Session for current thread");
}
}
對比OpenSessionInViewFilter#doFilterInternal ,可以發(fā)現(xiàn)兩者獲取hibernate 的session的邏輯相似;
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
SessionFactory sessionFactory = lookupSessionFactory(request);
boolean participate = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
String key = getAlreadyFilteredAttributeName();
if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
// Do not modify the Session: just set the participate flag.
participate = true;
}
else {
boolean isFirstRequest = !isAsyncDispatch(request);
if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) {
logger.debug("Opening Hibernate Session in OpenSessionInViewFilter");
Session session = openSession(sessionFactory);
SessionHolder sessionHolder = new SessionHolder(session);
TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder);
AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder);
asyncManager.registerCallableInterceptor(key, interceptor);
asyncManager.registerDeferredResultInterceptor(key, interceptor);
}
}
try {
filterChain.doFilter(request, response);
}
finally {
if (!participate) {
SessionHolder sessionHolder =
(SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
if (!isAsyncStarted(request)) {
logger.debug("Closing Hibernate Session in OpenSessionInViewFilter");
SessionFactoryUtils.closeSession(sessionHolder.getSession());
}
}
}
spring是如何管理hibernate的事務(wù)的?HibernateTransactionManager
本項(xiàng)目配置的hibernate事務(wù)管理器為
HibernateTransactionManager
HibernateTransactionManager extends AbstractPlatformTransactionManager......
其中獲取事務(wù)的源碼參見:AbstractPlatformTransactionManager#getTransaction
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
Object transaction = doGetTransaction();
// Cache debug flag to avoid repeated checks.
boolean debugEnabled = logger.isDebugEnabled();
if (definition == null) {
// Use defaults if no transaction definition given.
definition = new DefaultTransactionDefinition();
}
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// Check definition settings for new transaction.
if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout());
}
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition);
}
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
// Create "empty" transaction: no actual transaction, but potentially synchronization.
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
logger.warn("Custom isolation level specified but no actual transaction initiated; " +
"isolation level will effectively be ignored: " + definition);
}
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
其中dobegin進(jìn)入HibernateTransactionManager#doBegin
protected void doBegin(Object transaction, TransactionDefinition definition) {
HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;
if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
throw new IllegalTransactionStateException(
"Pre-bound JDBC Connection found! HibernateTransactionManager does not support " +
"running within DataSourceTransactionManager if told to manage the DataSource itself. " +
"It is recommended to use a single HibernateTransactionManager for all transactions " +
"on a single DataSource, no matter whether Hibernate or JDBC access.");
}
Session session = null;
try {
/*** 判斷是否 open一個(gè)newSession 見下文說明
txObject.hasSessionHolder() 一般應(yīng)返回true,因?yàn)樵趂ilter中新建了
txObject.getSessionHolder().isSynchronizedWithTransaction() 何時(shí)為true呢?
在try代碼塊的末尾會設(shè)置其為true: txObject.getSessionHolder().setSynchronizedWithTransaction(true);
有理由相信 可能是第1+n次進(jìn)入此dobegin方法時(shí)候,如果是txObject中的SessionHolder屬性為同一個(gè),則這個(gè)屬性為true.
那么什么時(shí)候第1+n次進(jìn)入dobegin,卻攜帶了相同的txObject呢?
參見:AbstractPlatformTransactionManager#
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
*************************************/
if (!txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction()) {
Interceptor entityInterceptor = getEntityInterceptor();
Session newSession = (entityInterceptor != null ?
obtainSessionFactory().withOptions().interceptor(entityInterceptor).openSession() :
obtainSessionFactory().openSession());
if (logger.isDebugEnabled()) {
logger.debug("Opened new Session [" + newSession + "] for Hibernate transaction");
}
txObject.setSession(newSession);
}
session = txObject.getSessionHolder().getSession();
boolean holdabilityNeeded = this.allowResultAccessAfterCompletion && !txObject.isNewSession();
boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT);
if (holdabilityNeeded || isolationLevelNeeded || definition.isReadOnly()) {
if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
// We're allowed to change the transaction settings of the JDBC Connection.
if (logger.isDebugEnabled()) {
logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
}
Connection con = ((SessionImplementor) session).connection();
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) {
int currentHoldability = con.getHoldability();
if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
txObject.setPreviousHoldability(currentHoldability);
con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
}
}
}
else {
// Not allowed to change the transaction settings of the JDBC Connection.
if (isolationLevelNeeded) {
// We should set a specific isolation level but are not allowed to...
throw new InvalidIsolationLevelException(
"HibernateTransactionManager is not allowed to support custom isolation levels: " +
"make sure that its 'prepareConnection' flag is on (the default) and that the " +
"Hibernate connection release mode is set to 'on_close' (the default for JDBC).");
}
if (logger.isDebugEnabled()) {
logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");
}
}
}
if (definition.isReadOnly() && txObject.isNewSession()) {
// Just set to MANUAL in case of a new Session for this transaction.
session.setFlushMode(FlushMode.MANUAL);
// As of 5.1, we're also setting Hibernate's read-only entity mode by default.
session.setDefaultReadOnly(true);
}
if (!definition.isReadOnly() && !txObject.isNewSession()) {
// We need AUTO or COMMIT for a non-read-only transaction.
FlushMode flushMode = SessionFactoryUtils.getFlushMode(session);
if (FlushMode.MANUAL.equals(flushMode)) {
session.setFlushMode(FlushMode.AUTO);
txObject.getSessionHolder().setPreviousFlushMode(flushMode);
}
}
Transaction hibTx;
// Register transaction timeout.
int timeout = determineTimeout(definition);
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
// Use Hibernate's own transaction timeout mechanism on Hibernate 3.1+
// Applies to all statements, also to inserts, updates and deletes!
hibTx = session.getTransaction();
hibTx.setTimeout(timeout);
hibTx.begin();
}
else {
// Open a plain Hibernate transaction without specified timeout.
hibTx = session.beginTransaction();
}
// Add the Hibernate transaction to the session holder.
txObject.getSessionHolder().setTransaction(hibTx);
// Register the Hibernate Session's JDBC Connection for the DataSource, if set.
if (getDataSource() != null) {
SessionImplementor sessionImpl = (SessionImplementor) session;
// The following needs to use a lambda expression instead of a method reference
// for compatibility with Hibernate ORM <5.2 where connection() is defined on
// SessionImplementor itself instead of on SharedSessionContractImplementor...
ConnectionHolder conHolder = new ConnectionHolder(() -> sessionImpl.connection());
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
conHolder.setTimeoutInSeconds(timeout);
}
if (logger.isDebugEnabled()) {
logger.debug("Exposing Hibernate transaction as JDBC [" + conHolder.getConnectionHandle() + "]");
}
TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
txObject.setConnectionHolder(conHolder);
}
// Bind the session holder to the thread.
if (txObject.isNewSessionHolder()) {
TransactionSynchronizationManager.bindResource(obtainSessionFactory(), txObject.getSessionHolder());
}
txObject.getSessionHolder().setSynchronizedWithTransaction(true);
}
catch (Throwable ex) {
if (txObject.isNewSession()) {
try {
if (session != null && session.getTransaction().getStatus() == TransactionStatus.ACTIVE) {
session.getTransaction().rollback();
}
}
catch (Throwable ex2) {
logger.debug("Could not rollback Session after failed transaction begin", ex);
}
finally {
SessionFactoryUtils.closeSession(session);
txObject.setSessionHolder(null);
}
}
throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex);
}
}
如上 :若 txObject.hasSessionHolder() || txObject.getSessionHolder().isSynchronizedWithTransaction() 返回ture則會新建session
其中txObject.hasSessionHolder() 一般應(yīng)返回true,因?yàn)槎嘣贠penSessionInViewFilter中新建了
那txObject.getSessionHolder().isSynchronizedWithTransaction() 何時(shí)為true呢?
在try代碼塊的末尾會設(shè)置其為true: txObject.getSessionHolder().setSynchronizedWithTransaction(true);
-
txObject(HibernateTransactionObject)對象來自doGetTransaction()方法
txObject的sessionFactory屬性來自:(SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
可知在一個(gè)線程中確實(shí)是一個(gè)對象
HibernateTransactionManager#doGetTransaction
protected Object doGetTransaction() {
HibernateTransactionObject txObject = new HibernateTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
//獲取 sessionFactory, 來自當(dāng)前HibernateTransactionManager實(shí)例的sessionFactory屬性
SessionFactory sessionFactory = obtainSessionFactory();
//獲取當(dāng)前線程綁定的sessionHolder,來自TransactionSynchronizationManager 的ThreadLocal<Map<Object, Object>> resources
SessionHolder sessionHolder, =
(SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if (sessionHolder != null) {
if (logger.isDebugEnabled()) {
logger.debug("Found thread-bound Session [" + sessionHolder.getSession() + "] for Hibernate transaction");
}
txObject.setSessionHolder(sessionHolder);
}
else if (this.hibernateManagedSession) {
try {
Session session = sessionFactory.getCurrentSession();
if (logger.isDebugEnabled()) {
logger.debug("Found Hibernate-managed Session [" + session + "] for Spring-managed transaction");
}
txObject.setExistingSession(session);
}
catch (HibernateException ex) {
throw new DataAccessResourceFailureException(
"Could not obtain Hibernate-managed Session for Spring-managed transaction", ex);
}
}
if (getDataSource() != null) {
ConnectionHolder conHolder = (ConnectionHolder)
TransactionSynchronizationManager.getResource(getDataSource());
txObject.setConnectionHolder(conHolder);
}
return txObject;
}
追蹤代碼而知, 在新建事務(wù)的時(shí)候,若當(dāng)前線程之前已經(jīng)新建了事務(wù),且進(jìn)入了doBegin方法,則hibernate的事務(wù)對象HibernateTransactionObject中持有的SessionHolder對象的synchronizedWithTransaction屬性會被設(shè)置為true;在這種情況下,新事務(wù)下的session為新打開的session,造成和先前的session不一致的情況.
6 留下的疑問:
項(xiàng)目中的serviceA的A方法并沒有開啟事務(wù)(雖然進(jìn)入事務(wù)切面, 但是傳播級別是SUPPORTS)
根據(jù)源碼只會新建一個(gè)空的事務(wù)(Create "empty" transaction: no actual transaction, but potentially synchronization.),
而不會進(jìn)入doBegin,也就不會設(shè)置SessionHolder對象的synchronizedWithTransaction屬性為true,
那么serviceB的B方法開啟新事務(wù)的時(shí)候, 理應(yīng)不會打開新的session才對......................
由于項(xiàng)目在內(nèi)網(wǎng)環(huán)境,無法debug進(jìn)入源碼, 暫時(shí)未追蹤運(yùn)行時(shí)進(jìn)入事務(wù)源碼的代碼,且留個(gè)疑問, 有暇在外網(wǎng)搭建個(gè)環(huán)境的時(shí)候,再做補(bǔ)充吧.
或者有哪些大俠直接告訴我答案也是好的.
文章來源: 臨窗旋墨的博客