I'm trying to understand the semantics of Session.persist()
, and exactly what the entity manager does with unsaved transient instances. What I want to achieve is to just add a new transient instance to the session and have Hibernate execute an INSERT
when the session is flushed.
I have found that if a new instance is persisted and then modified within the same session, the entity manager will generate both INSERT
and UPDATE
statements which can lead to constraint violations.
As an example, lets say I have an entity relation Foo with a NOT NULL
column bar and the following service method.
@Transactional
void persistFoo(String bar) {
Foo foo = new Foo();
session.persist(foo);
foo.setBar(bar);
}
Despite the fact that we provide a value for bar
, executing this code will violate the NULL constraint in the database.
BatchUpdateException: Cannot insert the value NULL into column 'bar'
The Hibernate documentation states the following.
persist() makes a transient instance persistent. However, it does not guarantee that the identifier value will be assigned to the persistent instance immediately, the assignment might happen at flush time...
The INSERT
is indeed executed at the point the session is flushed at the end of the transactional block, but it is still parameterised with property values from the object as it was when persist()
was called.
I know the particular problem this example illustrates can be fixed with some simple changes, but I'm more interested in trying to understand how its supposed to work.
My question is, is this behaviour just part of the contract of Session.persist()
or can it be changed? If so, how do I tell the session to defer gathering parameters for the generated INSERT
statement until the time when it is actually executed?
Yes, this is part of the contract of
Session.persist()
. According to Hibernate documentation, this is the order in which SQLs are executed:This order is part of the official Hibernate API, and applications rely on it when manipulating their entity graphs.
Putting changes that occurred after
Session.persist()
immediately toINSERT
statements would break this contract and would cause problems in some use cases.Let's suppose that we have a
User
entity, and it is possible that two users are associated with each other in some way. Then we can insert the two in one transaction:If everything was stored in
INSERT
statements, then we would get foreign key constraint violation when persistinguser1
.In general, by ensuring that only the state that was passed to
persist
ends up inINSERT
, Hibernate gives us more flexibility to satisfy underlying DB constraints.I am not aware of any config with which this behavior can be changed. Of course, as you mentioned, you can restructure your code so that
persist
is called after all the values are set, provided that no DB constraints are violated.