HHH-12355 - Fix ordered insert failures with composite types having associations.
(cherry picked from commit 493c968
)
This commit is contained in:
parent
1d668a1ffb
commit
78be6a5312
|
@ -49,6 +49,7 @@ import org.hibernate.metadata.ClassMetadata;
|
|||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
import org.hibernate.type.CollectionType;
|
||||
import org.hibernate.type.CompositeType;
|
||||
import org.hibernate.type.EntityType;
|
||||
import org.hibernate.type.ForeignKeyDirection;
|
||||
import org.hibernate.type.OneToOneType;
|
||||
|
@ -1240,40 +1241,54 @@ public class ActionQueue {
|
|||
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();
|
||||
String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName();
|
||||
addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, type, value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( entityType.isOneToOne() &&
|
||||
OneToOneType.class.cast( entityType ).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) {
|
||||
batchIdentifier.getChildEntityNames().add( entityName );
|
||||
if ( !rootEntityName.equals( entityName ) ) {
|
||||
batchIdentifier.getChildEntityNames().add( rootEntityName );
|
||||
}
|
||||
}
|
||||
else {
|
||||
batchIdentifier.getParentEntityNames().add( entityName );
|
||||
if ( !rootEntityName.equals( entityName ) ) {
|
||||
batchIdentifier.getParentEntityNames().add( rootEntityName );
|
||||
}
|
||||
}
|
||||
private void addParentChildEntityNameByPropertyAndValue(AbstractEntityInsertAction action, BatchIdentifier batchIdentifier, Type type, Object value) {
|
||||
if ( type.isEntityType() && value != null ) {
|
||||
final EntityType entityType = (EntityType) type;
|
||||
final String entityName = entityType.getName();
|
||||
final String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName();
|
||||
|
||||
if ( entityType.isOneToOne() && OneToOneType.class.cast( entityType ).getForeignKeyDirection() == ForeignKeyDirection.TO_PARENT ) {
|
||||
batchIdentifier.getChildEntityNames().add( entityName );
|
||||
if ( !rootEntityName.equals( entityName ) ) {
|
||||
batchIdentifier.getChildEntityNames().add( rootEntityName );
|
||||
}
|
||||
else if ( type.isCollectionType() && value != null ) {
|
||||
CollectionType collectionType = (CollectionType) type;
|
||||
final SessionFactoryImplementor sessionFactory = ( (SessionImplementor) action.getSession() )
|
||||
.getSessionFactory();
|
||||
if ( collectionType.getElementType( sessionFactory ).isEntityType() ) {
|
||||
String entityName = collectionType.getAssociatedEntityName( sessionFactory );
|
||||
String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName();
|
||||
batchIdentifier.getChildEntityNames().add( entityName );
|
||||
if ( !rootEntityName.equals( entityName ) ) {
|
||||
batchIdentifier.getChildEntityNames().add( rootEntityName );
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
batchIdentifier.getParentEntityNames().add( entityName );
|
||||
if ( !rootEntityName.equals( entityName ) ) {
|
||||
batchIdentifier.getParentEntityNames().add( rootEntityName );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( type.isCollectionType() && value != null ) {
|
||||
CollectionType collectionType = (CollectionType) type;
|
||||
final SessionFactoryImplementor sessionFactory = ( (SessionImplementor) action.getSession() )
|
||||
.getSessionFactory();
|
||||
if ( collectionType.getElementType( sessionFactory ).isEntityType() ) {
|
||||
String entityName = collectionType.getAssociatedEntityName( sessionFactory );
|
||||
String rootEntityName = action.getSession().getFactory().getMetamodel().entityPersister( entityName ).getRootEntityName();
|
||||
batchIdentifier.getChildEntityNames().add( entityName );
|
||||
if ( !rootEntityName.equals( entityName ) ) {
|
||||
batchIdentifier.getChildEntityNames().add( rootEntityName );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( type.isComponentType() && value != null ) {
|
||||
// Support recursive checks of composite type properties for associations and collections.
|
||||
CompositeType compositeType = (CompositeType) type;
|
||||
final SharedSessionContractImplementor session = action.getSession();
|
||||
Object[] componentValues = compositeType.getPropertyValues( value, session );
|
||||
for ( int j = 0; j < componentValues.length; ++j ) {
|
||||
Type componentValueType = compositeType.getSubtypes()[j];
|
||||
Object componentValue = componentValues[j];
|
||||
addParentChildEntityNameByPropertyAndValue( action, batchIdentifier, componentValueType, componentValue );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addToBatch(BatchIdentifier batchIdentifier, AbstractEntityInsertAction action) {
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Embedded;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.ForeignKey;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.OneToOne;
|
||||
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@TestForIssue(jiraKey = "HHH-12355")
|
||||
public class InsertOrderingWithCompositeTypeAssociation extends BaseEntityManagerFunctionalTestCase {
|
||||
|
||||
@Entity(name = "Book")
|
||||
public static class Book {
|
||||
@Id
|
||||
private String id;
|
||||
@Embedded
|
||||
private IntermediateObject intermediateObject;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public IntermediateObject getIntermediateObject() {
|
||||
return intermediateObject;
|
||||
}
|
||||
|
||||
public void setIntermediateObject(IntermediateObject intermediateObject) {
|
||||
this.intermediateObject = intermediateObject;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Comment")
|
||||
public static class Comment {
|
||||
@Id
|
||||
private String id;
|
||||
@Column(length = 256)
|
||||
private String comment;
|
||||
|
||||
Comment() {
|
||||
|
||||
}
|
||||
|
||||
Comment(String comment) {
|
||||
this.id = UUID.randomUUID().toString();
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
||||
|
||||
@Embeddable
|
||||
public static class IntermediateObject {
|
||||
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = Comment.class)
|
||||
@JoinColumn(name = "comment_comment", foreignKey = @ForeignKey(name = "id" ) )
|
||||
private Comment comment;
|
||||
|
||||
IntermediateObject() {
|
||||
|
||||
}
|
||||
|
||||
IntermediateObject(Comment comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public Comment getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(Comment comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { Book.class, Comment.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConfigOptions(Map options) {
|
||||
super.addConfigOptions( options );
|
||||
options.put( AvailableSettings.ORDER_INSERTS, "true" );
|
||||
options.put( AvailableSettings.ORDER_UPDATES, "true" );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOrderedInsertSupport() {
|
||||
// Without the fix, this transaction would eventually fail with a foreign-key constraint violation.
|
||||
//
|
||||
// The bookNoComment entity would be persisted just fine; however the bookWithComment would fail
|
||||
// because it would lead to inserting the Book entities first rather than making sure that the
|
||||
// Comment would be inserted first.
|
||||
//
|
||||
// The associated ActionQueue fix makes sure that regardless of the order of operations, the Comment
|
||||
// entity associated in the embeddable takes insert priority over the parent Book entity.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
Book bookNoComment = new Book();
|
||||
bookNoComment.setId( UUID.randomUUID().toString() );
|
||||
|
||||
Book bookWithComment = new Book();
|
||||
bookWithComment.setId( UUID.randomUUID().toString() );
|
||||
bookWithComment.setIntermediateObject( new IntermediateObject( new Comment( "This is a comment" ) ) );
|
||||
|
||||
entityManager.persist( bookNoComment );
|
||||
entityManager.persist( bookWithComment );
|
||||
} );
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue