如何在Micronaut Data(JDBC)中获取关联实体的主键?
问题:Micronaut Data JDBC中如何在投影DTO中包含关联实体的主键?
背景
我正在使用Micronaut Data搭配micronaut-jdbc-hikari驱动(本质为JDBC),采用4.5.0版本的平台BOM。需求如下:
- 两个实体类为
ManyToOne关联关系 - 获取实体的投影DTO,仅需包含关联实体的主键(已知Micronaut Data不支持嵌套投影,此限制可接受)
相关代码示例
Transaction实体类
@MappedEntity public record Transaction( @NonNull @NotNull @NotBlank @Id String id, @NonNull @NotNull @NotBlank LocalDate madeOn, @NonNull @NotNull @NotBlank @Size(max = 3) String currencyCode, @NotNull @NonNull @NotBlank String category, @NotNull @NonNull @NotBlank Boolean duplicated, @NotNull @NonNull @NotBlank LocalDateTime createdAt, @NotNull @NonNull @NotBlank LocalDateTime updatedAt, @Nullable @Relation(value = Kind.MANY_TO_ONE) Account account ) { }
Account实体类
@MappedEntity public record Account( @NonNull @NotNull @NotBlank @Id String id, @NonNull @NotNull @NotBlank String name, @NonNull @NotNull @NotBlank @Size(max = 3) String currencyCode, @NonNull @NotNull @NotBlank LocalDateTime createdAt, @NonNull @NotNull @NotBlank LocalDateTime updatedAt ) { }
目标投影DTO
@Introspected @Serdeable public record TransactionDTO( String id, String mode, LocalDate madeOn, String accountId // 此字段无法正常映射,导致编译报错 ) { }
Repository接口
@JdbcRepository(dialect = Dialect.POSTGRES) public interface TransactionRepository extends PageableRepository<Transaction, String> { Page<TransactionDTO> listByAccountInListAndMadeOnBetweenOrderByMadeOnDesc( List<Account> accounts, LocalDate from, LocalDate to, Pageable pageable ); }
上述代码编译失败,提示accountId是无效字段,因为Micronaut Data无法自动关联Transaction.account.id到DTO的accountId字段。
解决方案
方法1:使用@Query显式指定字段映射
在Repository方法上添加@Query注解,通过JPQL明确指定要查询的字段,并将关联主键别名设置为DTO中的accountId:
@JdbcRepository(dialect = Dialect.POSTGRES) public interface TransactionRepository extends PageableRepository<Transaction, String> { @Query("SELECT t.id, t.mode, t.madeOn, t.account.id AS accountId " + "FROM Transaction t " + "WHERE t.account IN :accounts AND t.madeOn BETWEEN :from AND :to " + "ORDER BY t.madeOn DESC") Page<TransactionDTO> listByAccountInListAndMadeOnBetweenOrderByMadeOnDesc( List<Account> accounts, LocalDate from, LocalDate to, Pageable pageable ); }
注意:JPQL中的别名accountId必须与DTO的字段名完全一致,Micronaut Data会自动完成映射。
方法2:在实体类中添加派生字段
在Transaction实体中添加一个派生的accountId字段,通过@Property注解指定其映射来源为关联实体的主键,同时用@Transient标记该字段不会生成数据库列:
@MappedEntity public record Transaction( @NonNull @NotNull @NotBlank @Id String id, @NonNull @NotNull @NotBlank LocalDate madeOn, @NonNull @NotNull @NotBlank @Size(max = 3) String currencyCode, @NotNull @NonNull @NotBlank String category, @NotNull @NonNull @NotBlank Boolean duplicated, @NotNull @NonNull @NotBlank LocalDateTime createdAt, @NotNull @NonNull @NotBlank LocalDateTime updatedAt, @Nullable @Relation(value = Kind.MANY_TO_ONE) Account account, @Transient @Property(name = "account.id") String accountId // 新增派生字段 ) { }
修改后,原Repository方法无需额外修改即可正常编译并映射accountId到DTO。
方法3:使用原生SQL优化查询(推荐)
直接查询数据库中的外键列(假设transaction表的外键列为account_id),同时将参数改为List<String>类型的账户ID,避免对象转换开销:
@JdbcRepository(dialect = Dialect.POSTGRES) public interface TransactionRepository extends PageableRepository<Transaction, String> { @Query(value = "SELECT t.id, t.mode, t.made_on, t.account_id AS accountId " + "FROM transaction t " + "WHERE t.account_id IN (:accountIds) AND t.made_on BETWEEN :from AND :to " + "ORDER BY t.made_on DESC", countQuery = "SELECT COUNT(*) FROM transaction t " + "WHERE t.account_id IN (:accountIds) AND t.made_on BETWEEN :from AND :to") Page<TransactionDTO> listByAccountIdsAndMadeOnBetweenOrderByMadeOnDesc( List<String> accountIds, LocalDate from, LocalDate to, Pageable pageable ); }
注意:原生SQL需要确保表名、列名与数据库实际结构一致,同时需要单独指定countQuery用于分页计数。
内容的提问来源于stack exchange,提问作者Fohlen
相关产品推荐
相关产品推荐

