HHH-12107 - ClassCastException when using L2Cache with "structured_cache"=true

This commit is contained in:
Vlad Mihalcea 2017-11-28 17:25:00 +02:00
parent 63e4702629
commit 992fdbcf3c
3 changed files with 192 additions and 15 deletions

View File

@ -28,7 +28,7 @@ import org.hibernate.type.TypeHelper;
*/
public class StandardCacheEntryImpl implements CacheEntry {
private final Serializable[] disassembledState;
private final String disassembledStateText;
private String disassembledStateText;
private final Object version;
private final String subclass;
@ -66,8 +66,8 @@ public class StandardCacheEntryImpl implements CacheEntry {
this.version = version;
}
StandardCacheEntryImpl(Serializable[] state, String disassembledStateText, String subclass, Object version) {
this.disassembledState = state;
StandardCacheEntryImpl(Serializable[] disassembledState, String disassembledStateText, String subclass, Object version) {
this.disassembledState = disassembledState;
this.disassembledStateText = disassembledStateText;
this.subclass = subclass;
this.version = version;
@ -138,18 +138,26 @@ public class StandardCacheEntryImpl implements CacheEntry {
}
//assembled state gets put in a new array (we read from cache by value!)
final Object[] assembledProps = TypeHelper.assemble(
final Object[] state = TypeHelper.assemble(
disassembledState,
persister.getPropertyTypes(),
session, instance
);
if ( disassembledStateText == null ) {
disassembledStateText = TypeHelper.toLoggableString(
state,
persister.getPropertyTypes(),
session.getFactory()
);
}
//persister.setIdentifier(instance, id); //before calling interceptor, for consistency with normal load
//TODO: reuse the PreLoadEvent
final PreLoadEvent preLoadEvent = new PreLoadEvent( session )
.setEntity( instance )
.setState( assembledProps )
.setState( state )
.setId( id )
.setPersister( persister );
@ -162,9 +170,9 @@ public class StandardCacheEntryImpl implements CacheEntry {
listener.onPreLoad( preLoadEvent );
}
persister.setPropertyValues( instance, assembledProps );
persister.setPropertyValues( instance, state );
return assembledProps;
return state;
}
@Override

View File

@ -9,11 +9,9 @@ package org.hibernate.cache.spi.entry;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.type.TypeHelper;
/**
* Structured CacheEntry format for entities. Used to store the entry into the second-level cache
@ -45,15 +43,15 @@ public class StructuredCacheEntry implements CacheEntryStructure {
final Object version = map.get( VERSION_KEY );
final EntityPersister subclassPersister = factory.getEntityPersister( subclass );
final String[] names = subclassPersister.getPropertyNames();
final Serializable[] state = new Serializable[names.length];
final Serializable[] disassembledState = new Serializable[names.length];
for ( int i = 0; i < names.length; i++ ) {
state[i] = (Serializable) map.get( names[i] );
disassembledState[i] = (Serializable) map.get( names[i] );
}
return new StandardCacheEntryImpl(
state,
TypeHelper.toLoggableString( state, subclassPersister.getPropertyTypes(), factory ),
subclass,
version
disassembledState,
null,
subclass,
version
);
}

View File

@ -0,0 +1,171 @@
/*
* 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.querycache;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import org.hibernate.CacheMode;
import org.hibernate.Session;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* @author Vlad Mihalceca
*/
@TestForIssue( jiraKey = "HHH-12107" )
public class StructuredQueryCacheTest extends BaseNonConfigCoreFunctionalTestCase {
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] {
OneToManyWithEmbeddedId.class,
OneToManyWithEmbeddedIdChild.class,
OneToManyWithEmbeddedIdKey.class
};
}
@Override
protected void addSettings(Map settings) {
settings.put( AvailableSettings.USE_QUERY_CACHE, "true" );
settings.put( AvailableSettings.CACHE_REGION_PREFIX, "foo" );
settings.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, "true" );
settings.put( AvailableSettings.GENERATE_STATISTICS, "true" );
settings.put( AvailableSettings.USE_STRUCTURED_CACHE, "true" );
}
@Override
protected boolean isCleanupTestDataRequired() {
return true;
}
@Override
protected String getCacheConcurrencyStrategy() {
return "transactional";
}
@Test
@TestForIssue( jiraKey = "HHH-12107" )
public void testEmbeddedIdInOneToMany() {
OneToManyWithEmbeddedIdKey key = new OneToManyWithEmbeddedIdKey( 1234 );
final OneToManyWithEmbeddedId o = new OneToManyWithEmbeddedId( key );
o.setItems( new HashSet<>() );
o.getItems().add( new OneToManyWithEmbeddedIdChild( 1 ) );
doInHibernate( this::sessionFactory, session -> {
session.persist( o );
});
doInHibernate( this::sessionFactory, session -> {
OneToManyWithEmbeddedId _entity = session.find( OneToManyWithEmbeddedId.class, key );
assertTrue( session.getSessionFactory().getCache().containsEntity( OneToManyWithEmbeddedId.class, key ) );
assertNotNull( _entity );
});
doInHibernate( this::sessionFactory, session -> {
OneToManyWithEmbeddedId _entity = session.find( OneToManyWithEmbeddedId.class, key );
assertTrue( session.getSessionFactory().getCache().containsEntity( OneToManyWithEmbeddedId.class, key ) );
assertNotNull( _entity );
});
}
@Entity(name = "OneToManyWithEmbeddedId")
public static class OneToManyWithEmbeddedId {
private OneToManyWithEmbeddedIdKey id;
private Set<OneToManyWithEmbeddedIdChild> items = new HashSet<>( );
public OneToManyWithEmbeddedId() {
}
public OneToManyWithEmbeddedId(OneToManyWithEmbeddedIdKey id) {
this.id = id;
}
@EmbeddedId
public OneToManyWithEmbeddedIdKey getId() {
return id;
}
public void setId(OneToManyWithEmbeddedIdKey id) {
this.id = id;
}
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, targetEntity = OneToManyWithEmbeddedIdChild.class, orphanRemoval = true)
@JoinColumn(name = "parent_id")
public Set<OneToManyWithEmbeddedIdChild> getItems() {
return items;
}
public void setItems(Set<OneToManyWithEmbeddedIdChild> items) {
this.items = items;
}
}
@Entity(name = "OneToManyWithEmbeddedIdChild")
public static class OneToManyWithEmbeddedIdChild {
private Integer id;
public OneToManyWithEmbeddedIdChild() {
}
public OneToManyWithEmbeddedIdChild(Integer id) {
this.id = id;
}
@Id
@Column(name = "id")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
@Embeddable
public static class OneToManyWithEmbeddedIdKey implements Serializable {
private Integer id;
public OneToManyWithEmbeddedIdKey() {
}
public OneToManyWithEmbeddedIdKey(Integer id) {
this.id = id;
}
@Column(name = "id")
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
}
}