Hibernate-enhance-maven-plugin 6.2.5.FINAL -> 6.4.4.FINAL changes behaviour for Kotlin val fields

Hi Folks,

I’m upgrading a Kotlin Based Spring Boot Service from 3.1 → 3.2.5 which bumps hibernate from 6.2.5.FINAL to 6.4.4.FINAL. We leverage the hibernate-enhance-maven-plugin plugin in order to support parent side OneToOne lazy loading. We are also leveraging the all-open plugin to make our Entity classes open. However, upon upgrading we noticed that the enhancement plugin has changed how it works and ends up breaking other lazy entity relationships.

For example, given the below entities, both the errorMetadata and batch associations are defined to use FetchType.LAZY. However if a lazily entity relationship is accessed by one of its val properties it does not trigger a database fetch, but when using a var it does.

After some investigation between hibernate versions, the cause seems to be this change which appears to not add the read interceptor for final fields and was added in Hibernate 6.4.1 based on the tags. Kotlin vals look to be compiled as Final

For example here is the decompiled Java Code post all-open and hibernate-enhance in Hibernate 6.4.1. We can see that a field defined as val in Kotlin does not have any of the interceptor code and thus does not make a database call when accessing lazily via a Hibernate Proxy

And if we look at the enhanced code in Hibernate 6.2.5 we can see that the read interceptor is still added for a field that was defined as a val in Kotlin. This in term does trigger a database fetch as expected

Was wondering if there a way around this behaviour change? It does work if we change vals to vars, but that is not ideal as we want immutability of the field within our Kotlin code. It would be better if we can get the same behaviour as under 6.2.5.FINAL.

val activity = activityRepository.findById(1).get();
activity.batch?.idempotencyKey // Does not trigger a fetch to the database and returns null.
activity.batch?.otherField // DOES trigger a fetch to the database

@Entity
class Activity(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false)
    var id: Long? = null,

    @OneToOne(mappedBy = "activity", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
    var errorMetadata: ErrorMetadata? = null,

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "batch_id")
    var batch: Batch? = null,
)

@Entity
class Batch(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(nullable = false)
    var id: Long? = null,

    @Column(nullable = false)
    val idempotencyKey: String,
    
    @Column(nullable = false)
    var otherField: String,
) 
<plugins>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <version>${kotlin.version}</version>
                <executions>
                    <execution>
                        <id>compile</id>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>test-compile</id>
                        <phase>test-compile</phase>
                        <goals>
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <compilerPlugins>
                        <plugin>jpa</plugin>
                        <plugin>spring</plugin>
                        <plugin>all-open</plugin>
                    </compilerPlugins>
                    <args>
                        <arg>-Xjsr305=strict</arg>
                    </args>
                    <pluginOptions>
                        <option>all-open:annotation=jakarta.persistence.Entity</option>
                        <option>all-open:annotation=jakarta.persistence.Embedded</option>
                        <option>all-open:annotation=jakarta.persistence.MappedSuperclass</option>
                    </pluginOptions>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-noarg</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>org.jetbrains.kotlin</groupId>
                        <artifactId>kotlin-maven-allopen</artifactId>
                        <version>${kotlin.version}</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
            <groupId>org.hibernate.orm.tooling</groupId>
            <artifactId>hibernate-enhance-maven-plugin</artifactId>
                <version>${hibernate.version}</version>
                <executions>
                    <execution>
                        <configuration>
                            <enableLazyInitialization>true</enableLazyInitialization>
                        </configuration>
                        <goals>
                            <goal>enhance</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
<plugins>

Thanks!

Hey,

Hibernate projects do not test with Kotlin, and from looking into some of the similar Kotlin-based reports we’ve seen it often mess things up :pensive:. With that said :smiley: is there any chance you could try to come up with a reproduced using this template. You probably can remove most of the additional configuration properties, the most important part is to keep the BytecodeEnhancerRunner so that bytecode enhancement is enabled for a test.

From what I can see about the all-open plugin, it makes properties non-final. Maybe you have a plugin execution problem? If the all-open is executed first and then the enhance plugin, then hibernate-orm/hibernate-core/src/main/java/org/hibernate/bytecode/enhance/internal/bytebuddy/FieldReaderAppender.java at 1d5f70ea5b0a0f342a237072c8caaf683ffe2ace · marko-bekhta/hibernate-orm · GitHub shouldn’t be final here, right?

Otherwise, modifying a final field in a method

public String $$_hibernate_read_idempotencyKey() {
     // ...
     this.idempotencyKey = smth ... 
     // ...
}

doesn’t make much sense :slightly_smiling_face:

Hey Marko!

Thanks for the quick reply on this. I can have a look at re-producing it with that template.

Another interesting this is that with and without all-open plugin the val properties themselves are final, and the all-open just appears to make method and class themself open. And post hibernate-enhance-maven-plugin the final actually gets stripped out there. So it seems like the hibernate-enhanced plugin might be removing the final part.

I’ll dive in more to see if this is is plugin execution issue.

Just with all-open

all-open + hibernate-enhance

Posting in two message since the forum only lets new users post 1 image per post