Correct usage of Session.persist()

2020-04-10 02:54发布

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?

1条回答
The star\"
2楼-- · 2020-04-10 03:37

Yes, this is part of the contract of Session.persist(). According to Hibernate documentation, this is the order in which SQLs are executed:

  1. Inserts, in the order they were performed
  2. Updates
  3. Deletion of collection elements
  4. Insertion of collection elements
  5. Deletes, in the order they were performed

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 to INSERT 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:

persist(user1);
persist(user2);
user1.setPartner(user2);
user2.setPartner(user1);

If everything was stored in INSERT statements, then we would get foreign key constraint violation when persisting user1.

In general, by ensuring that only the state that was passed to persist ends up in INSERT, 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.

查看更多
登录 后发表回答