Merge remote-tracking branch 'upstream/master' into wip/6.0

This commit is contained in:
Andrea Boriero 2021-01-20 17:48:43 +01:00
commit b761cfddd3
9 changed files with 267 additions and 106 deletions

View File

@ -76,6 +76,7 @@ import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement; import org.hibernate.query.sqm.tree.delete.SqmDeleteStatement;
import org.hibernate.query.sqm.tree.update.SqmUpdateStatement; import org.hibernate.query.sqm.tree.update.SqmUpdateStatement;
import org.hibernate.resource.jdbc.spi.JdbcSessionContext; import org.hibernate.resource.jdbc.spi.JdbcSessionContext;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.resource.jdbc.spi.StatementInspector; import org.hibernate.resource.jdbc.spi.StatementInspector;
import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl; import org.hibernate.resource.transaction.backend.jta.internal.JtaTransactionCoordinatorImpl;
import org.hibernate.resource.transaction.spi.TransactionCoordinator; import org.hibernate.resource.transaction.spi.TransactionCoordinator;
@ -124,6 +125,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
private FlushMode flushMode; private FlushMode flushMode;
private boolean autoJoinTransactions; private boolean autoJoinTransactions;
private final PhysicalConnectionHandlingMode connectionHandlingMode;
private CacheMode cacheMode; private CacheMode cacheMode;
@ -169,11 +171,9 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
sessionEventsManager = new SessionEventListenerManagerImpl( customSessionEventListener.toArray( new SessionEventListener[0] ) ); sessionEventsManager = new SessionEventListenerManagerImpl( customSessionEventListener.toArray( new SessionEventListener[0] ) );
} }
final StatementInspector statementInspector = interpret( options.getStatementInspector() );
this.jdbcSessionContext = new JdbcSessionContextImpl( this, statementInspector, fastSessionServices );
this.entityNameResolver = new CoordinatingEntityNameResolver( factory, interceptor ); this.entityNameResolver = new CoordinatingEntityNameResolver( factory, interceptor );
final StatementInspector statementInspector = interpret( options.getStatementInspector() );
if ( options instanceof SharedSessionCreationOptions && ( (SharedSessionCreationOptions) options ).isTransactionCoordinatorShared() ) { if ( options instanceof SharedSessionCreationOptions && ( (SharedSessionCreationOptions) options ).isTransactionCoordinatorShared() ) {
if ( options.getConnection() != null ) { if ( options.getConnection() != null ) {
throw new SessionException( "Cannot simultaneously share transaction context and specify connection" ); throw new SessionException( "Cannot simultaneously share transaction context and specify connection" );
@ -195,18 +195,27 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
); );
autoJoinTransactions = false; autoJoinTransactions = false;
} }
if ( sharedOptions.getPhysicalConnectionHandlingMode() != this.jdbcCoordinator.getLogicalConnection().getConnectionHandlingMode() ) { this.connectionHandlingMode = this.jdbcCoordinator.getLogicalConnection().getConnectionHandlingMode();
if ( sharedOptions.getPhysicalConnectionHandlingMode() != this.connectionHandlingMode ) {
log.debug( log.debug(
"Session creation specified 'PhysicalConnectionHandlingMode' which is invalid in conjunction " + "Session creation specified 'PhysicalConnectionHandlingMode' which is invalid in conjunction " +
"with sharing JDBC connection between sessions; ignoring" "with sharing JDBC connection between sessions; ignoring"
); );
} }
this.jdbcSessionContext = new JdbcSessionContextImpl( this, statementInspector,
connectionHandlingMode, fastSessionServices );
addSharedSessionTransactionObserver( transactionCoordinator ); addSharedSessionTransactionObserver( transactionCoordinator );
} }
else { else {
this.isTransactionCoordinatorShared = false; this.isTransactionCoordinatorShared = false;
this.autoJoinTransactions = options.shouldAutoJoinTransactions(); this.autoJoinTransactions = options.shouldAutoJoinTransactions();
this.connectionHandlingMode = options.getPhysicalConnectionHandlingMode();
this.jdbcSessionContext = new JdbcSessionContextImpl( this, statementInspector,
connectionHandlingMode, fastSessionServices );
// This must happen *after* the JdbcSessionContext was initialized,
// because some of the calls below retrieve this context indirectly through Session getters.
this.jdbcCoordinator = new JdbcCoordinatorImpl( options.getConnection(), this, fastSessionServices.jdbcServices ); this.jdbcCoordinator = new JdbcCoordinatorImpl( options.getConnection(), this, fastSessionServices.jdbcServices );
this.transactionCoordinator = fastSessionServices.transactionCoordinatorBuilder.buildTransactionCoordinator( jdbcCoordinator, this ); this.transactionCoordinator = fastSessionServices.transactionCoordinatorBuilder.buildTransactionCoordinator( jdbcCoordinator, this );
} }
@ -1014,7 +1023,8 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
factory = SessionFactoryImpl.deserialize( ois ); factory = SessionFactoryImpl.deserialize( ois );
fastSessionServices = factory.getFastSessionServices(); fastSessionServices = factory.getFastSessionServices();
sessionEventsManager = new SessionEventListenerManagerImpl( fastSessionServices.defaultSessionEventListeners.buildBaseline() ); sessionEventsManager = new SessionEventListenerManagerImpl( fastSessionServices.defaultSessionEventListeners.buildBaseline() );
jdbcSessionContext = new JdbcSessionContextImpl( this, (StatementInspector) ois.readObject(), fastSessionServices ); jdbcSessionContext = new JdbcSessionContextImpl( this, (StatementInspector) ois.readObject(),
connectionHandlingMode, fastSessionServices );
jdbcCoordinator = JdbcCoordinatorImpl.deserialize( ois, this ); jdbcCoordinator = JdbcCoordinatorImpl.deserialize( ois, this );
cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this ); cacheTransactionSync = factory.getCache().getRegionFactory().createTransactionContext( this );

