HHH-11585 - Batch ordering fails for bidirectional one-to-one associations
(cherry picked from commitf90845c30c
) 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 commitacae69ffaf
) 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:
parent
820caed4d7
commit
16a0f01f00
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue