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

Spring Data JPA复合键关联查询问题:实体映射与SQL等效实现

实体映射问题修正

你的实体映射存在几处关键问题,需要调整:

  1. Account实体的@OneToMany关联缺失配置
    当前@OneToMany未指定关联字段,JPA无法识别Account与AccountBalance的关联关系,需补充mappedBy属性指向AccountBalance中关联Account的字段:

    @Entity
    @IdClass(AccountCompositeKey.class)
    @Data
    public class Account {
    
        @Id
        @Column(name = "customerId")
        private int customerId;
    
        @Id
        @Column(name = "account_id")
        private UUID accountId;
    
        private String country;
    
        // 补充mappedBy,指向AccountBalance中的account关联字段
        @OneToMany(mappedBy = "account")
        private List<AccountBalance> balances;
    }
    
  2. AccountBalance实体的关联配置错误

    • 同时使用@JoinColumn和@Column会造成配置冲突,应移除@Column,@JoinColumn已指定数据库列名;
    • 需要添加@ManyToOne注解建立与Account的双向关联,有两种实现方式:
      方式一:直接关联Account实体作为主键字段
      @Entity
      @Table(name= "account_balance")
      @IdClass(AccountBalanceCompositeKey.class)
      @Data
      public class AccountBalance {
      
          @Id
          @ManyToOne
          @JoinColumn(name="account_id")
          private Account account;
      
          @Id
          @Enumerated(EnumType.STRING)
          private CurrencyEnum currency;
      
          private BigDecimal amount;
      }
      
      方式二:保留UUID类型的accountId作为主键,通过@MapsId映射关联
      @Entity
      @Table(name= "account_balance")
      @IdClass(AccountBalanceCompositeKey.class)
      @Data
      public class AccountBalance {
      
          @Id
          @Column(name = "account_id")
          private UUID accountId;
      
          @Id
          @Enumerated(EnumType.STRING)
          private CurrencyEnum currency;
      
          private BigDecimal amount;
      
          @ManyToOne
          @MapsId("accountId")
          @JoinColumn(name = "account_id")
          private Account account;
      }
      
  3. 复合主键类的强制要求
    AccountCompositeKey和AccountBalanceCompositeKey必须满足:

    • 实现Serializable接口;
    • 重写equals()和hashCode()方法;
    • 字段名、类型与实体类中的主键字段完全匹配。
实现等效SQL的JPA查询

针对你需要的SQL查询,提供三种实现方式:

方式1:原生SQL查询

先定义DTO接收结果:

@Data
public class AccountBalanceDto {
    private UUID accountId;
    private BigDecimal amount;
    private int customerId;
}

在AccountBalanceRepository中添加查询方法:

@Repository
public interface AccountBalanceRepository extends JpaRepository<AccountBalance, AccountBalanceCompositeKey> {

    @Query(value = "SELECT A.ACCOUNT_ID, A.AMOUNT, B.CUSTOMER_ID FROM ACCOUNT_BALANCE A JOIN ACCOUNT B ON A.ACCOUNT_ID=B.ACCOUNT_ID WHERE A.ACCOUNT_ID=?", nativeQuery = true)
    List<AccountBalanceDto> findByAccountIdNative(UUID accountId);
}

方式2:JPQL查询

使用JPQL替代原生SQL,支持DTO构造:

@Repository
public interface AccountBalanceRepository extends JpaRepository<AccountBalance, AccountBalanceCompositeKey> {

    @Query("SELECT new com.yourpackage.AccountBalanceDto(ab.accountId, ab.amount, a.customerId) FROM AccountBalance ab JOIN ab.account a WHERE ab.accountId = :accountId")
    List<AccountBalanceDto> findByAccountIdJpql(@Param("accountId") UUID accountId);
}

注意替换com.yourpackage为DTO实际包路径。

方式3:Spring Data投影

定义投影接口,Spring Data自动映射结果:

public interface AccountBalanceProjection {
    UUID getAccountId();
    BigDecimal getAmount();
    int getCustomerId();
}

在Repository中定义方法:

@Query("SELECT ab.accountId as accountId, ab.amount as amount, a.customerId as customerId FROM AccountBalance ab JOIN ab.account a WHERE ab.accountId = :accountId")
List<AccountBalanceProjection> findByAccountIdProjection(@Param("accountId") UUID accountId);
原Repository方法问题说明

你之前定义的Account findByAccountId(UUID accountId)存在逻辑歧义:Account的复合主键是customerId+accountId,同一个accountId可能对应多条Account记录,JPA无法确定返回单条结果,应改为返回List<Account>或调整查询逻辑。

内容的提问来源于stack exchange,提问作者Abhilash

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 23:35:40