考虑以下实体:
@Entity
@Table(name = "my_entity")
public class MyEntity {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "myEntitySeq")
@SequenceGenerator(name = "myEntitySeq", sequenceName = "my_entity_seq")
private Long id;
@OneToOne(optional = false)
@JoinColumn(name = "other_id")
private OtherEntity other;
@Column(nullable = false)
private String fieldA;
@Column(nullable = false)
private byte[] fieldB;
public MyEntity() {}
public MyEntity(OtherEntity other) {
this.other = other;
}
// public getter/setters for all fields...
}
所有数据库字段都应为非空。
然后我有一个应该创建或更新此实体的事务方法:
@Transactional
public void createOrUpdate(OtherEntity other, String fieldA, byte[] fieldB) {
var myEntity = myEntityRepository.findByOther(other)
// Create and get a new managed entity, missing fields will be updated right after:
.orElseGet(() -> entityManager.merge(new MyEntity(other)))
myEntity.setFieldA(fieldA);
myEntity.setFieldB(fieldB);
// Once the transaction ends I expect the new entity to be INSERTed, otherwise UPDATEd
}
但是此代码失败并显示错误,指出它无法插入新实体,因为它尝试将空字段插入表中(调用合并时,
fieldA
和fieldB
都是空,即使我稍后更新状态)。
我的理解是,由于 SQL 查询被推迟到事务结束,因此首先调用 merge 来获取托管实例,然后更新状态将正确地推迟最后的完整插入。
相反,它似乎推迟了一个不完整的
INSERT
,然后是一个UPDATE
,这使数据库NOT NULL
对不完整插入的约束......
删除
NOT NULL
约束可以让前面的代码不会失败,因为它允许不完整的插入通过,并且以某种方式它还使用后面设置的属性更新行。但这意味着我的数据库架构允许在我不想要的地方使用空值。
我期望
entityManager.merge(...)
后面跟着集合将所有内容分组到单个 INSERT
语句中是错误的吗?是否期望执行 merge
会使用此时提供的状态安排 INSERT
,并且仅在稍后 UPDATE
使用稍后在事务中更改的状态?
为了避免部分
INSERT
,我最终写的是推迟未初始化实体的合并,直到字段全部设置完毕:
@Transactional
public void createOrUpdate(OtherEntity other, String fieldA, byte[] fieldB) {
var myEntity = myEntityRepository.findByOther(other)
// Create a new unmanaged entity
.orElseGet(() -> new MyEntity(other))
myEntity.setFieldA(fieldA);
myEntity.setFieldB(fieldB);
entityManager.merge(myEntity);
}
或者,在本例中,我们似乎可以使用
myEntityRepository.save(myEntity)
代替 entityManager.merge(myEntity)
。