Micronaut DTO投影类型转换问题:构造函数匹配失败
问题:JPA构造函数投影中实现Point到自定义Location类型转换的可行性
我想通过JPA构造函数表达式执行DTO投影查询,将AccountEntity中的Point类型地理坐标转换为自定义Location类型,已经注册了Geometry到Location的自定义TypeConverter,但运行时抛出“unable to locate appropriate constructor on class AccountSummary”异常,请问这个需求是否可行?
实体模型代码
@Entity(name = "Accounts") open class AccountEntity( @field:Id @field:GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "accounts_sequence_generator") @field:SequenceGenerator(name = "accounts_sequence_generator", sequenceName = "sequence_accounts") @field:Column(name = "id", nullable = false, unique = true) open var id: Long? = null, @field:[NotBlank Size(min = 2, max = 255, message = "username must be between 2 and 255 characters long")] @field:Column(name = "username", nullable = false, unique = true) open var username: String, @field:Embedded open var address: Address? ) @Embeddable data class Address( @field:Embedded val geolocation: Geolocation ) @Embeddable data class Geolocation( @field:Column(name = "geolocation", columnDefinition = "geography(POINT,4326)") val geolocation: Point )
查询代码
val query = entityManager.createQuery( "select new org.example.dto.AccountSummary(acc.address.geolocation.geolocation, acc.id, acc.username) from Accounts acc", AccountSummary::class.java ) return query.resultList
AccountSummary类
@Introspected data class AccountSummary( val point: Location, val id: Long, val username: String )
自定义转换器代码
@Singleton class GeometryLocationConverter : TypeConverter<Geometry, Location> { override fun convert(`object`: Geometry?, targetType: Class<Location>?, context: ConversionContext?): Optional<Location> { return when (`object`) { is Point -> Optional.of(Location(`object`.y, `object`.x)) else -> throw Exception("unknown geometry type") } } }
解答
这个需求完全可行,但当前实现方式存在问题,导致JPA无法找到匹配的构造函数,以下是原因和解决办法:
问题原因
- JPA构造函数投影的类型匹配要求:JPQL的
new表达式要求构造函数的参数类型必须与查询返回的JDBC类型直接匹配。你的查询返回的是Point类型,但AccountSummary的构造函数只接受Location类型,JPA无法自动关联你注册的TypeConverter,因此抛出找不到合适构造函数的异常。 - TypeConverter的作用范围限制:你注册的
GeometryLocationConverter属于框架(比如Micronaut)层面的类型转换组件,JPA本身不会在构造函数投影过程中自动触发这个转换逻辑。
可行的解决办法
方法1:给AccountSummary添加兼容Point类型的构造函数
在AccountSummary中新增一个接受Point参数的构造函数,在内部手动调用转换器完成类型转换:
@Introspected data class AccountSummary( val point: Location, val id: Long, val username: String ) { // 供JPA构造函数投影调用的兼容构造函数 constructor(point: Point, id: Long, username: String) : this( GeometryLocationConverter().convert(point, Location::class.java, null).get(), id, username ) }
如果转换器是通过依赖注入管理的(比如Micronaut的@Singleton),可以考虑将转换器注入到DTO,或者通过静态工具类封装转换逻辑,避免在构造函数中直接实例化转换器。
方法2:先查询中间结果再批量转换
先查询出包含Point类型的临时DTO,再通过转换器批量转换为目标AccountSummary:
// 定义临时DTO,用于接收JPA查询结果 data class TempAccountSummary(val point: Point, val id: Long, val username: String) // 执行查询 val tempResult = entityManager.createQuery( "select new org.example.dto.TempAccountSummary(acc.address.geolocation.geolocation, acc.id, acc.username) from Accounts acc", TempAccountSummary::class.java ).resultList // 调用转换器完成批量转换 return tempResult.map { temp -> AccountSummary( geometryLocationConverter.convert(temp.point, Location::class.java, null).get(), temp.id, temp.username ) }
这种方式更灵活,也能更好地利用依赖注入的转换器实例。
方法3:使用Hibernate自定义类型(若用Hibernate作为JPA实现)
直接将数据库的POINT类型映射为自定义的Location类型,让JPA查询时直接返回Location:
- 实现Hibernate的
UserType或CompositeUserType,完成Point与Location的双向转换。 - 在实体类的
geolocation字段上添加@Type注解,指定自定义类型:
@Embeddable data class Geolocation( @field:Column(name = "geolocation", columnDefinition = "geography(POINT,4326)") @field:Type(type = "org.example.converter.LocationUserType") val geolocation: Location )
这样JPQL查询时返回的就是Location类型,构造函数投影可以直接匹配AccountSummary的构造函数,无需额外转换。
内容的提问来源于stack exchange,提问作者JerryThePineapple
相关产品推荐
相关产品推荐

