Consider the following entity:
@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...
}
All database fields should be non-null.
I then have a transactional method that should create or update this entity:
@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
}
But this code fails with an error saying it cannot insert the new entity because it tries to insert null fields into the table (fieldA
and fieldB
are both null when calling merge, even if I update the state later).
My understanding was that since SQL queries are deferred until the end of a transaction, calling merge first to get a managed instance followed by updates to the state would properly defer a complete insertion at the end.
Instead it seems to defer an incomplete INSERT
followed by an UPDATE
, which trips the database NOT NULL
constraints on the incomplete insert...
Removing the NOT NULL
constraint allow the previous code to not fail because it allows the incomplete insert to go through, and somehow it also updates the row with the properties set after. But this means my database schema allows null values in places where I don't want them.
Am I wrong to expect entityManager.merge(...)
followed by sets to group everything into a single INSERT
statement? Is it expected that doing merge
will schedule an INSERT
with the state provided at this point, and only later UPDATE
using the state as changed later in the transaction?
What I ended up writing to avoid the partial INSERT
was to defer the merge of the uninitialized entity until the fields were all set:
@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);
// Merge into the persistence context only once all fields are set
entityManager.merge(myEntity);
}
Alternatively it seems like we can use myEntityRepository.save(myEntity)
in place of entityManager.merge(myEntity)
in this example.