Skip to content

Commit

Permalink
HibernateTransactionManager lazily acquires JDBC Connection
Browse files Browse the repository at this point in the history
Aligned with HibernateJpaDialect, using ConnectionHandle as functional interface now. Also, LazyConnectionDataSourceProxy supports Connection holdability as applied by HibernateTransactionManager, for use with prepareConnection=true.

Issue: SPR-17216
  • Loading branch information
jhoeller committed Aug 31, 2018
1 parent a689daa commit 78cad0f
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -27,6 +27,7 @@
* @see SimpleConnectionHandle
* @see ConnectionHolder
*/
@FunctionalInterface
public interface ConnectionHandle {

/**
Expand All @@ -36,8 +37,11 @@ public interface ConnectionHandle {

/**
* Release the JDBC Connection that this handle refers to.
* <p>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) {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
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;

Expand Down Expand Up @@ -256,14 +257,16 @@ private class LazyConnectionInvocationHandler implements InvocationHandler {
@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
Expand Down Expand Up @@ -319,11 +322,15 @@ else if (method.getName().equals("getTargetConnection")) {
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")) {
Expand All @@ -337,15 +344,18 @@ else if (method.getName().equals("setTransactionIsolation")) {
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")) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -49,14 +49,6 @@ public Connection getConnection() {
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,16 @@ protected final SessionFactory obtainSessionFactory() {
* 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.
* <p><b>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}.</b>
* 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) {
Expand Down Expand Up @@ -462,33 +469,37 @@ protected void doBegin(Object transaction, TransactionDefinition definition) {

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 + "]");
}
}
}

Expand Down Expand Up @@ -529,13 +540,13 @@ protected void doBegin(Object transaction, TransactionDefinition definition) {

// 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,6 @@ public Connection getConnection() {
}
return this.connection;
}

@Override
public void releaseConnection(Connection con) {
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@

/**
* {@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
Expand Down Expand Up @@ -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.
* <p>Default is "true" on Hibernate EntityManager 4.x (with its 'on-close'
* connection release mode.
* <p>If you turn this flag off, JPA transaction management will not support
* per-transaction isolation levels anymore. It will not call
* <p>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.
Expand Down Expand Up @@ -415,10 +413,6 @@ public Connection getConnection() {
return doGetConnection(this.session);
}

@Override
public void releaseConnection(Connection con) {
}

public static Connection doGetConnection(Session session) {
try {
Method methodToUse = connectionMethodToUse;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

/**
* {@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.
*
* <p>Exposes Hibernate's persistence provider and EntityManager extension interface,
Expand Down

0 comments on commit 78cad0f

Please sign in to comment.