[TOC]
No session exception occurred in Hibernate lazy loading due to transaction
Article source: Linwindow Xuanmo blog
I am not particularly familiar with the use of Hibernate, here is just a record to help a colleague troubleshooting the process.
The Spring version in the project is 4.1.6
The spring version of the posted source code is 5.1.9
1. Project technical framework
spring + springmvc + hibernate + freemarker
2 The occurrence of exceptions
The controller directly calls the serviceA method A page can render normally;
The controller invokes method B in serviceB, and method B invokes method A in serviceA
3 Problem Tracking
- According to the phenomenon, the most intuitive reason is that the session is closed, and then the session is not obtained when the lazy object attributes are obtained.
- There is no error in the direct call, but after another service call, there is no session
3.1 Check whether it is a session debugging: the result is not a session;
This project turned on the OpenSessionInViewFilter,
- The breakpoint tracks the open Session in OpenSessionInViewFilter and records sesson hashCode –>code1;
- Access serviceB by obtaining
sessionFactory.getCurrentSession()
, record session hashcode, equal to code1, indicating that the session used here is the session opened in filter; - Enter serviceA from serviceB, get the current session, get the hashCode value and
code1
Don’t agree;
3.2 Why not a session: Because not in one thing
- Transactions for this project are configured by aspects in the configuration file;
- Because of the naming problem,
- Propagation =”SUPPORTS”
- Method A needs to run in A thing ((Propagation =”REQUIRED”)
- The result is that no transaction is required after entering method B, but method A called by B runs in A transaction
About the propagation of affairs, partners can review by themselves;
3.3 Troubleshooting: Change the method name to ensure that it is in a transaction
After modifying the method name, ensure that the two methods are in one transaction, verify again, the session is consistent, the lazy loaded attribute can be loaded normally, and the finally code block of OpenSessionInViewFilter can also be closed normally.
4 q: How does openSession differ from getCurrentSession
- Sessions created with getCurrentSession() are automatically closed at commit or ROLLBACK,
- Sessions created using openSession() must be closed manually
- The session created with getCurrentSession() is bound to the current thread,
- Sessions created using openSession() do not. So the session above is always going to be one session.
The session (getCurrentSession()) in method A is running in A transaction, so it is closed after the commit.
Q: Why is the session in starting a new thing inconsistent with the session in the current thread
5.1 How does Spring manage Hibernate Sessions
Check the configuration file for the sessionFactory configuration as LocalSessionFactoryBean
LocalSessionFactoryBean` implements FactoryBean…….
SessionFactory. GetCurrentSession source tracking
SessionFactoryImpl#getCurrentSession
public Session getCurrentSession(a) throws HibernateException {
if ( currentSessionContext == null ) {
throw new HibernateException( "No CurrentSessionContext configured!" );
}
return currentSessionContext.currentSession();
}
Copy the code
SpringSessionContext#currentSession
public Session currentSession(a) throws HibernateException {
OpenSessionInViewFilter (sessionFactory); /* * OpenSessionInViewFilter (sessionFactory); See openSessIon code */ for OpenSessionInViewFilter
Object value = TransactionSynchronizationManager.getResource(this.sessionFactory);
if (value instanceof Session) {
return (Session) value;
}
else if (value instanceof SessionHolder) {
Hibernate transaction manager
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));
}
returnsession; }}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"); }}Copy the code
Compare OpenSessionInViewFilter#doFilterInternal with openSessionInViewFilterInternal.
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 {
booleanisFirstRequest = ! 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 = newAsyncRequestInterceptor(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()); }}}Copy the code
How does Spring manage Hibernate transactions?HibernateTransactionManager
The project configuration of hibernate HibernateTransactionManager transaction manager
HibernateTransactionManager extends AbstractPlatformTransactionManager……
One for transaction source see: 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 {
booleannewSynchronization = (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);
throwex; }}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); }}Copy the code
The dobegin into 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 {
/ * * * determine whether open a newSession. See below instructions txObject hasSessionHolder () should return true, because the built in the filter TxObject. GetSessionHolder (.) isSynchronizedWithTransaction when () is true? At the end of the try block will set it to true: txObject. GetSessionHolder () setSynchronizedWithTransaction (true); If the SessionHolder attribute in txObject is the same, then this attribute will be true. When do you enter DoBEGIN for the first +n time with the same txObject? See also: 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();booleanisolationLevelNeeded = (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); }}Copy the code
Above: if the txObject. HasSessionHolder () | | txObject. GetSessionHolder () isSynchronizedWithTransaction () returns true will be a new session
-
The txObject. HasSessionHolder () should return true commonly, because many built in OpenSessionInViewFilter
-
The txObject. GetSessionHolder (.) isSynchronizedWithTransaction when () is true?
-
At the end of the try block will set it to true: txObject. GetSessionHolder () setSynchronizedWithTransaction (true);
-
TxObject (HibernateTransactionObject) objects from doGetTransaction () method
-
TxObject sessionFactory attributes from: (SessionHolder) TransactionSynchronizationManager. GetResource (sessionFactory);
-
Is an object in a thread
-
HibernateTransactionManager#doGetTransaction
protected Object doGetTransaction(a) {
HibernateTransactionObject txObject = new HibernateTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
/ / get sessionFactory, from the current HibernateTransactionManager instance sessionFactory attributes
SessionFactory sessionFactory = obtainSessionFactory();
/ / get the current thread binding sessionHolder, from TransactionSynchronizationManager ThreadLocal < Map < Object, the 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;
}
Copy the code
By tracking the code, When a new transaction, if the current thread has a new transaction before, and entered the doBegin method, is held in the affairs of the hibernate object HibernateTransactionObject synchronizedWithTrans SessionHolder objects The action property is set to true; In this case, the session under the new transaction is the newly opened session, causing an inconsistency with the previous session.
6 Questions left behind:
The A method of the serviceA in the project does not start A transaction (although it enters the transaction aspect, the propagation level is SUPPORTS)
Create “empty” Transaction: No actual transaction, but potentially synchronization.
Without entering doBegin, there would be no set SessionHolder synchronizedWithTransaction attribute of an object to true,
So serviceB B method to open a new transaction, should not open a new session to…
Because the project is in the internal network environment, it cannot debug into the source code, so we have not traced the code that entered the transactional source code at runtime for the time being, and I leave a question. When we have free time to build an environment on the extranet, we can make a supplement.
Or someone who can just tell me the answer.
Article source: Linwindow Xuanmo blog