9

I ve got the following object (simplified):

@Entity
@Table(name = "delivery_addresses")
data class DeliveryAddress (
        val street: String
) {

    @ManyToOne(fetch=FetchType.LAZY)
    lateinit var user: User
}

and when I query the object by ID

val deliveryAddress = deliveryAddressService.findById(1)

which does

override fun findById(deliveryAddressId: Long): DeliveryAddress? {
    return deliveryAddressRepository.findById(deliveryAddressId).orElse(null) // JpaRepository
}

I can see the following queries are executed:

select deliveryad0_.street as street6_2_0_, deliveryad0_.user_id as user_id8_2_0_, from delivery_addresses deliveryad0_ where deliveryad0_.id=?

select user0_.id as id1_5_0_, user0_.email as email2_5_0_, user0_.password as password3_5_0_, where user0_.id=?

How can I make FetchType.LAZY work as excepted (also @Basic(fetch = FetchType.LAZY) is not working for me)?

6
  • Usually when the field isn't lazy - you get a join. In your logs you have a separate select. So you probably access the field somewhere and that's why it's loaded. E.g. it could be used in toString() or if you stop in debug and see variables - IDE accesses the fields to show you the values and this may also load the lazy association. Or maybe Kotlin does something that you don't expect (I don't know Kotlin so can't comment on what exactly). And you certainly don't need bytecode enhancement for this. Commented Jun 4, 2020 at 15:48
  • Both Queries are executed inside JdkDynamicAopProxy.java, so it doesnt seem so.
    – mleister
    Commented Jun 4, 2020 at 16:07
  • They are both called here: MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); retVal = invocation.proceed();
    – mleister
    Commented Jun 4, 2020 at 16:09
  • Well, this doesn't tell us much.. It's a generic thing and it's the method does the work. BTW, you can set property hibernate.use_sql_comments=true to the EntitManagerFactory, maybe it will tell you why it's loading the association. Another thing - you can set a breakpoint in your getUser() method or in every method of the User to figure out which code accesses it. Commented Jun 4, 2020 at 16:23
  • Is one of toString/equals/hashCode of DeliveryAddress relying on user? Might be a "hidden usage" of the field that triggers the query.
    – sp00m
    Commented Jun 4, 2020 at 16:39

1 Answer 1

9

Finally I was able to figure it out. The issue is related to kotlin data classes (see KT-28525)

One should not use data classes with spring-data-jpa. Referenced from kotlin spring-guide

Here we don’t use data classes with val properties because JPA is not designed to work with immutable classes or the methods generated automatically by data classes. If you are using other Spring Data flavor, most of them are designed to support such constructs so you should use classes like data class User(val login: String, …​) when using Spring Data MongoDB, Spring Data JDBC, etc.

In order to make lazy fetching working as expected, entities should be open. My solution:

build.gradle.kts:

plugins {
  kotlin("plugin.allopen") version "1.3.61"
}

allOpen {
  annotation("javax.persistence.Entity")
  annotation("javax.persistence.Embeddable")
  annotation("javax.persistence.MappedSuperclass")
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.