Skip to content

Commit cbda722

Browse files
committed
HibernateJpaDialect prepares JDBC Connection by default if on Hibernate EntityManager 4 (with its connection release mode ON_CLOSE)
Analogous to HibernateTransactionManager, there is a "prepareConnection" flag on HibernateJpaDialect which allows for overriding the actual mode of operation. This is easily accessible from HibernateJpaVendorAdapter now which declares HibernateJpaDialect from its getJpaDialect() method. Issue: SPR-8959 Issue: SPR-11942
1 parent e08c56f commit cbda722

File tree

3 files changed

+97
-25
lines changed

3 files changed

+97
-25
lines changed

spring-orm-hibernate4/src/main/java/org/springframework/orm/hibernate4/HibernateTransactionManager.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@
9191
* support nested transactions! Hence, do not expect Hibernate access code to
9292
* semantically participate in a nested transaction.</i>
9393
*
94+
* <p><b>NOTE: Hibernate 4.2+ is strongly recommended for efficient transaction
95+
* management with Spring, in particular for transactional Spring JDBC access.</b>
96+
*
9497
* @author Juergen Hoeller
9598
* @since 3.1
9699
* @see #setSessionFactory
@@ -437,7 +440,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) {
437440
throw new InvalidIsolationLevelException(
438441
"HibernateTransactionManager is not allowed to support custom isolation levels: " +
439442
"make sure that its 'prepareConnection' flag is on (the default) and that the " +
440-
"Hibernate connection release mode is set to 'on_close' (SpringTransactionFactory's default).");
443+
"Hibernate connection release mode is set to 'on_close' (the default for JDBC).");
441444
}
442445
if (logger.isDebugEnabled()) {
443446
logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");

spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java

Lines changed: 90 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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.
@@ -55,12 +55,14 @@
5555
import org.springframework.dao.InvalidDataAccessResourceUsageException;
5656
import org.springframework.dao.PessimisticLockingFailureException;
5757
import org.springframework.jdbc.datasource.ConnectionHandle;
58+
import org.springframework.jdbc.datasource.DataSourceUtils;
5859
import org.springframework.jdbc.support.JdbcUtils;
5960
import org.springframework.orm.ObjectOptimisticLockingFailureException;
6061
import org.springframework.orm.ObjectRetrievalFailureException;
6162
import org.springframework.orm.jpa.DefaultJpaDialect;
6263
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
6364
import org.springframework.orm.jpa.JpaSystemException;
65+
import org.springframework.transaction.InvalidIsolationLevelException;
6466
import org.springframework.transaction.TransactionDefinition;
6567
import org.springframework.transaction.TransactionException;
6668
import org.springframework.util.ClassUtils;
@@ -70,8 +72,8 @@
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
}

spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,10 @@
3333
import org.hibernate.dialect.PostgreSQLDialect;
3434
import org.hibernate.dialect.SQLServerDialect;
3535

36-
import org.springframework.orm.jpa.JpaDialect;
37-
3836
/**
3937
* {@link org.springframework.orm.jpa.JpaVendorAdapter} implementation for
4038
* Hibernate EntityManager. Developed and tested against Hibernate 3.6 and 4.2/4.3.
39+
* <b>Hibernate 4.2+ is strongly recommended for use with Spring 4.0+.</b>
4140
*
4241
* <p>Exposes Hibernate's persistence provider and EntityManager extension interface,
4342
* and adapts {@link AbstractJpaVendorAdapter}'s common configuration settings.
@@ -62,7 +61,7 @@
6261
*/
6362
public class HibernateJpaVendorAdapter extends AbstractJpaVendorAdapter {
6463

65-
private final JpaDialect jpaDialect = new HibernateJpaDialect();
64+
private final HibernateJpaDialect jpaDialect = new HibernateJpaDialect();
6665

6766
private final PersistenceProvider persistenceProvider;
6867

@@ -159,7 +158,7 @@ protected Class<?> determineDatabaseDialectClass(Database database) {
159158
}
160159

161160
@Override
162-
public JpaDialect getJpaDialect() {
161+
public HibernateJpaDialect getJpaDialect() {
163162
return this.jpaDialect;
164163
}
165164

0 commit comments

Comments
 (0)