HHH-13759 - Support for private Embedded in MappedSuperclass

This commit is contained in:
Andrea Boriero 2019-11-26 19:07:23 +00:00 committed by Sanne Grinovero
parent c4dc9b0d25
commit 0dea8319d4
4 changed files with 196 additions and 9 deletions

View File

@ -26,6 +26,14 @@ import org.hibernate.engine.spi.ExtendedSelfDirtinessTracker;
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.bytecode.StackManipulation;
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.member.MethodInvocation;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.jar.asm.Opcodes;
class CodeTemplates {
@ -510,4 +518,26 @@ class CodeTemplates {
@interface MappedBy {
}
// mapping to get private field from superclass by calling the enhanced reader, for use when field is not visible
static class GetterMapping implements Advice.OffsetMapping {
private final FieldDescription persistentField;
GetterMapping(FieldDescription persistentField) {
this.persistentField = persistentField;
}
@Override public Target resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod, Assigner assigner, Advice.ArgumentHandler argumentHandler, Sort sort) {
MethodDescription.Token signature = new MethodDescription.Token( EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName(), Opcodes.ACC_PUBLIC, persistentField.getType() );
MethodDescription method = new MethodDescription.Latent( instrumentedType.getSuperClass().asErasure(), signature );
return new Target.AbstractReadOnlyAdapter() {
@Override
public StackManipulation resolveRead() {
return new StackManipulation.Compound( MethodVariableAccess.loadThis(), MethodInvocation.invoke( method ).special( method.getDeclaringType().asErasure() ) );
}
};
}
}
}

View File

@ -64,8 +64,13 @@ final class InlineDirtyCheckingHandler implements Implementation, ByteCodeAppend
if ( enhancementContext.isCompositeClass( persistentField.getType().asErasure() )
&& persistentField.hasAnnotation( Embedded.class ) ) {
implementation = Advice.withCustomMapping()
.bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() )
// HHH-13759 - Call getter on superclass if field is not visible
Advice.WithCustomMapping advice = Advice.withCustomMapping();
advice = persistentField.isVisibleTo( managedCtClass )
? advice.bind( CodeTemplates.FieldValue.class, persistentField.getFieldDescription() )
: advice.bind( CodeTemplates.FieldValue.class, new CodeTemplates.GetterMapping( persistentField.getFieldDescription() ) );
implementation = advice
.bind( CodeTemplates.FieldName.class, persistentField.getName() )
.to( CodeTemplates.CompositeFieldDirtyCheckingHandler.class )
.wrap( implementation );

View File

@ -520,26 +520,30 @@ public class PersistentAttributesEnhancer extends EnhancerImpl {
// make sure to add the CompositeOwner interface
addCompositeOwnerInterface( managedCtClass );
String readFragment = persistentField.visibleFrom( managedCtClass ) ? persistentField.getName() : "super." + EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + persistentField.getName() + "()";
// cleanup previous owner
fieldWriter.insertBefore(
String.format(
"if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n",
persistentField.getName(),
"if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%4$s\"); }%n",
readFragment,
CompositeTracker.class.getName(),
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER,
persistentField.getName()
)
);
// trigger track changes
fieldWriter.insertAfter(
String.format(
"if (%1$s != null) { ((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this); }%n" +
"%5$s(\"%1$s\");",
persistentField.getName(),
"if (%1$s != null) { ((%2$s) %1$s).%4$s(\"%6$s\", (%3$s) this); }%n" +
"%5$s(\"%6$s\");",
readFragment,
CompositeTracker.class.getName(),
CompositeOwner.class.getName(),
EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
EnhancerConstants.TRACKER_CHANGER_NAME
EnhancerConstants.TRACKER_CHANGER_NAME,
persistentField.getName()
)
);
}

View File

@ -0,0 +1,148 @@
/*
* 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.proxy;
import java.io.Serializable;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
import org.hibernate.testing.bytecode.enhancement.EnhancementOptions;
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertThat;
/**
* @author Andrea Boriero
*/
@RunWith(BytecodeEnhancerRunner.class)
@EnhancementOptions(lazyLoading = true, inlineDirtyChecking = true)
public class MappedSuperclassWithEmbeddableTest extends BaseNonConfigCoreFunctionalTestCase {
@Override
protected void configureStandardServiceRegistryBuilder(StandardServiceRegistryBuilder ssrb) {
super.configureStandardServiceRegistryBuilder( ssrb );
ssrb.applySetting( AvailableSettings.ALLOW_ENHANCEMENT_AS_PROXY, "true" );
ssrb.applySetting( AvailableSettings.FORMAT_SQL, "false" );
ssrb.applySetting( AvailableSettings.USE_SECOND_LEVEL_CACHE, "false" );
ssrb.applySetting( AvailableSettings.GENERATE_STATISTICS, "true" );
}
@Override
public Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { TestEntity.class };
}
@Before
public void prepare() {
doInHibernate( this::sessionFactory, s -> {
TestEntity testEntity = new TestEntity( "2", "test" );
s.persist( testEntity );
} );
}
@After
public void tearDown() {
doInHibernate( this::sessionFactory, s -> {
s.createQuery( "delete from TestEntity" ).executeUpdate();
} );
}
@Test
public void testIt() {
doInHibernate( this::sessionFactory, s -> {
TestEntity testEntity = s.get( TestEntity.class, "2" );
assertThat( testEntity, notNullValue() );
} );
}
@MappedSuperclass
public static abstract class BaseEntity {
@Embedded
private EmbeddedValue superField;
public EmbeddedValue getSuperField() {
return superField;
}
public void setSuperField(EmbeddedValue superField) {
this.superField = superField;
}
}
@Embeddable
public static class EmbeddedValue implements Serializable {
@Column(name = "super_field")
private String superField;
public EmbeddedValue() {
}
private EmbeddedValue(String superField) {
this.superField = superField;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
EmbeddedValue that = (EmbeddedValue) o;
return superField.equals( that.superField );
}
@Override
public int hashCode() {
return Objects.hash( superField );
}
}
@Entity(name = "TestEntity")
public static class TestEntity extends BaseEntity {
@Id
private String id;
private String name;
public TestEntity() {
}
private TestEntity(String id, String name) {
this.id = id;
this.name = name;
EmbeddedValue value = new EmbeddedValue( "SUPER " + name );
setSuperField( value );
}
public String id() {
return id;
}
public String name() {
return name;
}
}
}