JPA @OneToMany 关联用空值保存 id

JPA @OneToMany association saves id with null value

我实际上正在尝试定义由 JPA 管理的两个实体(在我的示例中称为 Aggregate1 和 Aggregate2)之间的关系,映射 table 表示为另一个 JPA 实体(称为 Association)

这里是 Aggregate1 的定义 class :

@Data
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Entity
@Table(name = "aggregate1")
public class Aggregate1 {

    @Id
    @GeneratedValue
    private Long                   id;
    @Column
    private String                 something;

    @OneToMany(fetch = FetchType.EAGER, cascade = { ALL }, orphanRemoval = true)
    @JoinColumn(name = "aggregate1_id")
    private final Set<Association> associations = new HashSet<>();

    public Aggregate1(String something) {
        this.something = something;
    }

    // methods to add, update and remove association omitted
}

和协会的定义class:

@Value
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
@AllArgsConstructor
@Entity
@Table(name = "association")
public class Association implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "aggregate1_id")
    private final Long        aggregate1Id;
    @Id
    @Column(name = "aggregate2_id")
    private final Long        aggregate2Id;
    @Column(name = "association_value")
    private final String      associationValue;

    // methods omitted
}

您可以找到 运行 代码的完整示例 here

我知道表示两个实体之间多对多关系的标准方法是使用 @ManyToMany 关联(如 post 中所述),但我不想 link 直接 Aggregate1Aggregate2 实体,而是通过身份 link 它们。我不需要从 Aggregate2Aggregate1 的 link。

所以使用这个 JPA 映射,我确实可以检索一个 Aggregate1 及其 Association,但是当我尝试添加一个新的关联时,在新的 Association实体,抛出如下异常:

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
...
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "AGGREGATE1_ID"; SQL statement:
insert into association (association_value, aggregate2_id, aggregate1_id) values (?, ?, ?) [23502-196]

这是保存期间发生的 Hibernate 日志:

2018-02-23 10:13:36.664 DEBUG 9076 --- [           main] org.hibernate.SQL                        : select associatio0_.aggregate2_id as aggregat1_2_0_, associatio0_.aggregate1_id as aggregat2_2_0_, associatio0_.association_value as associat3_2_0_ from association associatio0_ where associatio0_.aggregate2_id=? and associatio0_.aggregate1_id=?
2018-02-23 10:13:36.666 TRACE 9076 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [BIGINT] - [3]
2018-02-23 10:13:36.666 TRACE 9076 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [1]
2018-02-23 10:13:36.686 DEBUG 9076 --- [           main] org.hibernate.SQL                        : insert into association (association_value, aggregate2_id, aggregate1_id) values (?, ?, ?)
2018-02-23 10:13:36.687 TRACE 9076 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [test association]
2018-02-23 10:13:36.687 TRACE 9076 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [BIGINT] - [null]
2018-02-23 10:13:36.687 TRACE 9076 --- [           main] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [BIGINT] - [null]
2018-02-23 10:13:36.688  WARN 9076 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23502, SQLState: 23502
2018-02-23 10:13:36.688 ERROR 9076 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : NULL not allowed for column "AGGREGATE1_ID"; SQL statement:
insert into association (association_value, aggregate2_id, aggregate1_id) values (?, ?, ?) [23502-196]

因此值设置正确,如关联 table 中的初始 select 所示,但似乎 aggregate1Idaggregate2Id 属性值已重置保存前为空。

谁能解释一下保存过程中到底发生了什么,以及为什么这些值被重置为空?

所以DN1提出的解决方案有效,下面的AssociationId class实际上是缺失的:

@Value
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
@AllArgsConstructor
public class AssociationId implements Serializable {

    private static final long serialVersionUID = 1L;

    private final Long        aggregate1Id;
    private final Long        aggregate2Id;

}

并且,在 Association class 中,@IdClass 注释必须定义为:

@Value
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
@AllArgsConstructor
@Entity
@Table(name = "association")
@IdClass(AssociationId.class)
public class Association implements Serializable {
...
}

---编辑---

为了能够添加和删除关联,Aggregate1 实体定义必须使用这些更改进行更新:

@Data
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Entity
@Table(name = "aggregate1")
public class Aggregate1 {

    ...

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "aggregate1Id", cascade = { ALL }, orphanRemoval = true)
    private final Set<Association> associations = new HashSet<>();

    ...
}