diff --git a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java index 56b12d8eac..dabf4f5643 100644 --- a/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java +++ b/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java @@ -54,55 +54,64 @@ abstract class FieldReaderAppender implements ByteCodeAppender { TypeDescription dispatcherType = persistentFieldAsDefined.getType().isPrimitive() ? persistentFieldAsDefined.getType().asErasure() : TypeDescription.OBJECT; - // if ( this.$$_hibernate_getInterceptor() != null ) - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - methodVisitor.visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - managedCtClass.getInternalName(), - EnhancerConstants.INTERCEPTOR_GETTER_NAME, - Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ), - false - ); - Label skip = new Label(); - methodVisitor.visitJumpInsn( Opcodes.IFNULL, skip ); - // this (for field write) - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - // this.$$_hibernate_getInterceptor(); - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - methodVisitor.visitMethodInsn( - Opcodes.INVOKEVIRTUAL, - managedCtClass.getInternalName(), - EnhancerConstants.INTERCEPTOR_GETTER_NAME, - Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ), - false - ); - // .readXXX( self, fieldName, field ); - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - methodVisitor.visitLdcInsn( persistentFieldAsDefined.getName() ); - methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); - fieldRead( methodVisitor ); - methodVisitor.visitMethodInsn( - Opcodes.INVOKEINTERFACE, - Type.getInternalName( PersistentAttributeInterceptor.class ), - "read" + EnhancerImpl.capitalize( dispatcherType.getSimpleName() ), - Type.getMethodDescriptor( - Type.getType( dispatcherType.getDescriptor() ), - Type.getType( Object.class ), - Type.getType( String.class ), - Type.getType( dispatcherType.getDescriptor() ) - ), - true - ); - // field = (cast) XXX - if ( !dispatcherType.isPrimitive() ) { - methodVisitor.visitTypeInsn( Opcodes.CHECKCAST, persistentFieldAsDefined.getType().asErasure().getInternalName() ); - } - fieldWrite( methodVisitor ); - // end if - methodVisitor.visitLabel( skip ); - if ( implementationContext.getClassFileVersion().isAtLeast( ClassFileVersion.JAVA_V6 ) ) { - methodVisitor.visitFrame( Opcodes.F_SAME, 0, null, 0, null ); + // From `PersistentAttributeTransformer`: + // Final fields will only be written to from the constructor, + // so there's no point trying to replace final field writes with a method call. + // as a result if a field is final then there will be no write method, and we don't want to have this block: + if ( !persistentField.asDefined().isFinal() ) { + // if ( this.$$_hibernate_getInterceptor() != null ) + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + managedCtClass.getInternalName(), + EnhancerConstants.INTERCEPTOR_GETTER_NAME, + Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ), + false + ); + Label skip = new Label(); + methodVisitor.visitJumpInsn( Opcodes.IFNULL, skip ); + // this (for field write) + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + // this.$$_hibernate_getInterceptor(); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + managedCtClass.getInternalName(), + EnhancerConstants.INTERCEPTOR_GETTER_NAME, + Type.getMethodDescriptor( Type.getType( PersistentAttributeInterceptor.class ) ), + false + ); + // .readXXX( self, fieldName, field ); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitLdcInsn( persistentFieldAsDefined.getName() ); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + + fieldRead( methodVisitor ); + methodVisitor.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + Type.getInternalName( PersistentAttributeInterceptor.class ), + "read" + EnhancerImpl.capitalize( dispatcherType.getSimpleName() ), + Type.getMethodDescriptor( + Type.getType( dispatcherType.getDescriptor() ), + Type.getType( Object.class ), + Type.getType( String.class ), + Type.getType( dispatcherType.getDescriptor() ) + ), + true + ); + // field = (cast) XXX + if ( !dispatcherType.isPrimitive() ) { + methodVisitor.visitTypeInsn( + Opcodes.CHECKCAST, persistentFieldAsDefined.getType().asErasure().getInternalName() ); + } + fieldWrite( methodVisitor ); + // end if + methodVisitor.visitLabel( skip ); + if ( implementationContext.getClassFileVersion().isAtLeast( ClassFileVersion.JAVA_V6 ) ) { + methodVisitor.visitFrame( Opcodes.F_SAME, 0, null, 0, null ); + } } + // return field methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); fieldRead( methodVisitor ); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/superclass/MappedSuperclassTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/superclass/MappedSuperclassTest.java new file mode 100644 index 0000000000..ca18452d23 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/bytecode/enhancement/superclass/MappedSuperclassTest.java @@ -0,0 +1,74 @@ +package org.hibernate.orm.test.bytecode.enhancement.superclass; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +import java.time.LocalDateTime; + +import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; + +@JiraKey("HHH-17418") +@RunWith(BytecodeEnhancerRunner.class) +public class MappedSuperclassTest extends BaseCoreFunctionalTestCase { + private static final LocalDateTime TEST_DATE_UPDATED_VALUE = LocalDateTime.of( 2023, 11, 10, 0, 0 ); + private static final long TEST_ID = 1L; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { MyEntity.class }; + } + + @Before + public void prepare() { + doInHibernate( this::sessionFactory, s -> { + MyEntity testEntity = new MyEntity(); + testEntity.id = TEST_ID; + s.persist( testEntity ); + } ); + } + + @Test + public void test() { + doInHibernate( this::sessionFactory, s -> { + MyEntity testEntity = s.get( MyEntity.class, TEST_ID ); + assertThat( testEntity.value() ).isEqualTo( TEST_DATE_UPDATED_VALUE ); + } ); + } + + @After + public void cleanup() { + doInHibernate( this::sessionFactory, s -> { + MyEntity testEntity = s.get( MyEntity.class, TEST_ID ); + s.remove( testEntity ); + } ); + } + + + @MappedSuperclass + public static class MappedBase { + // field is private on purpose so that enhancer will not use field access + @Column + private final LocalDateTime updated = TEST_DATE_UPDATED_VALUE; + + public LocalDateTime value() { + return updated; + } + } + + @Entity + public static class MyEntity extends MappedBase { + @Id + Long id; + } +}