Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
Expand Down Expand Up @@ -92,6 +93,9 @@ public boolean isSnapshotEmpty(Serializable snapshot) {
public Collection getOrphans(Serializable snapshot, String entityName) throws HibernateException {
final Object[] sn = (Object[]) snapshot;
final Object[] arr = (Object[]) array;
if ( arr.length == 0 ) {
return Arrays.asList( sn );
}
final ArrayList result = new ArrayList();
Collections.addAll( result, sn );
for ( int i=0; i<sn.length; i++ ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -601,10 +601,20 @@ private static <T> void cascadeCollectionElements(
}
}

// a newly instantiated collection can't have orphans
final PersistentCollection<?> persistentCollection;
if ( child instanceof PersistentCollection<?> pc ) {
persistentCollection = pc;
}
else {
persistentCollection = eventSource.getPersistenceContext()
.getCollectionHolder( child );
}

final boolean deleteOrphans = style.hasOrphanDelete()
&& action.deleteOrphans()
&& elemType instanceof EntityType
&& child instanceof PersistentCollection<?> persistentCollection
&& persistentCollection != null
// a newly instantiated collection can't have orphans
&& !persistentCollection.isNewlyInstantiated();

Expand All @@ -617,7 +627,7 @@ private static <T> void cascadeCollectionElements(
// 1. newly instantiated collections
// 2. arrays (we can't track orphans for detached arrays)
final String entityName = collectionType.getAssociatedEntityName( eventSource.getFactory() );
deleteOrphans( eventSource, entityName, (PersistentCollection<?>) child );
deleteOrphans( eventSource, entityName, persistentCollection );

if ( traceEnabled ) {
LOG.tracev( "Done deleting orphans for collection: {0}", collectionType.getRole() );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.hibernate.Internal;
import org.hibernate.MappingException;
import org.hibernate.collection.spi.AbstractPersistentCollection;
import org.hibernate.collection.spi.PersistentArrayHolder;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CollectionEntry;
import org.hibernate.engine.spi.CollectionKey;
Expand Down Expand Up @@ -634,11 +635,31 @@ public Object replace(
if ( target == null ) {
return null;
}
if ( target instanceof PersistentCollection ) {
final Collection collection = (Collection) target;
if ( target instanceof Collection<?> collection ) {
collection.clear();
return collection;
}
else if ( target instanceof Map<?,?> map ) {
map.clear();
return map;
}
else {
final PersistenceContext persistenceContext = session.getPersistenceContext();
final PersistentCollection<?> collectionHolder = persistenceContext
.getCollectionHolder( target );
if ( collectionHolder != null ) {
if ( collectionHolder instanceof PersistentArrayHolder<?> persistentArrayHolder ) {
persistenceContext.removeCollectionHolder( target );
persistentArrayHolder.beginRead();
persistentArrayHolder.injectLoadedState( persistenceContext.getCollectionEntry( collectionHolder ).getLoadedPersister().getAttributeMapping(), null );
persistentArrayHolder.endRead();
persistentArrayHolder.dirty();
persistenceContext.addCollectionHolder( collectionHolder );
return persistentArrayHolder.getArray();
}
}
}

return null;
}
if ( !Hibernate.isInitialized( original ) ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.orm.test.jpa.orphan.onetomany.merge;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

@DomainModel(
annotatedClasses = {
MergeCascadeWithMapCollectionTest.Parent.class,
MergeCascadeWithMapCollectionTest.Child.class,
}
)
@SessionFactory
@JiraKey("HHH-18842")
public class MergeCascadeWithMapCollectionTest {

private static final Long ID_PARENT_WITHOUT_CHILDREN = 1L;
private static final Long ID_PARENT_WITH_CHILDREN = 2L;

@BeforeEach
public void setUp(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Parent parent = new Parent( ID_PARENT_WITHOUT_CHILDREN, "old name" );
session.persist( parent );

Parent parent2 = new Parent( ID_PARENT_WITH_CHILDREN, "old name" );
Child child = new Child( 2l, "Child" );
parent2.addChild( child );

session.persist( child );
session.persist( parent2 );
}
);
}

@AfterEach
private void tearDown(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
session.createMutationQuery( "delete from Parent" ).executeUpdate();
session.createMutationQuery( "delete from Child" ).executeUpdate();
}
);
}

@Test
public void testMergeParentWihoutChildren(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Parent parent = new Parent( ID_PARENT_WITHOUT_CHILDREN, "new name" );
Parent merged = session.merge( parent );
assertThat( merged.getName() ).isEqualTo( "new name" );
}
);
}

@Test
public void testMergeParentWithChildren(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Parent parent = new Parent( ID_PARENT_WITH_CHILDREN, "new name" );
Child child = new Child( 2l, "Child" );
parent.addChild( child );
Parent merged = session.merge( parent );
assertThat( merged.getName() ).isEqualTo( "new name" );
}
);
scope.inTransaction(
session -> {
Parent parent = session.get( Parent.class, ID_PARENT_WITH_CHILDREN );
assertThat( parent.getChildren().size() ).isEqualTo( 1 );
}
);
}

@Test
public void testMergeParentWithChildren2(SessionFactoryScope scope) {
scope.inTransaction(
session -> {
Parent parent = new Parent( ID_PARENT_WITH_CHILDREN, "new name" );
session.merge( parent );
}
);
scope.inTransaction(
session -> {
Parent parent = session.get( Parent.class, ID_PARENT_WITH_CHILDREN );
assertThat( parent.getName() ).isEqualTo( "new name" );
assertThat( parent.getChildren().size() ).isEqualTo( 0 );

List<Child> children = session.createQuery( "Select c from Child c", Child.class ).list();
assertThat( children.size() ).isEqualTo( 1 );
}
);
}

@Entity(name = "Parent")
public static class Parent {

@Id
private Long id;

private String name;

@OneToMany(cascade = CascadeType.MERGE)
private Map<String, Child> children;

public Parent() {
}

public Parent(Long id, String name) {
this.id = id;
this.name = name;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Map<String, Child> getChildren() {
return children;
}

public void setChildren(Map<String, Child> children) {
this.children = children;
}

public void addChild(Child child) {
if ( children == null ) {
children = new HashMap<>();
}
children.put( child.getName(), child );
}
}

@Entity(name = "Child")
public static class Child {

@Id
private Long id;

private String name;

public Child() {
}

public Child(Long id, String name) {
this.id = id;
this.name = name;
}

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

}

}
Loading
Loading