HHH-14619 Test and fix ClassCastException because collection of uninitialized proxy is dirty checked

This commit is contained in:
Christian Beikov 2021-05-19 15:50:24 +02:00
parent 6dc3b4a726
commit bf19f98c2d
4 changed files with 182 additions and 3 deletions

View File

@ -219,7 +219,6 @@ public abstract class AbstractFlushingEventListener implements JpaBootstrapSensi
final int count = entityEntries.length;
for ( Map.Entry<Object,EntityEntry> me : entityEntries ) {
// Update the status of the object and if necessary, schedule an update
EntityEntry entry = me.getValue();

View File

@ -485,6 +485,7 @@ public class DefaultFlushEntityEventListener implements FlushEntityEventListener
private boolean hasDirtyCollections(FlushEntityEvent event, EntityPersister persister, Status status) {
if ( isCollectionDirtyCheckNecessary( persister, status ) ) {
DirtyCollectionSearchVisitor visitor = new DirtyCollectionSearchVisitor(
event.getEntity(),
event.getSession(),
persister.getPropertyVersionability()
);

View File

@ -7,7 +7,9 @@
package org.hibernate.event.internal;
import org.hibernate.HibernateException;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.PersistentAttributeInterceptable;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.EventSource;
import org.hibernate.type.CollectionType;
@ -22,11 +24,19 @@ import org.hibernate.type.CollectionType;
*/
public class DirtyCollectionSearchVisitor extends AbstractVisitor {
private final EnhancementAsProxyLazinessInterceptor interceptor;
private final boolean[] propertyVersionability;
private boolean dirty;
private boolean[] propertyVersionability;
public DirtyCollectionSearchVisitor(EventSource session, boolean[] propertyVersionability) {
public DirtyCollectionSearchVisitor(Object entity, EventSource session, boolean[] propertyVersionability) {
super( session );
EnhancementAsProxyLazinessInterceptor interceptor = null;
if ( entity instanceof PersistentAttributeInterceptable ) {
if ( ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor() instanceof EnhancementAsProxyLazinessInterceptor ) {
interceptor = (EnhancementAsProxyLazinessInterceptor) ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor();
}
}
this.interceptor = interceptor;
this.propertyVersionability = propertyVersionability;
}
@ -45,6 +55,9 @@ public class DirtyCollectionSearchVisitor extends AbstractVisitor {
// return (ah==null) ? true : searchForDirtyCollections(ah, type);
}
else {
if ( interceptor != null && !interceptor.isAttributeLoaded( type.getName() ) ) {
return null;
}
// if not wrapped yet, its dirty (this can't occur, because
// we now always call wrap() before getting to here)
// return ( ! (obj instanceof PersistentCollection) ) ?

View File

@ -0,0 +1,166 @@
/*
* 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.bytecode.enhancement.lazy;
import java.util.HashSet;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.bytecode.enhance.spi.UnloadedClass;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.LoadEvent;
import org.hibernate.event.spi.LoadEventListener;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.CustomEnhancementContext;
import org.hibernate.testing.bytecode.enhancement.EnhancerTestContext;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Version;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
/**
* @author Christian Beikov
*/
@TestForIssue( jiraKey = "HHH-14619" )
@RunWith( BytecodeEnhancerRunner.class )
public class LazyProxyWithCollectionTest extends BaseCoreFunctionalTestCase {
private Long childId;
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[]{Parent.class, Child.class};
}
@Before
public void prepare() {
doInJPA( this::sessionFactory, em -> {
Child c = new Child();
em.persist( c );
childId = c.getId();
} );
}
@Test
public void testReference() {
doInJPA( this::sessionFactory, em -> {
Child child = em.getReference( Child.class, childId );
Parent parent = new Parent();
parent.child = child;
em.persist( parent );
// Class cast exception occurs during auto-flush
em.find( Parent.class, parent.getId() );
} );
}
@Test
public void testLazyCollection() {
doInJPA( this::sessionFactory, em -> {
Child child = em.find( Child.class, childId );
Parent parent = new Parent();
parent.child = child;
em.persist( parent );
child.children = new HashSet<>();
// Class cast exception occurs during auto-flush
em.find( Parent.class, parent.getId() );
} );
}
// --- //
@Entity
@Table( name = "PARENT" )
private static class Parent {
@Id
@GeneratedValue( strategy = GenerationType.AUTO )
Long id;
@OneToOne( fetch = FetchType.LAZY )
Child child;
public Long getId() {
return id;
}
public Child getChild() {
return child;
}
public void setChild(Child child) {
this.child = child;
}
}
@Entity
@Table( name = "CHILD" )
private static class Child {
@Id
@GeneratedValue( strategy = GenerationType.AUTO )
Long id;
@Version
Long version;
String name;
@OneToMany
Set<Child> children = new HashSet<>();
Child() {
// No-arg constructor necessary for proxy factory
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getVersion() {
return version;
}
public void setVersion(Long version) {
this.version = version;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Child> getChildren() {
return children;
}
public void setChildren(Set<Child> children) {
this.children = children;
}
}
}