diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHandle.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHandle.java index 714adb8573..8a0bec6d30 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHandle.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/ConnectionHandle.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ import java.sql.Connection; * @see SimpleConnectionHandle * @see ConnectionHolder */ +@FunctionalInterface public interface ConnectionHandle { /** @@ -36,8 +37,11 @@ public interface ConnectionHandle { /** * Release the JDBC Connection that this handle refers to. + *

The default implementation is empty, assuming that the lifecycle + * of the connection is managed externally. * @param con the JDBC Connection to release */ - void releaseConnection(Connection con); + default void releaseConnection(Connection con) { + } } diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java index cfa2cd447c..3b7bc688a5 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java @@ -21,6 +21,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; @@ -256,14 +257,16 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource { @Nullable private String password; - private Boolean readOnly = Boolean.FALSE; - @Nullable private Boolean autoCommit; @Nullable private Integer transactionIsolation; + private boolean readOnly = false; + + private int holdability = ResultSet.CLOSE_CURSORS_AT_COMMIT; + private boolean closed = false; @Nullable @@ -319,11 +322,15 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource { if (method.getName().equals("toString")) { return "Lazy Connection proxy for target DataSource [" + getTargetDataSource() + "]"; } - else if (method.getName().equals("isReadOnly")) { - return this.readOnly; + else if (method.getName().equals("getAutoCommit")) { + if (this.autoCommit != null) { + return this.autoCommit; + } + // Else fetch actual Connection and check there, + // because we didn't have a default specified. } - else if (method.getName().equals("setReadOnly")) { - this.readOnly = (Boolean) args[0]; + else if (method.getName().equals("setAutoCommit")) { + this.autoCommit = (Boolean) args[0]; return null; } else if (method.getName().equals("getTransactionIsolation")) { @@ -337,15 +344,18 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource { this.transactionIsolation = (Integer) args[0]; return null; } - else if (method.getName().equals("getAutoCommit")) { - if (this.autoCommit != null) { - return this.autoCommit; - } - // Else fetch actual Connection and check there, - // because we didn't have a default specified. + else if (method.getName().equals("isReadOnly")) { + return this.readOnly; } - else if (method.getName().equals("setAutoCommit")) { - this.autoCommit = (Boolean) args[0]; + else if (method.getName().equals("setReadOnly")) { + this.readOnly = (Boolean) args[0]; + return null; + } + else if (method.getName().equals("getHoldability")) { + return this.holdability; + } + else if (method.getName().equals("setHoldability")) { + this.holdability = (Integer) args[0]; return null; } else if (method.getName().equals("commit")) { diff --git a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/SimpleConnectionHandle.java b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/SimpleConnectionHandle.java index 6140a51f1a..15f6bdd87a 100644 --- a/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/SimpleConnectionHandle.java +++ b/spring-jdbc/src/main/java/org/springframework/jdbc/datasource/SimpleConnectionHandle.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2012 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,14 +49,6 @@ public class SimpleConnectionHandle implements ConnectionHandle { return this.connection; } - /** - * This implementation is empty, as we're using a standard - * Connection handle that does not have to be released. - */ - @Override - public void releaseConnection(Connection con) { - } - @Override public String toString() { diff --git a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java index 238ca0a910..50f4ad47a6 100644 --- a/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java @@ -197,9 +197,16 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana * manager needs to work on the underlying target DataSource. If there's * nevertheless a TransactionAwareDataSourceProxy passed in, it will be * unwrapped to extract its target DataSource. + *

NOTE: For scenarios with many transactions that just read data from + * Hibernate's cache (and do not actually access the database), consider using + * a {@link org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy} + * for the actual target DataSource. Alternatively, consider switching + * {@link #setPrepareConnection "prepareConnection"} to {@code false}. + * In both cases, this transaction manager will not eagerly acquire a + * JDBC Connection for each Hibernate Session anymore (as of Spring 5.1). * @see #setAutodetectDataSource * @see TransactionAwareDataSourceProxy - * @see DataSourceUtils + * @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy * @see org.springframework.jdbc.core.JdbcTemplate */ public void setDataSource(@Nullable DataSource dataSource) { @@ -462,33 +469,37 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana session = txObject.getSessionHolder().getSession(); - 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); + 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 (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { - // 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 + "]"); + 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 + "]"); + } } } @@ -529,13 +540,13 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana // Register the Hibernate Session's JDBC Connection for the DataSource, if set. if (getDataSource() != null) { - Connection con = ((SessionImplementor) session).connection(); - ConnectionHolder conHolder = new ConnectionHolder(con); + SessionImplementor sessionImpl = (SessionImplementor) session; + ConnectionHolder conHolder = new ConnectionHolder(sessionImpl::connection); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { conHolder.setTimeoutInSeconds(timeout); } if (logger.isDebugEnabled()) { - logger.debug("Exposing Hibernate transaction as JDBC transaction [" + con + "]"); + logger.debug("Exposing Hibernate transaction as JDBC [" + conHolder.getConnectionHandle() + "]"); } TransactionSynchronizationManager.bindResource(getDataSource(), conHolder); txObject.setConnectionHolder(conHolder); diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java index 99c6726d04..d3d4a1d700 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java @@ -416,7 +416,7 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager conHolder.setTimeoutInSeconds(timeoutToUse); } if (logger.isDebugEnabled()) { - logger.debug("Exposing JPA transaction as JDBC transaction [" + conHandle + "]"); + logger.debug("Exposing JPA transaction as JDBC [" + conHandle + "]"); } TransactionSynchronizationManager.bindResource(getDataSource(), conHolder); txObject.setConnectionHolder(conHolder); diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java index d6c52a02e5..0cfcafbbbe 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/EclipseLinkJpaDialect.java @@ -131,10 +131,6 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect { } return this.connection; } - - @Override - public void releaseConnection(Connection con) { - } } } diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java index 3e6e834831..09c26c73bd 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java @@ -75,7 +75,7 @@ import org.springframework.util.ReflectionUtils; /** * {@link org.springframework.orm.jpa.JpaDialect} implementation for - * Hibernate EntityManager. Developed against Hibernate 5.0/5.1/5.2. + * Hibernate EntityManager. Developed against Hibernate 5.1/5.2/5.3. * * @author Juergen Hoeller * @author Costin Leau @@ -119,10 +119,8 @@ public class HibernateJpaDialect extends DefaultJpaDialect { * Hibernate Session, that is, whether to apply a transaction-specific * isolation level and/or the transaction's read-only flag to the underlying * JDBC Connection. - *

Default is "true" on Hibernate EntityManager 4.x (with its 'on-close' - * connection release mode. - *

If you turn this flag off, JPA transaction management will not support - * per-transaction isolation levels anymore. It will not call + *

Default is "true". If you turn this flag off, JPA transaction management + * will not support per-transaction isolation levels anymore. It will not call * {@code Connection.setReadOnly(true)} for read-only transactions anymore either. * If this flag is turned off, no cleanup of a JDBC Connection is required after * a transaction, since no Connection settings will get modified. @@ -415,10 +413,6 @@ public class HibernateJpaDialect extends DefaultJpaDialect { return doGetConnection(this.session); } - @Override - public void releaseConnection(Connection con) { - } - public static Connection doGetConnection(Session session) { try { Method methodToUse = connectionMethodToUse; diff --git a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java index 8533fdc5ec..692955f759 100644 --- a/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java +++ b/spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaVendorAdapter.java @@ -41,7 +41,7 @@ import org.springframework.lang.Nullable; /** * {@link org.springframework.orm.jpa.JpaVendorAdapter} implementation for Hibernate - * EntityManager. Developed and tested against Hibernate 5.0, 5.1 and 5.2; + * EntityManager. Developed and tested against Hibernate 5.0, 5.1, 5.2 and 5.3; * backwards-compatible with Hibernate 4.3 at runtime on a best-effort basis. * *

Exposes Hibernate's persistence provider and EntityManager extension interface,