View File

@ -31,10 +31,11 @@ public class JdbcSessionContextImpl implements JdbcSessionContext {
public JdbcSessionContextImpl( public JdbcSessionContextImpl(
SharedSessionContractImplementor session, SharedSessionContractImplementor session,
StatementInspector statementInspector, StatementInspector statementInspector,
PhysicalConnectionHandlingMode connectionHandlingMode,
FastSessionServices fastSessionServices) { FastSessionServices fastSessionServices) {
this.sessionFactory = session.getFactory(); this.sessionFactory = session.getFactory();
this.statementInspector = statementInspector; this.statementInspector = statementInspector;
this.connectionHandlingMode = settings().getPhysicalConnectionHandlingMode(); this.connectionHandlingMode = connectionHandlingMode;
this.serviceRegistry = sessionFactory.getServiceRegistry(); this.serviceRegistry = sessionFactory.getServiceRegistry();
this.jdbcObserver = new JdbcObserverImpl( session, fastSessionServices ); this.jdbcObserver = new JdbcObserverImpl( session, fastSessionServices );

View File

@ -1431,7 +1431,7 @@ public class SessionFactoryImpl implements SessionFactoryImplementor {
@Override @Override
public PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode() { public PhysicalConnectionHandlingMode getPhysicalConnectionHandlingMode() {
return null; return sessionFactory.getSessionFactoryOptions().getPhysicalConnectionHandlingMode();
} }
@Override @Override

View File

@ -193,33 +193,34 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple
} }
private void releaseConnection() { private void releaseConnection() {
//Some managed containers might trigger this release concurrently:
//this is not how they should do things, still we make a local
//copy of the variable to prevent confusing errors due to a race conditions
//(to trigger a more clear error, if any).
final Connection localVariableConnection = this.physicalConnection; final Connection localVariableConnection = this.physicalConnection;
if ( localVariableConnection == null ) { if ( localVariableConnection == null ) {
return; return;
} }
// We need to set the connection to null before we release resources,
// in order to prevent recursion into this method.
// Recursion can happen when we release resources and when batch statements are in progress:
// when releasing resources, we'll abort the batch statement,
// which will trigger "logicalConnection.afterStatement()",
// which in some configurations will release the connection.
this.physicalConnection = null;
try { try {
if ( ! localVariableConnection.isClosed() ) { try {
sqlExceptionHelper.logAndClearWarnings( localVariableConnection ); getResourceRegistry().releaseResources();
if ( !localVariableConnection.isClosed() ) {
sqlExceptionHelper.logAndClearWarnings( localVariableConnection );
}
}
finally {
jdbcConnectionAccess.releaseConnection( localVariableConnection );
} }
jdbcConnectionAccess.releaseConnection( localVariableConnection );
} }
catch (SQLException e) { catch (SQLException e) {
throw sqlExceptionHelper.convert( e, "Unable to release JDBC Connection" ); throw sqlExceptionHelper.convert( e, "Unable to release JDBC Connection" );
} }
finally { finally {
observer.jdbcConnectionReleaseEnd(); observer.jdbcConnectionReleaseEnd();
boolean concurrentUsageDetected = ( this.physicalConnection == null );
this.physicalConnection = null;
getResourceRegistry().releaseResources();
if ( concurrentUsageDetected ) {
throw new HibernateException( "Detected concurrent management of connection resources." +
" This might indicate a multi-threaded use of Hibernate in combination with managed resources, which is not supported." );
}
} }
} }

View File

@ -7,46 +7,98 @@
package org.hibernate.test.connections; package org.hibernate.test.connections;
import org.hibernate.cfg.AvailableSettings; import java.sql.Connection;
import org.hibernate.dialect.H2Dialect; import java.sql.SQLException;
import org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl; import java.sql.Statement;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import java.util.Arrays;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import java.util.List;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode; import java.util.Map;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.env.ConnectionProviderBuilder;
import org.hibernate.testing.jta.TestingJtaBootstrap;
import org.hibernate.testing.jta.TestingJtaPlatformImpl;
import org.hibernate.testing.transaction.TransactionUtil;
import org.junit.Test;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Table; import javax.persistence.Table;
import javax.transaction.RollbackException; import javax.transaction.RollbackException;
import javax.transaction.SystemException; import javax.transaction.SystemException;
import javax.transaction.Transaction; import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource; import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import static org.junit.Assert.assertTrue; import org.hibernate.Session;
import static org.junit.Assert.fail; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.env.ConnectionProviderBuilder;
import org.hibernate.testing.jta.TestingJtaBootstrap;
import org.hibernate.testing.jta.TestingJtaPlatformImpl;
import org.hibernate.testing.junit4.CustomParameterized;
import org.hibernate.testing.transaction.TransactionUtil2;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
/** /**
* @author Luis Barreiro * @author Luis Barreiro
*/ */
@RequiresDialect( H2Dialect.class ) @RequiresDialect( H2Dialect.class )
@RunWith(CustomParameterized.class)
public class BeforeCompletionReleaseTest extends BaseEntityManagerFunctionalTestCase { public class BeforeCompletionReleaseTest extends BaseEntityManagerFunctionalTestCase {
@Parameterized.Parameters(name = "{0}")
public static List<Object[]> params() {
return Arrays.asList( new Object[][] {
{
"Setting connection handling mode from properties",
PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_BEFORE_TRANSACTION_COMPLETION,
null
},
{
"Setting connection handling mode through SessionBuilder",
PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT,
PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_BEFORE_TRANSACTION_COMPLETION
}
} );
}
@Rule
public MockitoRule mockito = MockitoJUnit.rule().strictness( Strictness.STRICT_STUBS );
private final PhysicalConnectionHandlingMode connectionHandlingModeInProperties;
private final PhysicalConnectionHandlingMode connectionHandlingModeInSessionBuilder;
public BeforeCompletionReleaseTest(
String ignoredTestLabel, PhysicalConnectionHandlingMode connectionHandlingModeInProperties,
PhysicalConnectionHandlingMode connectionHandlingModeInSessionBuilder) {
this.connectionHandlingModeInProperties = connectionHandlingModeInProperties;
this.connectionHandlingModeInSessionBuilder = connectionHandlingModeInSessionBuilder;
}
@Override @Override
protected Map getConfig() { protected Map getConfig() {
Map config = super.getConfig(); Map config = super.getConfig();
TestingJtaBootstrap.prepare( config ); TestingJtaBootstrap.prepare( config );
config.put( AvailableSettings.CONNECTION_PROVIDER, new ConnectionProviderDecorator() ); config.put( AvailableSettings.CONNECTION_PROVIDER, new ConnectionProviderDecorator() );
config.put( AvailableSettings.CONNECTION_HANDLING, PhysicalConnectionHandlingMode.DELAYED_ACQUISITION_AND_RELEASE_BEFORE_TRANSACTION_COMPLETION ); if ( connectionHandlingModeInProperties != null ) {
config.put( AvailableSettings.CONNECTION_HANDLING, connectionHandlingModeInProperties );
}
return config; return config;
} }
@ -56,12 +108,49 @@ public class BeforeCompletionReleaseTest extends BaseEntityManagerFunctionalTest
} }
@Test @Test
public void testConnectionAcquisitionCount() { @TestForIssue(jiraKey = {"HHH-13976", "HHH-14326"})
TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { public void testResourcesReleasedThenConnectionClosedThenCommit() throws SQLException, XAException {
Thing thing = new Thing(); XAResource transactionSpy = mock( XAResource.class );
thing.setId( 1 ); Connection[] connectionSpies = new Connection[1];
entityManager.persist( thing ); Statement statementMock = Mockito.mock( Statement.class );
});
try (SessionImplementor s = (SessionImplementor) openSession()) {
TransactionUtil2.inTransaction( s, session -> {
spyOnTransaction( transactionSpy );
Thing thing = new Thing();
thing.setId( 1 );
session.persist( thing );
LogicalConnectionImplementor logicalConnection = session.getJdbcCoordinator().getLogicalConnection();
logicalConnection.getResourceRegistry().register( statementMock, true );
connectionSpies[0] = logicalConnection.getPhysicalConnection();
} );
}
Connection connectionSpy = connectionSpies[0];
// Must close the resources, then the connection, then commit
InOrder inOrder = inOrder( statementMock, connectionSpy, transactionSpy );
inOrder.verify( statementMock ).close();
inOrder.verify( connectionSpy ).close();
inOrder.verify( transactionSpy ).commit( any(), anyBoolean() );
}
private void spyOnTransaction(XAResource xaResource) {
try {
TestingJtaPlatformImpl.transactionManager().getTransaction().enlistResource( xaResource );
}
catch (RollbackException | SystemException e) {
throw new IllegalStateException( e );
}
}
private Session openSession() {
return connectionHandlingModeInSessionBuilder == null
? entityManagerFactory().openSession()
: entityManagerFactory().withOptions().connectionHandlingMode( connectionHandlingModeInSessionBuilder )
.openSession();
} }
// --- // // --- //
@ -94,63 +183,7 @@ public class BeforeCompletionReleaseTest extends BaseEntityManagerFunctionalTest
@Override @Override
public Connection getConnection() throws SQLException { public Connection getConnection() throws SQLException {
Connection connection = dataSource.getConnection(); return spy( dataSource.getConnection() );
try {
Transaction tx = TestingJtaPlatformImpl.transactionManager().getTransaction();
if ( tx != null) {
tx.enlistResource( new XAResource() {
@Override public void commit(Xid xid, boolean onePhase) {
try {
assertTrue( "Connection should be closed prior to commit", connection.isClosed() );
} catch ( SQLException e ) {
fail( "Unexpected SQLException: " + e.getMessage() );
}
}
@Override public void end(Xid xid, int flags) {
}
@Override public void forget(Xid xid) {
}
@Override public int getTransactionTimeout() {
return 0;
}
@Override public boolean isSameRM(XAResource xares) {
return false;
}
@Override public int prepare(Xid xid) {
return 0;
}
@Override public Xid[] recover(int flag) {
return new Xid[0];
}
@Override public void rollback(Xid xid) {
try {
assertTrue( "Connection should be closed prior to rollback", connection.isClosed() );
} catch ( SQLException e ) {
fail( "Unexpected SQLException: " + e.getMessage() );
}
}
@Override public boolean setTransactionTimeout(int seconds) {
return false;
}
@Override public void start(Xid xid, int flags) {
}
});
}
} catch ( SystemException | RollbackException e ) {
fail( e.getMessage() );
}
return connection;
} }
@Override @Override
@ -159,3 +192,4 @@ public class BeforeCompletionReleaseTest extends BaseEntityManagerFunctionalTest
} }
} }
} }

