HHH-11585 - Batch ordering fails for bidirectional one-to-one associations

(cherry picked from commit f90845c30c)

HHH-11585 : Batch ordering fails for bidirectional one-to-one associations

HHH-11585 - Batch ordering fails for bidirectional one-to-one associations

- take into consideration legacy one-to-one mappings with composite ids as well

(cherry picked from commit acae69ffaf)

HHH-11585 : Fix test case to work on pre-5.2 branches

HHH-11585 - Batch ordering fails for bidirectional one-to-one associations
This commit is contained in:
Vlad Mihalcea 2017-03-22 10:43:22 +02:00 committed by Gail Badner
parent 820caed4d7
commit 16a0f01f00
11 changed files with 831 additions and 20 deletions

View File

@ -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 );
}
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<PreparedStatement, Integer> batchesAddedByPreparedStatement = new LinkedHashMap<PreparedStatement, Integer>();
@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();
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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;
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<PreparedStatement, String> sqlByPreparedStatement = new LinkedHashMap<PreparedStatement, String>();
@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<PreparedStatement> 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<PreparedStatement> getPreparedStatements(String sql) {
final List<PreparedStatement> preparedStatements = new ArrayList<PreparedStatement>();
for ( Map.Entry<PreparedStatement,String> entry : sqlByPreparedStatement.entrySet() ) {
if ( entry.getValue().equals( sql ) ) {
preparedStatements.add( entry.getKey() );
}
}
return preparedStatements;
}
@Override
public List<PreparedStatement> getPreparedStatements() {
return new ArrayList<PreparedStatement>( sqlByPreparedStatement.keySet() );
}
@Override
public void connectionProviderStopped() {
sqlByPreparedStatement.clear();
}
}

View File

@ -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<PreparedStatement> getPreparedStatements(String sql);
/**
* Get the PreparedStatements that were executed since the last clear operation.
*
* @return list of recorded PreparedStatements.
*/
List<PreparedStatement> getPreparedStatements();
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<Connection, Connection> acquiredConnectionProxyByConnection = new LinkedHashMap<Connection,Connection>();
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;
}
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
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<String, Object> settings = new HashMap<String, Object>( 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> T unwrap(Class<T> unwrapType) {
return connectionProvider.unwrap( unwrapType );
}
@Override
public void stop() {
if ( connectionProvider instanceof Stoppable ) {
( (Stoppable) connectionProvider ).stop();
}
}
}