Spring JPA 在嵌入的复合键上使用 @MapsId、@AttributeOverride 时插入重复的选择列

Posted

技术标签:

【中文标题】Spring JPA 在嵌入的复合键上使用 @MapsId、@AttributeOverride 时插入重复的选择列【英文标题】:Spring JPA inserts duplicate select columns when using @MapsId, @AttributeOverride on embedded composite key 【发布时间】:2021-07-22 17:25:28 【问题描述】:

我正在开发一个 Spring Boot REST API 应用程序,但在为 Transact-SQL (SQL Server) 方言生成 SQL 时遇到了问题,我不确定我在哪里做错了。

该应用程序是关于存储管理的,我有两个实体:PartStock。我已将结构简化为尽可能简单。

我有复合 PK - PartPK:

@Data @Embeddable 
class PartPK 

     @Column(name = "PART_ID")
     private String partId;

     @Column(name = "PART_ORGANIZATION_ID")
     private String orgId;


... 实体Part 具有PartPK 作为@EmbeddedId

@Entity @Table(name = "parts") 
class Part 

    @EmbeddedId
    private PartPK id;

然后我有一个Stock 实体,它与Part 实体和商店相关联。该实体具有具有以下结构的复合 PK,其中我将覆盖来自 PartPK 的属性(给它们 STOCK_ 前缀)

 @Data @Embeddable 
 class StockPK 

     @Column(name = "STOCK_STORE_ID")
     private String storeId;

    @Embedded
    @AttributeOverrides(
        @AttributeOverride(name = "partId", column = @Column(name = "STOCK_PART_ID")),
        @AttributeOverride(name = "orgId", column = @Column(name = "STOCK_PART_ORGANIZATION_ID")),
    )
    private PartPK partId;


... 并附上 Stock 实体,我尝试使用 @MapsId 引用 Part 实体:

@Entity @Table(name = "stocks") 
class Stock 

    @EmbeddedId
    private StockPK id;

    @MapsId("partId")
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns(
        @JoinColumn(name = "STOCK_PART_ID", referencedColumnName = "PART_ID"),
        @JoinColumn(name = "STOCK_PART_ORGANIZATION_ID", referencedColumnName = "PART_ORGANIZATION_ID"),
    )
    private Part part;


编译,但在从存储库执行查询后,它会生成以下查询:

select TOP (?) 
           stockdb0_.stock_part_organization_id as bis_part0_0_,
           stockdb0_.stock_store_id             as bis_stor3_0_,
           stockdb0_.stock_part_organization_id as bis_part5_0_,
           stockdb0_.stock_part_id              as bis_part6_0_
from stocks stockdb0_

如您所见,由于某种原因,它使用了 2 次 stock_part_organization_id 列。实体在持久化映射后具有不正确的值(具有相同 Store 但不同部分的两个 Stock 行被认为是同一实体)。从Stock 实体中删除part 属性时,查询和生成的持久性映射是正确的。

是不是我做错了什么?

我用的是Spring Boot 2.4.5(最新)和同版本的Started Data Jpa。

【问题讨论】:

【参考方案1】:

我认为在这种情况下使用@IdClass 会更好:

class StockPK implements Serializable 

  private String storeId;

  private Part part;

  ...


@Entity @Table(name = "stocks") 
@IdClass(StockPK.class)
class Stock 

    @Id
    private String id;

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns(
        @JoinColumn(name = "STOCK_PART_ID", referencedColumnName = "PART_ID"),
        @JoinColumn(name = "STOCK_PART_ORGANIZATION_ID", referencedColumnName = "PART_ORGANIZATION_ID"),
    )
    private Part part;

    ...

但是如果你想使用@EmbeddedId:

@Embeddable
public static class StockPK implements Serializable 

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns(
        @JoinColumn(name = "STOCK_PART_ID", referencedColumnName = "PART_ID"),
        @JoinColumn(name = "STOCK_PART_ORGANIZATION_ID", referencedColumnName = "PART_ORGANIZATION_ID"),
    )
    private Part part;

    @Column(name = "STOCK_STORE_ID")
    private String storeId;




@Entity @Table(name = "stocks") 
class Stock 

    @EmbeddedId
    private StockPK id;
    
    // The association is already defined in the key

无论如何,您不必使用@MapsId (that's for something else),您可以找到这两种方法的示例以及更多详细信息in the Hibernate ORM documentation。

【讨论】:

感谢您的回复。(1) 我想避免在复合 PK 中声明实体类型,因为在特定情况下我只需要键属性,并且在访问时会获取整个实体。 (2) 关联是在 StockPK 中定义的,但我将无法从 Stock 实体中截取 Part 实体并根据 Stock 上下文中 Part 实体的非键值进行 Criteria 查询。

以上是关于Spring JPA 在嵌入的复合键上使用 @MapsId、@AttributeOverride 时插入重复的选择列的主要内容,如果未能解决你的问题,请参考以下文章

Spring data JPA只有一个复合键自动递增

Spring Boot REST JPA JSON 格式

是否可以在 Spring Boot 中运行两个使用 spring.jpa.generate-ddl 填充的嵌入式数据库?

复合主键上的慢连接

Spring JPA 主键

如何禁用嵌入式数据库 Spring-boot spring-data-jpa