HibernateTransactionManager lazily acquires JDBC Connection
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
This commit is contained in:
parent
a689daadf9
commit
78cad0fdd3
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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 SimpleConnectionHandle
|
||||||
* @see ConnectionHolder
|
* @see ConnectionHolder
|
||||||
*/
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
public interface ConnectionHandle {
|
public interface ConnectionHandle {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -36,8 +37,11 @@ public interface ConnectionHandle {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Release the JDBC Connection that this handle refers to.
|
* 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
|
* @param con the JDBC Connection to release
|
||||||
*/
|
*/
|
||||||
void releaseConnection(Connection con);
|
default void releaseConnection(Connection con) {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import javax.sql.DataSource;
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
|
@ -256,14 +257,16 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
|
||||||
@Nullable
|
@Nullable
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
private Boolean readOnly = Boolean.FALSE;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Boolean autoCommit;
|
private Boolean autoCommit;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Integer transactionIsolation;
|
private Integer transactionIsolation;
|
||||||
|
|
||||||
|
private boolean readOnly = false;
|
||||||
|
|
||||||
|
private int holdability = ResultSet.CLOSE_CURSORS_AT_COMMIT;
|
||||||
|
|
||||||
private boolean closed = false;
|
private boolean closed = false;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
@ -319,11 +322,15 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
|
||||||
if (method.getName().equals("toString")) {
|
if (method.getName().equals("toString")) {
|
||||||
return "Lazy Connection proxy for target DataSource [" + getTargetDataSource() + "]";
|
return "Lazy Connection proxy for target DataSource [" + getTargetDataSource() + "]";
|
||||||
}
|
}
|
||||||
else if (method.getName().equals("isReadOnly")) {
|
else if (method.getName().equals("getAutoCommit")) {
|
||||||
return this.readOnly;
|
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")) {
|
else if (method.getName().equals("setAutoCommit")) {
|
||||||
this.readOnly = (Boolean) args[0];
|
this.autoCommit = (Boolean) args[0];
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else if (method.getName().equals("getTransactionIsolation")) {
|
else if (method.getName().equals("getTransactionIsolation")) {
|
||||||
|
|
@ -337,15 +344,18 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
|
||||||
this.transactionIsolation = (Integer) args[0];
|
this.transactionIsolation = (Integer) args[0];
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
else if (method.getName().equals("getAutoCommit")) {
|
else if (method.getName().equals("isReadOnly")) {
|
||||||
if (this.autoCommit != null) {
|
return this.readOnly;
|
||||||
return this.autoCommit;
|
|
||||||
}
|
|
||||||
// Else fetch actual Connection and check there,
|
|
||||||
// because we didn't have a default specified.
|
|
||||||
}
|
}
|
||||||
else if (method.getName().equals("setAutoCommit")) {
|
else if (method.getName().equals("setReadOnly")) {
|
||||||
this.autoCommit = (Boolean) args[0];
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
else if (method.getName().equals("commit")) {
|
else if (method.getName().equals("commit")) {
|
||||||
|
|
|
||||||
|
|
@ -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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with 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;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
|
|
||||||
|
|
@ -197,9 +197,16 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
|
||||||
* manager needs to work on the underlying target DataSource. If there's
|
* manager needs to work on the underlying target DataSource. If there's
|
||||||
* nevertheless a TransactionAwareDataSourceProxy passed in, it will be
|
* nevertheless a TransactionAwareDataSourceProxy passed in, it will be
|
||||||
* unwrapped to extract its target DataSource.
|
* 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 #setAutodetectDataSource
|
||||||
* @see TransactionAwareDataSourceProxy
|
* @see TransactionAwareDataSourceProxy
|
||||||
* @see DataSourceUtils
|
* @see org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy
|
||||||
* @see org.springframework.jdbc.core.JdbcTemplate
|
* @see org.springframework.jdbc.core.JdbcTemplate
|
||||||
*/
|
*/
|
||||||
public void setDataSource(@Nullable DataSource dataSource) {
|
public void setDataSource(@Nullable DataSource dataSource) {
|
||||||
|
|
@ -462,33 +469,37 @@ public class HibernateTransactionManager extends AbstractPlatformTransactionMana
|
||||||
|
|
||||||
session = txObject.getSessionHolder().getSession();
|
session = txObject.getSessionHolder().getSession();
|
||||||
|
|
||||||
if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
|
boolean holdabilityNeeded = this.allowResultAccessAfterCompletion && !txObject.isNewSession();
|
||||||
// We're allowed to change the transaction settings of the JDBC Connection.
|
boolean isolationLevelNeeded = (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT);
|
||||||
if (logger.isDebugEnabled()) {
|
if (holdabilityNeeded || isolationLevelNeeded || definition.isReadOnly()) {
|
||||||
logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
|
if (this.prepareConnection && isSameConnectionForEntireSession(session)) {
|
||||||
}
|
// We're allowed to change the transaction settings of the JDBC Connection.
|
||||||
Connection con = ((SessionImplementor) session).connection();
|
if (logger.isDebugEnabled()) {
|
||||||
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
|
logger.debug("Preparing JDBC Connection of Hibernate Session [" + session + "]");
|
||||||
txObject.setPreviousIsolationLevel(previousIsolationLevel);
|
}
|
||||||
if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) {
|
Connection con = ((SessionImplementor) session).connection();
|
||||||
int currentHoldability = con.getHoldability();
|
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
|
||||||
if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
|
txObject.setPreviousIsolationLevel(previousIsolationLevel);
|
||||||
txObject.setPreviousHoldability(currentHoldability);
|
if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) {
|
||||||
con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
|
int currentHoldability = con.getHoldability();
|
||||||
|
if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
|
||||||
|
txObject.setPreviousHoldability(currentHoldability);
|
||||||
|
con.setHoldability(ResultSet.HOLD_CURSORS_OVER_COMMIT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else {
|
||||||
else {
|
// Not allowed to change the transaction settings of the JDBC Connection.
|
||||||
// Not allowed to change the transaction settings of the JDBC Connection.
|
if (isolationLevelNeeded) {
|
||||||
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
|
// We should set a specific isolation level but are not allowed to...
|
||||||
// We should set a specific isolation level but are not allowed to...
|
throw new InvalidIsolationLevelException(
|
||||||
throw new InvalidIsolationLevelException(
|
"HibernateTransactionManager is not allowed to support custom isolation levels: " +
|
||||||
"HibernateTransactionManager is not allowed to support custom isolation levels: " +
|
"make sure that its 'prepareConnection' flag is on (the default) and that the " +
|
||||||
"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).");
|
||||||
"Hibernate connection release mode is set to 'on_close' (the default for JDBC).");
|
}
|
||||||
}
|
if (logger.isDebugEnabled()) {
|
||||||
if (logger.isDebugEnabled()) {
|
logger.debug("Not preparing JDBC Connection of Hibernate Session [" + session + "]");
|
||||||
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.
|
// Register the Hibernate Session's JDBC Connection for the DataSource, if set.
|
||||||
if (getDataSource() != null) {
|
if (getDataSource() != null) {
|
||||||
Connection con = ((SessionImplementor) session).connection();
|
SessionImplementor sessionImpl = (SessionImplementor) session;
|
||||||
ConnectionHolder conHolder = new ConnectionHolder(con);
|
ConnectionHolder conHolder = new ConnectionHolder(sessionImpl::connection);
|
||||||
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
|
if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
|
||||||
conHolder.setTimeoutInSeconds(timeout);
|
conHolder.setTimeoutInSeconds(timeout);
|
||||||
}
|
}
|
||||||
if (logger.isDebugEnabled()) {
|
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);
|
TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
|
||||||
txObject.setConnectionHolder(conHolder);
|
txObject.setConnectionHolder(conHolder);
|
||||||
|
|
|
||||||
|
|
@ -416,7 +416,7 @@ public class JpaTransactionManager extends AbstractPlatformTransactionManager
|
||||||
conHolder.setTimeoutInSeconds(timeoutToUse);
|
conHolder.setTimeoutInSeconds(timeoutToUse);
|
||||||
}
|
}
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("Exposing JPA transaction as JDBC transaction [" + conHandle + "]");
|
logger.debug("Exposing JPA transaction as JDBC [" + conHandle + "]");
|
||||||
}
|
}
|
||||||
TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
|
TransactionSynchronizationManager.bindResource(getDataSource(), conHolder);
|
||||||
txObject.setConnectionHolder(conHolder);
|
txObject.setConnectionHolder(conHolder);
|
||||||
|
|
|
||||||
|
|
@ -131,10 +131,6 @@ public class EclipseLinkJpaDialect extends DefaultJpaDialect {
|
||||||
}
|
}
|
||||||
return this.connection;
|
return this.connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void releaseConnection(Connection con) {
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ import org.springframework.util.ReflectionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link org.springframework.orm.jpa.JpaDialect} implementation for
|
* {@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 Juergen Hoeller
|
||||||
* @author Costin Leau
|
* @author Costin Leau
|
||||||
|
|
@ -119,10 +119,8 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
|
||||||
* Hibernate Session, that is, whether to apply a transaction-specific
|
* Hibernate Session, that is, whether to apply a transaction-specific
|
||||||
* isolation level and/or the transaction's read-only flag to the underlying
|
* isolation level and/or the transaction's read-only flag to the underlying
|
||||||
* JDBC Connection.
|
* JDBC Connection.
|
||||||
* <p>Default is "true" on Hibernate EntityManager 4.x (with its 'on-close'
|
* <p>Default is "true". If you turn this flag off, JPA transaction management
|
||||||
* connection release mode.
|
* will not support per-transaction isolation levels anymore. It will not call
|
||||||
* <p>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.
|
* {@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
|
* If this flag is turned off, no cleanup of a JDBC Connection is required after
|
||||||
* a transaction, since no Connection settings will get modified.
|
* a transaction, since no Connection settings will get modified.
|
||||||
|
|
@ -415,10 +413,6 @@ public class HibernateJpaDialect extends DefaultJpaDialect {
|
||||||
return doGetConnection(this.session);
|
return doGetConnection(this.session);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void releaseConnection(Connection con) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Connection doGetConnection(Session session) {
|
public static Connection doGetConnection(Session session) {
|
||||||
try {
|
try {
|
||||||
Method methodToUse = connectionMethodToUse;
|
Method methodToUse = connectionMethodToUse;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link org.springframework.orm.jpa.JpaVendorAdapter} implementation for Hibernate
|
* {@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.
|
* backwards-compatible with Hibernate 4.3 at runtime on a best-effort basis.
|
||||||
*
|
*
|
||||||
* <p>Exposes Hibernate's persistence provider and EntityManager extension interface,
|
* <p>Exposes Hibernate's persistence provider and EntityManager extension interface,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue