11/*
2- * Copyright 2002-2013 the original author or authors.
2+ * Copyright 2002-2014 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
5555import org .springframework .dao .InvalidDataAccessResourceUsageException ;
5656import org .springframework .dao .PessimisticLockingFailureException ;
5757import org .springframework .jdbc .datasource .ConnectionHandle ;
58+ import org .springframework .jdbc .datasource .DataSourceUtils ;
5859import org .springframework .jdbc .support .JdbcUtils ;
5960import org .springframework .orm .ObjectOptimisticLockingFailureException ;
6061import org .springframework .orm .ObjectRetrievalFailureException ;
6162import org .springframework .orm .jpa .DefaultJpaDialect ;
6263import org .springframework .orm .jpa .EntityManagerFactoryUtils ;
6364import org .springframework .orm .jpa .JpaSystemException ;
65+ import org .springframework .transaction .InvalidIsolationLevelException ;
6466import org .springframework .transaction .TransactionDefinition ;
6567import org .springframework .transaction .TransactionException ;
6668import org .springframework .util .ClassUtils ;
7072 * {@link org.springframework.orm.jpa.JpaDialect} implementation for
7173 * Hibernate EntityManager. Developed against Hibernate 3.6 and 4.2/4.3.
7274 *
73- * @author Costin Leau
7475 * @author Juergen Hoeller
76+ * @author Costin Leau
7577 * @since 2.0
7678 */
7779@ SuppressWarnings ({"serial" , "deprecation" })
@@ -100,22 +102,73 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
100102 }
101103
102104
105+ private boolean prepareConnection = (HibernateConnectionHandle .sessionConnectionMethod == null );
106+
107+
108+ /**
109+ * Set whether to prepare the underlying JDBC Connection of a transactional
110+ * Hibernate Session, that is, whether to apply a transaction-specific
111+ * isolation level and/or the transaction's read-only flag to the underlying
112+ * JDBC Connection.
113+ * <p>Default is "true" on Hibernate EntityManager 4.x (with its 'on-close'
114+ * connection release mode, and "false" on Hibernate EntityManager 3.6 (due to
115+ * the 'after-transaction' release mode there). <b>Note that Hibernate 4.2+ is
116+ * strongly recommended in order to make isolation levels work efficiently.</b>
117+ * <p>If you turn this flag off, JPA transaction management will not support
118+ * per-transaction isolation levels anymore. It will not call
119+ * {@code Connection.setReadOnly(true)} for read-only transactions anymore either.
120+ * If this flag is turned off, no cleanup of a JDBC Connection is required after
121+ * a transaction, since no Connection settings will get modified.
122+ * @see java.sql.Connection#setTransactionIsolation
123+ * @see java.sql.Connection#setReadOnly
124+ */
125+ public void setPrepareConnection (boolean prepareConnection ) {
126+ this .prepareConnection = prepareConnection ;
127+ }
128+
129+
103130 @ Override
104131 public Object beginTransaction (EntityManager entityManager , TransactionDefinition definition )
105132 throws PersistenceException , SQLException , TransactionException {
106133
134+ Session session = getSession (entityManager );
135+
107136 if (definition .getTimeout () != TransactionDefinition .TIMEOUT_DEFAULT ) {
108- getSession ( entityManager ) .getTransaction ().setTimeout (definition .getTimeout ());
137+ session .getTransaction ().setTimeout (definition .getTimeout ());
109138 }
110- super .beginTransaction (entityManager , definition );
111- return prepareTransaction (entityManager , definition .isReadOnly (), definition .getName ());
139+
140+ Integer previousIsolationLevel = null ;
141+ boolean isolationLevelNeeded = (definition .getIsolationLevel () != TransactionDefinition .ISOLATION_DEFAULT );
142+ if (isolationLevelNeeded || definition .isReadOnly ()) {
143+ if (this .prepareConnection ) {
144+ Connection con = HibernateConnectionHandle .doGetConnection (session );
145+ previousIsolationLevel = DataSourceUtils .prepareConnectionForTransaction (con , definition );
146+ }
147+ else if (isolationLevelNeeded ) {
148+ throw new InvalidIsolationLevelException (getClass ().getSimpleName () +
149+ " does not support custom isolation levels since the 'prepareConnection' flag is off. " +
150+ "This is the case on Hibernate 3.6 by default; either switch that flag at your own risk " +
151+ "or upgrade to Hibernate 4.x, with 4.2+ recommended." );
152+ }
153+ }
154+
155+ // Standard JPA transaction begin call for full JPA context setup...
156+ entityManager .getTransaction ().begin ();
157+
158+ // Adapt flush mode and store previous isolation level, if any.
159+ return doPrepareTransaction (session , definition .isReadOnly (), previousIsolationLevel );
112160 }
113161
114162 @ Override
115163 public Object prepareTransaction (EntityManager entityManager , boolean readOnly , String name )
116164 throws PersistenceException {
117165
118- Session session = getSession (entityManager );
166+ return doPrepareTransaction (getSession (entityManager ), readOnly , null );
167+ }
168+
169+ protected Object doPrepareTransaction (Session session , boolean readOnly , Integer previousIsolationLevel )
170+ throws PersistenceException {
171+
119172 FlushMode flushMode = session .getFlushMode ();
120173 FlushMode previousFlushMode = null ;
121174 if (readOnly ) {
@@ -130,12 +183,14 @@ public Object prepareTransaction(EntityManager entityManager, boolean readOnly,
130183 previousFlushMode = flushMode ;
131184 }
132185 }
133- return new SessionTransactionData (session , previousFlushMode );
186+
187+ boolean resetConnection = (previousIsolationLevel != null || readOnly );
188+ return new SessionTransactionData (session , previousFlushMode , resetConnection , previousIsolationLevel );
134189 }
135190
136191 @ Override
137192 public void cleanupTransaction (Object transactionData ) {
138- ((SessionTransactionData ) transactionData ).resetFlushMode ();
193+ ((SessionTransactionData ) transactionData ).resetSessionState ();
139194 }
140195
141196 @ Override
@@ -255,15 +310,26 @@ private static class SessionTransactionData {
255310
256311 private final FlushMode previousFlushMode ;
257312
258- public SessionTransactionData (Session session , FlushMode previousFlushMode ) {
313+ private final boolean connectionReset ;
314+
315+ private final Integer previousIsolationLevel ;
316+
317+ public SessionTransactionData (
318+ Session session , FlushMode previousFlushMode , boolean resetConnection , Integer previousIsolationLevel ) {
259319 this .session = session ;
260320 this .previousFlushMode = previousFlushMode ;
321+ this .connectionReset = resetConnection ;
322+ this .previousIsolationLevel = previousIsolationLevel ;
261323 }
262324
263- public void resetFlushMode () {
325+ public void resetSessionState () {
264326 if (this .previousFlushMode != null ) {
265327 this .session .setFlushMode (this .previousFlushMode );
266328 }
329+ if (this .connectionReset && this .session .isConnected ()) {
330+ Connection con = HibernateConnectionHandle .doGetConnection (this .session );
331+ DataSourceUtils .resetConnectionAfterTransaction (con , this .previousIsolationLevel );
332+ }
267333 }
268334 }
269335
@@ -284,16 +350,7 @@ public HibernateConnectionHandle(Session session) {
284350
285351 @ Override
286352 public Connection getConnection () {
287- try {
288- if (connectionMethodToUse == null ) {
289- // Reflective lookup trying to find SessionImpl's connection() on Hibernate 4.x
290- connectionMethodToUse = this .session .getClass ().getMethod ("connection" );
291- }
292- return (Connection ) ReflectionUtils .invokeMethod (connectionMethodToUse , this .session );
293- }
294- catch (NoSuchMethodException ex ) {
295- throw new IllegalStateException ("Cannot find connection() method on Hibernate Session" , ex );
296- }
353+ return doGetConnection (this .session );
297354 }
298355
299356 @ Override
@@ -307,6 +364,19 @@ public void releaseConnection(Connection con) {
307364 JdbcUtils .closeConnection (con );
308365 }
309366 }
367+
368+ public static Connection doGetConnection (Session session ) {
369+ try {
370+ if (connectionMethodToUse == null ) {
371+ // Reflective lookup trying to find SessionImpl's connection() on Hibernate 4.x
372+ connectionMethodToUse = session .getClass ().getMethod ("connection" );
373+ }
374+ return (Connection ) ReflectionUtils .invokeMethod (connectionMethodToUse , session );
375+ }
376+ catch (NoSuchMethodException ex ) {
377+ throw new IllegalStateException ("Cannot find connection() method on Hibernate Session" , ex );
378+ }
379+ }
310380 }
311381
312382}
0 commit comments