Kotlin JPA实体类加open修饰符引发懒加载异常问题咨询
我之前了解到Kotlin的JPA Entity需要添加open修饰符,于是基于spring-boot-starter-data-jpa:3.0.4写了下面的代码:
@Entity open class Test { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) open val id: Long? = null } @Repository interface TestRepository: JpaRepository<Test, Long> { } @Component class Service(private val testRepository: TestRepository){ @PostConstruct fun init() { val data = testRepository.getReferenceById(1) data.getId() } }
但调用后抛出了Hibernate could not initialize proxy – no Session异常,给类或方法加@Transactional注解也解决不了问题,只有两种方式能修复:
- 移除Test类的
open修饰符,修改后代码:
@Entity class Test { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) val id: Long? = null }
- 在配置中设置
enable_lazy_load_no_trans=true
我想搞清楚:为什么加open会引发这个异常?这和“JPA Entity需要open”的推荐做法矛盾的原因是什么?
补充细节
pom.xml中的插件配置:
<plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <configuration> <jvmTarget>19</jvmTarget> <args> <arg>-Xjsr305=strict</arg> </args> <compilerPlugins> <plugin>spring</plugin> <plugin>jpa</plugin> </compilerPlugins> </configuration> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-allopen</artifactId> <version>${kotlin.version}</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-noarg</artifactId> <version>${kotlin.version}</version> </dependency> </dependencies> </plugin> </plugins>
错误堆栈(敏感信息已掩码):
Caused by: org.hibernate.LazyInitializationException: could not initialize proxy [xxx.jpa.entity.Test#55] - no Session at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:164) at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309) at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:44) at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:102) at xxx.jpa.entity.Test$HibernateProxy$bI6oMG8J.getId(Unknown Source) at xxx.xxx$lambda$0(xxx.kt:43) at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.invokeConsumer(SimpleFunctionRegistry.java:973) at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.doApply(SimpleFunctionRegistry.java:684) at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.apply(SimpleFunctionRegistry.java:533) at org.springframework.cloud.stream.function.PartitionAwareFunctionWrapper.apply(PartitionAwareFunctionWrapper.java:84) at org.springframework.cloud.stream.function.FunctionConfiguration$FunctionWrapper.apply(FunctionConfiguration.java:785) at org.springframework.cloud.stream.function.FunctionConfiguration$FunctionToDestinationBinder$1.handleMessageInternal(FunctionConfiguration.java:621) at org.springframework.integration.handler.AbstractMessageHandler.doHandleMessage(AbstractMessageHandler.java:105) ... 33 more
问题原因分析
1. Kotlin类的final特性与Hibernate代理的关系
Kotlin默认所有类和成员方法/属性都是final的,而Hibernate实现懒加载的核心是生成实体类的代理子类(通过ByteBuddy或CGLIB),这要求实体类必须是可继承的(即open)。
- 当你手动添加
open修饰符:Test类变成可继承,Hibernate会为它生成懒加载代理。getReferenceById()方法返回的是这个代理对象,此时并没有执行SQL查询加载真实数据,只有当你调用代理的方法(比如getId())时,才会触发数据库查询。 - 当你移除
open:Test类是final的,Hibernate无法生成代理子类,此时getReferenceById()会直接执行SQL查询,返回真实的Test实体对象,自然不会有懒加载初始化的问题。
2. @PostConstruct方法的事务问题
你尝试添加@Transactional注解但无效,核心原因是**@PostConstruct的执行时机**:这个方法是在Spring Bean初始化完成后立即执行的,此时Spring的事务代理还未完全生效(Bean代理是在初始化之后创建的),所以@Transactional注解无法为该方法开启事务,也就没有可用的Hibernate Session。
当你调用代理对象的getId()时,由于没有Session,Hibernate无法初始化代理,就抛出了LazyInitializationException。
3. 与“推荐加open”的矛盾点
所谓“JPA Entity需要open”的推荐做法,其实是指让Hibernate能够生成代理实现懒加载,但你已经在pom.xml中配置了Kotlin的jpa编译器插件(配合kotlin-maven-allopen依赖)——这个插件会自动为带有@Entity注解的类和成员添加open修饰符,你手动加open完全是多余的。
手动加open本身不是问题,问题出在@PostConstruct方法的执行环境没有Session,而open类让Hibernate用了懒加载代理,才触发了异常。
正确的解决方案建议
除了你提到的两种修复方式,更合理的做法是:
- 保留Kotlin的
jpa插件,不要手动加open,让插件自动处理实体类的可继承性。 - 把初始化逻辑从
@PostConstruct移到应用启动完成后的回调中,比如实现ApplicationListener<ApplicationReadyEvent>,并在回调方法上添加@Transactional,确保有可用的Session:
@Component class Service(private val testRepository: TestRepository): ApplicationListener<ApplicationReadyEvent> { @Transactional override fun onApplicationEvent(event: ApplicationReadyEvent) { val data = testRepository.getReferenceById(1) data.getId() } }
这样既符合JPA实体类的要求,又能避免懒加载初始化异常。
内容的提问来源于stack exchange,提问作者user3908406

