diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java index c9ba7b2bf0..4b98dfce48 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/ActionQueue.java @@ -44,10 +44,13 @@ import org.hibernate.cache.CacheException; import org.hibernate.engine.internal.NonNullableTransientDependencies; import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.metadata.ClassMetadata; import org.hibernate.proxy.HibernateProxy; import org.hibernate.proxy.LazyInitializer; import org.hibernate.type.CollectionType; import org.hibernate.type.EntityType; +import org.hibernate.type.ForeignKeyDirection; +import org.hibernate.type.OneToOneType; import org.hibernate.type.Type; /** @@ -1134,22 +1137,33 @@ public class ActionQueue { */ private void addParentChildEntityNames(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier) { Object[] propertyValues = action.getState(); - Type[] propertyTypes = action.getPersister().getClassMetadata().getPropertyTypes(); - for ( int i = 0; i < propertyValues.length; i++ ) { - Object value = propertyValues[i]; - Type type = propertyTypes[i]; - if ( type.isEntityType() && value != null ) { - EntityType entityType = (EntityType) type; - String entityName = entityType.getName(); - batchIdentifier.getParentEntityNames().add( entityName ); - } - else if ( type.isCollectionType() && value != null ) { - CollectionType collectionType = (CollectionType) type; - final SessionFactoryImplementor sessionFactory = action.getSession().getFactory(); - if ( collectionType.getElementType( sessionFactory ).isEntityType() ) { - String entityName = collectionType.getAssociatedEntityName( sessionFactory ); - batchIdentifier.getChildEntityNames().add( entityName ); + ClassMetadata classMetadata = action.getPersister().getClassMetadata(); + if ( classMetadata != null ) { + Type[] propertyTypes = classMetadata.getPropertyTypes(); + + for ( int i = 0; i < propertyValues.length; i++ ) { + Object value = propertyValues[i]; + Type type = propertyTypes[i]; + if ( type.isEntityType() && value != null ) { + EntityType entityType = (EntityType) type; + String entityName = entityType.getName(); + + if ( entityType.isOneToOne() && + OneToOneType.class.cast( entityType ).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) { + batchIdentifier.getChildEntityNames().add( entityName ); + } + else { + batchIdentifier.getParentEntityNames().add( entityName ); + } + } + else if ( type.isCollectionType() && value != null ) { + CollectionType collectionType = (CollectionType) type; + final SessionFactoryImplementor sessionFactory = action.getSession().getFactory(); + if ( collectionType.getElementType( sessionFactory ).isEntityType() ) { + String entityName = collectionType.getAssociatedEntityName( sessionFactory ); + batchIdentifier.getChildEntityNames().add( entityName ); + } } } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/BatchCountingPreparedStatementObserver.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/BatchCountingPreparedStatementObserver.java new file mode 100644 index 0000000000..09b2a23e36 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/BatchCountingPreparedStatementObserver.java @@ -0,0 +1,51 @@ +/* + * 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 . + */ +package org.hibernate.test.insertordering; + +import java.lang.reflect.Method; +import java.sql.PreparedStatement; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.hibernate.test.util.jdbc.BasicPreparedStatementObserver; + +/** + * @author Gail Badner + */ +class BatchCountingPreparedStatementObserver extends BasicPreparedStatementObserver { + private final Map batchesAddedByPreparedStatement = new LinkedHashMap(); + + @Override + public void preparedStatementCreated(PreparedStatement preparedStatement, String sql) { + super.preparedStatementCreated( preparedStatement, sql ); + batchesAddedByPreparedStatement.put( preparedStatement, 0 ); + } + + @Override + public void preparedStatementMethodInvoked( + PreparedStatement preparedStatement, + Method method, + Object[] args, + Object invocationReturnValue) { + super.preparedStatementMethodInvoked( preparedStatement, method, args, invocationReturnValue ); + if ( "addBatch".equals( method.getName() ) ) { + batchesAddedByPreparedStatement.put( + preparedStatement, + batchesAddedByPreparedStatement.get( preparedStatement ) + 1 + ); + } + } + + public int getNumberOfBatchesAdded(PreparedStatement preparedStatement) { + return batchesAddedByPreparedStatement.get( preparedStatement ); + } + + public void connectionProviderStopped() { + super.connectionProviderStopped(); + batchesAddedByPreparedStatement.clear(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalMapsIdOneToOne.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalMapsIdOneToOne.java new file mode 100644 index 0000000000..34a004c667 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalMapsIdOneToOne.java @@ -0,0 +1,123 @@ +/* + * 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 . + */ +package org.hibernate.test.insertordering; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MapsId; +import javax.persistence.OneToOne; +import javax.persistence.SequenceGenerator; + +import org.hibernate.Session; +import org.hibernate.cfg.Environment; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.test.util.jdbc.PreparedStatementProxyConnectionProvider; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-9864") +public class InsertOrderingWithBidirectionalMapsIdOneToOne + extends BaseNonConfigCoreFunctionalTestCase { + + private BatchCountingPreparedStatementObserver preparedStatementObserver = new BatchCountingPreparedStatementObserver(); + private PreparedStatementProxyConnectionProvider connectionProvider = new PreparedStatementProxyConnectionProvider( + preparedStatementObserver + ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Address.class, Person.class }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( Environment.ORDER_INSERTS, "true" ); + settings.put( Environment.STATEMENT_BATCH_SIZE, "10" ); + settings.put( + org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, + connectionProvider + ); + } + + @Override + public void releaseResources() { + super.releaseResources(); + connectionProvider.stop(); + } + + @Test + public void testBatching() throws SQLException { + Session session = openSession(); + session.getTransaction().begin(); + { + Person worker = new Person(); + Person homestay = new Person(); + + Address home = new Address(); + Address office = new Address(); + + home.addPerson( homestay ); + + office.addPerson( worker ); + + session.persist( home ); + session.persist( office ); + + connectionProvider.clear(); + } + session.getTransaction().commit(); + session.close(); + + PreparedStatement addressPreparedStatement = preparedStatementObserver.getPreparedStatement( + "insert into Address (ID) values (?)" ); + assertEquals( 2, preparedStatementObserver.getNumberOfBatchesAdded( addressPreparedStatement ) ); + + PreparedStatement personPreparedStatement = preparedStatementObserver.getPreparedStatement( + "insert into Person (address_ID) values (?)" ); + assertEquals( 2, preparedStatementObserver.getNumberOfBatchesAdded( personPreparedStatement ) ); + } + + @Entity(name = "Address") + public static class Address { + @Id + @Column(name = "ID", nullable = false) + @SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID") + private Long id; + + @OneToOne(mappedBy = "address", cascade = CascadeType.PERSIST) + private Person person; + + public void addPerson(Person person) { + this.person = person; + person.address = this; + } + } + + @Entity(name = "Person") + public static class Person { + @Id + private Long id; + + @OneToOne + @MapsId + private Address address; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToOne.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToOne.java new file mode 100644 index 0000000000..7239df979e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithBidirectionalOneToOne.java @@ -0,0 +1,118 @@ +/* + * 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 . + */ +package org.hibernate.test.insertordering; + +import org.hibernate.Session; +import org.hibernate.cfg.Environment; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import javax.persistence.*; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Map; + +import org.hibernate.test.util.jdbc.PreparedStatementProxyConnectionProvider; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-9864") +public class InsertOrderingWithBidirectionalOneToOne + extends BaseNonConfigCoreFunctionalTestCase { + private BatchCountingPreparedStatementObserver preparedStatementObserver = new BatchCountingPreparedStatementObserver(); + private PreparedStatementProxyConnectionProvider connectionProvider = new PreparedStatementProxyConnectionProvider( + preparedStatementObserver + ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Address.class, Person.class }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( Environment.ORDER_INSERTS, "true" ); + settings.put( Environment.STATEMENT_BATCH_SIZE, "10" ); + settings.put( + org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, + connectionProvider + ); + } + + @Override + public void releaseResources() { + super.releaseResources(); + connectionProvider.stop(); + } + + @Test + public void testBatching() throws SQLException { + Session session = openSession(); + session.getTransaction().begin(); + { + Person worker = new Person(); + Person homestay = new Person(); + + Address home = new Address(); + Address office = new Address(); + + home.addPerson( homestay ); + + office.addPerson( worker ); + + session.persist( home ); + session.persist( office ); + + connectionProvider.clear(); + } + session.getTransaction().commit(); + session.close(); + + PreparedStatement addressPreparedStatement = preparedStatementObserver.getPreparedStatement( + "insert into Address (ID) values (?)" + ); + assertEquals( 2, preparedStatementObserver.getNumberOfBatchesAdded( addressPreparedStatement ) ); + + PreparedStatement personPreparedStatement = preparedStatementObserver.getPreparedStatement( + "insert into Person (address_ID, ID) values (?, ?)" + ); + assertEquals( 2, preparedStatementObserver.getNumberOfBatchesAdded( personPreparedStatement ) ); + } + + @Entity(name = "Address") + public static class Address { + @Id + @Column(name = "ID", nullable = false) + @SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID") + private Long id; + + @OneToOne(mappedBy = "address", cascade = CascadeType.PERSIST) + private Person person; + + public void addPerson(Person person) { + this.person = person; + person.address = this; + } + } + + @Entity(name = "Person") + public static class Person { + @Id + @Column(name = "ID", nullable = false) + @SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID") + private Long id; + + @OneToOne + private Address address; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithUnidirectionalOneToOne.java b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithUnidirectionalOneToOne.java new file mode 100644 index 0000000000..9c5b40fb6d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/insertordering/InsertOrderingWithUnidirectionalOneToOne.java @@ -0,0 +1,120 @@ +/* + * 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 . + */ +package org.hibernate.test.insertordering; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.OneToOne; +import javax.persistence.SequenceGenerator; + +import org.hibernate.Session; +import org.hibernate.cfg.Environment; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.test.util.jdbc.PreparedStatementProxyConnectionProvider; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Vlad Mihalcea + */ +@TestForIssue(jiraKey = "HHH-9864") +public class InsertOrderingWithUnidirectionalOneToOne + extends BaseNonConfigCoreFunctionalTestCase { + + private BatchCountingPreparedStatementObserver preparedStatementObserver = new BatchCountingPreparedStatementObserver(); + private PreparedStatementProxyConnectionProvider connectionProvider = new PreparedStatementProxyConnectionProvider( + preparedStatementObserver + ); + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Address.class, Person.class }; + } + + @Override + protected void addSettings(Map settings) { + settings.put( Environment.ORDER_INSERTS, "true" ); + settings.put( Environment.STATEMENT_BATCH_SIZE, "10" ); + settings.put( + org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER, + connectionProvider + ); + } + + @Override + public void releaseResources() { + super.releaseResources(); + connectionProvider.stop(); + } + + @Test + public void testBatching() throws SQLException { + Session session = openSession(); + session.getTransaction().begin(); + { + Person worker = new Person(); + Person homestay = new Person(); + + Address home = new Address(); + Address office = new Address(); + + home.addPerson( homestay ); + + office.addPerson( worker ); + + session.persist( home ); + session.persist( office ); + + connectionProvider.clear(); + } + session.getTransaction().commit(); + session.close(); + + PreparedStatement addressPreparedStatement = preparedStatementObserver.getPreparedStatement( + "insert into Address (person_ID, ID) values (?, ?)" ); + assertEquals( 2, preparedStatementObserver.getNumberOfBatchesAdded( addressPreparedStatement ) ); + + PreparedStatement personPreparedStatement = preparedStatementObserver.getPreparedStatement( + "insert into Person (ID) values (?)" ); + assertEquals( 2, preparedStatementObserver.getNumberOfBatchesAdded( personPreparedStatement ) ); + } + + @Entity(name = "Address") + public static class Address { + @Id + @Column(name = "ID", nullable = false) + @SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID") + private Long id; + + @OneToOne( cascade = CascadeType.PERSIST ) + private Person person; + + public void addPerson(Person person) { + this.person = person; + } + } + + @Entity(name = "Person") + public static class Person { + @Id + @Column(name = "ID", nullable = false) + @SequenceGenerator(name = "ID", sequenceName = "ADDRESS_SEQ") + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID") + private Long id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java index c00a82b1c3..4598e10c4b 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/FumTest.java @@ -40,6 +40,8 @@ import org.hibernate.LockMode; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; import org.hibernate.criterion.MatchMode; import org.hibernate.criterion.Restrictions; import org.hibernate.dialect.AbstractHANADialect; @@ -90,6 +92,15 @@ public class FumTest extends LegacyTestCase { }; } + @Override + public void configure(Configuration cfg) { + super.configure(cfg); + Properties props = new Properties(); + props.put( Environment.ORDER_INSERTS, "true" ); + props.put( Environment.STATEMENT_BATCH_SIZE, "10" ); + cfg.addProperties( props ); + } + @Test public void testQuery() { Session s = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/legacy/MasterDetailTest.java b/hibernate-core/src/test/java/org/hibernate/test/legacy/MasterDetailTest.java index 567d57f908..d08302f684 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/legacy/MasterDetailTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/legacy/MasterDetailTest.java @@ -9,11 +9,7 @@ package org.hibernate.test.legacy; import java.io.Serializable; import java.sql.Connection; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; +import java.util.*; import org.hibernate.Hibernate; import org.hibernate.LockMode; @@ -21,6 +17,8 @@ import org.hibernate.ObjectNotFoundException; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.Transaction; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; import org.hibernate.criterion.Example; import org.hibernate.criterion.Restrictions; import org.hibernate.dialect.HSQLDialect; @@ -54,6 +52,15 @@ public class MasterDetailTest extends LegacyTestCase { }; } + @Override + public void configure(Configuration cfg) { + super.configure(cfg); + Properties props = new Properties(); + props.put( Environment.ORDER_INSERTS, "true" ); + props.put( Environment.STATEMENT_BATCH_SIZE, "10" ); + cfg.addProperties( props ); + } + @Test public void testOuterJoin() throws Exception { Session s = openSession(); diff --git a/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/BasicPreparedStatementObserver.java b/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/BasicPreparedStatementObserver.java new file mode 100644 index 0000000000..62532ef69d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/BasicPreparedStatementObserver.java @@ -0,0 +1,71 @@ +/* + * 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 . + */ +package org.hibernate.test.util.jdbc; + +import java.lang.reflect.Method; +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Vlad Mihalcea + * @author Gail Badner + */ +public class BasicPreparedStatementObserver implements PreparedStatementObserver { + private final Map sqlByPreparedStatement = new LinkedHashMap(); + + @Override + public void preparedStatementCreated(PreparedStatement preparedStatement, String sql) { + sqlByPreparedStatement.put( preparedStatement, sql ); + } + + @Override + public void preparedStatementMethodInvoked( + PreparedStatement preparedStatement, + Method method, + Object[] args, + Object invocationReturnValue) { + // do nothing by default + } + + @Override + public PreparedStatement getPreparedStatement(String sql) { + List preparedStatements = getPreparedStatements( sql ); + if ( preparedStatements.isEmpty() ) { + throw new IllegalArgumentException( + "There is no PreparedStatement for this SQL statement " + sql ); + } + else if ( preparedStatements.size() > 1 ) { + throw new IllegalArgumentException( "There are " + preparedStatements + .size() + " PreparedStatements for this SQL statement " + sql ); + } + return preparedStatements.get( 0 ); + } + + @Override + public List getPreparedStatements(String sql) { + final List preparedStatements = new ArrayList(); + for ( Map.Entry entry : sqlByPreparedStatement.entrySet() ) { + if ( entry.getValue().equals( sql ) ) { + preparedStatements.add( entry.getKey() ); + } + } + return preparedStatements; + } + + @Override + public List getPreparedStatements() { + return new ArrayList( sqlByPreparedStatement.keySet() ); + } + + @Override + public void connectionProviderStopped() { + sqlByPreparedStatement.clear(); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/PreparedStatementObserver.java b/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/PreparedStatementObserver.java new file mode 100644 index 0000000000..a90f7c5b6a --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/PreparedStatementObserver.java @@ -0,0 +1,66 @@ +package org.hibernate.test.util.jdbc; + +import java.lang.reflect.Method; +import java.sql.PreparedStatement; +import java.util.List; + +/** + * @author Gail Badner + */ +public interface PreparedStatementObserver { + + /** + * Called after a PreparedStatement is created. + * + * @param preparedStatement The created PreparedStatement + * @param sql The SQL used to create the PreparedStatement + */ + void preparedStatementCreated(PreparedStatement preparedStatement, String sql); + + /** + * Called after the specified method was invoked on the specified PreparedStatement. + * + * @param preparedStatement The PreparedStatement to which the Method has been invoked. + * @param method The Method that was invoked. + * @param args The arguments passed to the Method. + * @param invocationReturnValue The value returned by the Method invocation. + * @return The return value from the invocation. + */ + void preparedStatementMethodInvoked( + PreparedStatement preparedStatement, + Method method, + Object[] args, + Object invocationReturnValue); + + /** + * Called after the ConnectionProvider is stopped. Clears the recorded PreparedStatements and associated data. + */ + void connectionProviderStopped(); + + /** + * Get one and only one PreparedStatement associated to the given SQL statement. + * + * @param sql SQL statement. + * + * @return matching PreparedStatement. + * + * @throws IllegalArgumentException If there is no matching PreparedStatement or multiple instances, an exception is being thrown. + */ + PreparedStatement getPreparedStatement(String sql); + + /** + * Get the PreparedStatements that are associated to the following SQL statement. + * + * @param sql SQL statement. + * + * @return list of recorded PreparedStatements matching the SQL statement. + */ + List getPreparedStatements(String sql); + + /** + * Get the PreparedStatements that were executed since the last clear operation. + * + * @return list of recorded PreparedStatements. + */ + List getPreparedStatements(); +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/PreparedStatementProxyConnectionProvider.java b/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/PreparedStatementProxyConnectionProvider.java new file mode 100644 index 0000000000..48dfc62114 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/util/jdbc/PreparedStatementProxyConnectionProvider.java @@ -0,0 +1,132 @@ +/* + * 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 . + */ +package org.hibernate.test.util.jdbc; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; + +import org.hibernate.testing.jdbc.ConnectionProviderDelegate; + +/** + * This {@link ConnectionProvider} extends any other ConnectionProvider that would be used by default taken the current configuration properties, and it + * intercept the underlying {@link PreparedStatement} method calls. + * + * @author Gail Badner + */ + +public class PreparedStatementProxyConnectionProvider extends ConnectionProviderDelegate { + + private final Map acquiredConnectionProxyByConnection = new LinkedHashMap(); + private final PreparedStatementObserver preparedStatementObserver; + + public PreparedStatementProxyConnectionProvider(BasicPreparedStatementObserver preparedStatementObserver) { + this.preparedStatementObserver = preparedStatementObserver; + } + + protected Connection actualConnection() throws SQLException { + return super.getConnection(); + } + + @Override + public Connection getConnection() throws SQLException { + + Connection actualConnection = actualConnection(); + Connection connectionProxy = acquiredConnectionProxyByConnection.get( actualConnection ); + if ( connectionProxy == null ) { + connectionProxy = (Connection) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] { Connection.class }, + new ConnectionHandler( actualConnection, preparedStatementObserver ) + ); + acquiredConnectionProxyByConnection.put( actualConnection, connectionProxy ); + } + return connectionProxy; + } + + @Override + public void closeConnection(Connection conn) throws SQLException { + final Connection actualConnection = + Proxy.isProxyClass( conn.getClass() ) ? + ( (ConnectionHandler) Proxy.getInvocationHandler( conn ) ).actualConnection : + conn; + acquiredConnectionProxyByConnection.remove( actualConnection ); + super.closeConnection( actualConnection ); + } + + @Override + public void stop() { + clear(); + super.stop(); + preparedStatementObserver.connectionProviderStopped(); + } + + /** + * Clears the recorded PreparedStatements. + */ + public void clear() { + acquiredConnectionProxyByConnection.clear(); + } + + private static class ConnectionHandler implements InvocationHandler { + + private final Connection actualConnection; + private final PreparedStatementObserver preparedStatementObserver; + + ConnectionHandler(Connection actualConnection, PreparedStatementObserver preparedStatementObserver) { + this.actualConnection = actualConnection; + this.preparedStatementObserver = preparedStatementObserver; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + final String methodName = method.getName(); + if ( "prepareStatement".equals( methodName ) ) { + String sql = (String) args[0]; + final PreparedStatement preparedStatement = (PreparedStatement) Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[] { PreparedStatement.class }, + new PreparedStatementHandler( actualConnection.prepareStatement( sql ), + preparedStatementObserver + ) + ); + preparedStatementObserver.preparedStatementCreated( preparedStatement, sql ); + return preparedStatement; + } + return method.invoke( actualConnection, args ); + } + } + + private static class PreparedStatementHandler implements InvocationHandler { + private final PreparedStatement actualPreparedStatement; + private final PreparedStatementObserver preparedStatementObserver; + + PreparedStatementHandler(PreparedStatement actualPreparedStatement, PreparedStatementObserver preparedStatementObserver) { + this.actualPreparedStatement = actualPreparedStatement; + this.preparedStatementObserver = preparedStatementObserver; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + final Object returnValue = method.invoke( actualPreparedStatement, args ); + preparedStatementObserver.preparedStatementMethodInvoked( + (PreparedStatement) proxy, + method, + args, + returnValue + ); + return returnValue; + } + } +} diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/ConnectionProviderDelegate.java b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/ConnectionProviderDelegate.java new file mode 100644 index 0000000000..0cfce1f137 --- /dev/null +++ b/hibernate-testing/src/main/java/org/hibernate/testing/jdbc/ConnectionProviderDelegate.java @@ -0,0 +1,98 @@ +/* + * 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 . + */ +package org.hibernate.testing.jdbc; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; +import org.hibernate.service.spi.Configurable; +import org.hibernate.service.spi.ServiceRegistryAwareService; +import org.hibernate.service.spi.ServiceRegistryImplementor; +import org.hibernate.service.spi.Stoppable; + +/** + * This {@link ConnectionProvider} extends any other ConnectionProvider + * that would be used by default taken the current configuration properties. + * + * @author Vlad Mihalcea + */ +public class ConnectionProviderDelegate implements + ConnectionProvider, + Configurable, + ServiceRegistryAwareService, + Stoppable { + + private ServiceRegistryImplementor serviceRegistry; + + private ConnectionProvider connectionProvider; + + public ConnectionProviderDelegate() { + } + + public ConnectionProviderDelegate(ConnectionProvider connectionProvider) { + this.connectionProvider = connectionProvider; + } + + @Override + public void injectServices(ServiceRegistryImplementor serviceRegistry) { + this.serviceRegistry = serviceRegistry; + } + + @Override + public void configure(Map configurationValues) { + if ( connectionProvider == null ) { + @SuppressWarnings("unchecked") + Map settings = new HashMap( configurationValues ); + settings.remove( AvailableSettings.CONNECTION_PROVIDER ); + connectionProvider = ConnectionProviderInitiator.INSTANCE.initiateService( + settings, + serviceRegistry + ); + if ( connectionProvider instanceof Configurable ) { + Configurable configurableConnectionProvider = (Configurable) connectionProvider; + configurableConnectionProvider.configure( settings ); + } + } + } + + @Override + public Connection getConnection() throws SQLException { + return connectionProvider.getConnection(); + } + + @Override + public void closeConnection(Connection conn) throws SQLException { + connectionProvider.closeConnection( conn ); + } + + @Override + public boolean supportsAggressiveRelease() { + return connectionProvider.supportsAggressiveRelease(); + } + + @Override + public boolean isUnwrappableAs(Class unwrapType) { + return connectionProvider.isUnwrappableAs( unwrapType ); + } + + @Override + public T unwrap(Class unwrapType) { + return connectionProvider.unwrap( unwrapType ); + } + + @Override + public void stop() { + if ( connectionProvider instanceof Stoppable ) { + ( (Stoppable) connectionProvider ).stop(); + } + } +}