调用persist()时Hibernate 6转JPA PersistenceException错误排查
问题描述
使用Ktor 2.2.1(Kotlin 1.8)+ Hibernate 6.1.6.Final开发Web应用,将实体保存至H2内存数据库(v2.1.214)时,调用session.persist()触发以下错误:
Exception in thread "main" jakarta.persistence.PersistenceException: Converting `org.hibernate.property.access.spi.PropertyAccessException` to JPA `PersistenceException` : Error accessing field [private java.lang.Long com.example.models.jpa.UserDto.id] by reflection for persistent property [com.example.models.jpa.UserDto#id] : com.example.models.jpa.UserDto@32ba5c65 at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:165) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:175) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:182) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:743) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:721) at com.example.models.H2DatabaseFactory.init(H2DatabaseFactory.kt:27) at com.example.ApplicationKt.module(Application.kt:11) // 省略中间栈帧 Caused by: org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [private java.lang.Long com.example.models.jpa.UserDto.id] by reflection for persistent property [com.example.models.jpa.UserDto#id] : com.example.models.jpa.UserDto@32ba5c65 at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:48) // 省略中间栈帧 Caused by: java.lang.IllegalArgumentException: Can not set java.lang.Long field com.example.models.jpa.UserDto.id to com.example.models.jpa.UserDto at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167) at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171) at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58) at java.base/jdk.internal.reflect.UnsafeObjectFieldAccessorImpl.get(UnsafeObjectFieldAccessorImpl.java:36) at java.base/java.lang.reflect.Field.get(Field.java:418) at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:44) // 省略后续栈帧
相关业务代码:
import org.hibernate.cfg.Configuration val sessionFactory = Configuration() .addAnnotatedClass(UserDto::class.java) .buildSessionFactory() val session = sessionFactory.openSession() session.beginTransaction() var userDto = UserDto() userDto.username = "admin" session.persist(userDto) // 错误触发点 session.transaction.commit()
UserDto实体类:
package com.example.models.jpa import jakarta.persistence.* @Entity open class UserDto { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) open var id: Long? = null @Column(nullable = false) open var username: String = "" }
查询功能(session.createQuery("select u from UserDto u", UserDto::class.java).list())可正常执行,但保存操作失败。
build.gradle.kts核心配置:
plugins { kotlin("jvm") version "1.8.0" id("io.ktor.plugin") version "2.2.1" id("org.jetbrains.kotlin.plugin.allopen") version "1.8.0" id("org.jetbrains.kotlin.plugin.noarg") version "1.8.0" id("org.jetbrains.kotlin.plugin.jpa") version "1.8.0" } allOpen { annotation("javax.persistence.Entity") annotation("javax.persistence.MappedSuperclass") annotation("javax.persistence.Embeddable") annotation("jakarta.persistence.Entity") annotation("jakarta.persistence.MappedSuperclass") annotation("jakarta.persistence.Embeddable") } dependencies { // Ktor相关依赖省略 // 数据库依赖 implementation("mysql:mysql-connector-java:8.0.30") implementation("org.hibernate:hibernate-core:6.1.6.Final") implementation("com.h2database:h2:$h2_version") }
application.yml配置:
ktor: application: modules: - com.example.ApplicationKt.module development: true deployment: port: 8080 watch: - classes - resources jwt: domain: "https://jwt-provider-domain/" audience: "jwt-audience" realm: "ktor sample app"
问题原因
核心错误Can not set java.lang.Long field com.example.models.jpa.UserDto.id to com.example.models.jpa.UserDto表明:Hibernate通过反射访问id字段时出现类型匹配异常。根源在于Kotlin字段的open修饰符与Hibernate代理生成机制冲突,且属性访问策略未适配Kotlin语法特性。
解决方法
1. 移除实体类字段的open修饰符
Hibernate仅要求实体类本身为open(由allOpen插件自动处理),字段无需额外标记open,否则会干扰代理生成时的反射逻辑:
package com.example.models.jpa import jakarta.persistence.* @Entity open class UserDto { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) var id: Long? = null // 移除open修饰符 @Column(nullable = false) var username: String = "" // 移除open修饰符 }
2. 显式指定属性访问策略
在@Entity注解上添加@Access(AccessType.FIELD),强制Hibernate直接访问字段,避免Kotlin getter/setter带来的反射问题:
@Entity @Access(AccessType.FIELD) open class UserDto { // 字段代码不变 }
3. 配置noarg插件适配Jakarta注解
在build.gradle.kts中明确指定noarg插件处理Jakarta的@Entity注解,确保无参构造函数正确生成:
noarg { annotation("jakarta.persistence.Entity") }
4. 完善Hibernate数据库配置
确保Configuration包含完整的H2数据库连接参数,避免因配置缺失导致的隐性错误:
val sessionFactory = Configuration() .addAnnotatedClass(UserDto::class.java) // H2数据库核心配置 .setProperty("hibernate.connection.driver_class", "org.h2.Driver") .setProperty("hibernate.connection.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1") .setProperty("hibernate.connection.username", "sa") .setProperty("hibernate.connection.password", "") .setProperty("hibernate.dialect", "org.hibernate.dialect.H2Dialect") .setProperty("hibernate.hbm2ddl.auto", "update") // 自动创建/更新表结构 .buildSessionFactory()
内容的提问来源于stack exchange,提问作者capo11