View File

@ -0,0 +1,23 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.id.hhh14407;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
/**
* @author Sönke Reimer
*/
@Entity(name="ChildEntity")
class ChildEntity extends ParentEntity {
@Basic
@Column(name="CHILD")
private String ivChild;
}

View File

@ -0,0 +1,34 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.id.hhh14407;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Version;
/**
* @author Sönke Reimer
*/
@Entity(name="ParentEntity")
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
class ParentEntity {
@Id
@Column(name = "ID", length = 32)
private String Id;
@Version
@Column(name = "LOCK_VERSION")
private int Lock_Version;
public String getId() {
return Id;
}
}

View File

@ -0,0 +1,58 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.id.hhh14407;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
import org.hibernate.dialect.H2Dialect;
import org.hibernate.hql.spi.id.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.id.persistent.PersistentTableBulkIdStrategy;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
/**
* @author Nathan Xu
* @author Sönke Reimer
*/
@RequiresDialect( value = H2Dialect.class )
@TestForIssue( jiraKey = "HHH14407" )
public class PersistentTableBulkIdStrategyNPETest extends BaseCoreFunctionalTestCase {
@Override
protected void configure(Configuration configuration) {
configuration.setProperty(
Environment.DIALECT,
PersistentTableBulkIdH2Dialect.class.getName()
);
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
ParentEntity.class,
ChildEntity.class
};
}
@Test
public void hhh14407Test() {
// without fix of HHH14407, the test case will trigger exception due to NPE in PersistentTableBulkIdStrategy
}
public static class PersistentTableBulkIdH2Dialect extends H2Dialect {
@Override
public MultiTableBulkIdStrategy getDefaultMultiTableBulkIdStrategy() {
return new PersistentTableBulkIdStrategy();
}
}
}

View File

@ -23,5 +23,5 @@ public @interface TestForIssue {
* The key of a JIRA issue tested. * The key of a JIRA issue tested.
* @return The jira issue key * @return The jira issue key
*/ */
String jiraKey(); String[] jiraKey();
} }