HHH-13759 - Support for private Embedded in MappedSuperclass
This commit is contained in:
parent
c4dc9b0d25
commit
0dea8319d4
|
@ -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() ) );
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -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()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue