From 992fdbcf3c76b73a54198b666daf34849b6a59d4 Mon Sep 17 00:00:00 2001 From: Vlad Mihalcea Date: Tue, 28 Nov 2017 17:25:00 +0200 Subject: [PATCH] HHH-12107 - ClassCastException when using L2Cache with "structured_cache"=true --- .../spi/entry/StandardCacheEntryImpl.java | 22 ++- .../cache/spi/entry/StructuredCacheEntry.java | 14 +- .../querycache/StructuredQueryCacheTest.java | 171 ++++++++++++++++++ 3 files changed, 192 insertions(+), 15 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/querycache/StructuredQueryCacheTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java index 536a40555cdb..7fc6b4c282db 100644 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StandardCacheEntryImpl.java @@ -28,7 +28,7 @@ */ 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 StandardCacheEntryImpl( 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 Object[] assemble( } //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 Object[] assemble( listener.onPreLoad( preLoadEvent ); } - persister.setPropertyValues( instance, assembledProps ); + persister.setPropertyValues( instance, state ); - return assembledProps; + return state; } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java index 3fe74701327d..e334e5f01e0b 100755 --- a/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java +++ b/hibernate-core/src/main/java/org/hibernate/cache/spi/entry/StructuredCacheEntry.java @@ -9,11 +9,9 @@ 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 Object destructure(Object structured, SessionFactoryImplementor factory) 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 ); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/querycache/StructuredQueryCacheTest.java b/hibernate-core/src/test/java/org/hibernate/test/querycache/StructuredQueryCacheTest.java new file mode 100644 index 000000000000..1dac7cdf1666 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/querycache/StructuredQueryCacheTest.java @@ -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 . + */ +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 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 getItems() { + return items; + } + + public void setItems(Set 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; + } + } +} \ No newline at end of file