JPA Flush caused the misbehavior
Sometimes ago, the development team encounters a strange
behavior in JPA. There was a use case that checked uniqueness of a
record data by querying the db and checking if it has any result.
If no result found, it should update the record. The problem
occured when before running this query some objects were attached
in the context (for example by finding a record) and some updates
were applied on the object before query the db for finding
duplicates. JPA logs showed that an UPDATE query was submitted to
db before the SELECT search query. The intent of the designer
was checking for uniqueness then updating the record but in this
case the record has been updated independent of the result of the
SELECT query.
JPA Queries and the Flush Mode
The problem relates to not including the flush behavior of
EntityManager in the design. As we know, EntityManager operations
like persist, merge, and remove do not cause immediate database
changes, instead these operations are postponed until the
EntityManager is flushed. The true motivation for doing thing this
way is performance optimization and JPA tuning.
Flush Mode
Flush mode has two different modes: AUTO and COMMIT. By default,
the JPA flush mode is set to AUTO. That means that the
EntityManagers performs a flush operation automatically as needed.
For transaction scoped EntityManagers, that will occur at the end
of transaction and for application managed or extended scoped
EntityManagers, it occurs when the persistence context closed. In
other words, if entities with pending changes are used in a
query, the persistence provider will flush changes to the database
before executing the query. On the other hand, the
COMMIT mode means that the flush occurs just at the end of the
transaction when commit occurs.
JPA flushmode in our use-case
In our use-case, that error occurred because the changes were
applied on the objects before checking the db for duplication.
Better design would be checking the db for duplication then doing
the mapping and updating the objects. We applied this design
change, without changing the JPA flush mode, the system works
properly.
JPA providers and FlushMode
But How we can change flush mode when it is necessary?
- For a specific query it can be changed using the
Query.setFlushMode method.
- In an EntityManager it can be changed by calling
setFlushMode(FlushModeType flushMode) of the
EntityManager.
- In eclipseLink, there is also a persistence unit property named
"eclipse.persistance-context.flush-mode" that can be set
to COMMIT or AUTO.
- In OpenJPA, there is a property
named FlushBeforeQueries that determines whether
or not to flush any changes made in the current transaction to the
data base before executing a query.
As a final note, changing flush mode from the default value to
COMMIT can introduce performance and functionality issues when you
don't understand the exact procedures inside JPA.