You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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无法找到匹配的构造函数,以下是原因和解决办法:

问题原因

  1. JPA构造函数投影的类型匹配要求:JPQL的new表达式要求构造函数的参数类型必须与查询返回的JDBC类型直接匹配。你的查询返回的是Point类型,但AccountSummary的构造函数只接受Location类型,JPA无法自动关联你注册的TypeConverter,因此抛出找不到合适构造函数的异常。
  2. 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:

  1. 实现Hibernate的UserType或CompositeUserType,完成Point与Location的双向转换。
  2. 在实体类的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 09:41:20