When should I commit a transaction in hibernate after saving it. Is it before or after I call session.evict(obj). Currently my code looks like this(only required parts).
Session session = connector.getSession();
Transaction tx = session.beginTransaction();
try {
Criteria crit = session.createCriteria(ST_CODE_SETTINGS_STORE.class).add(Restrictions.eq("TYPE", "issueno"));
List<ST_CODE_SETTINGS_STORE> ls = crit.list();
if (ls.size() < 1) {
session.save(st_code_settings_store);
session.evict(st_code_settings_store);
msg = "insert";
}
else {
Long Id = null;
ST_CODE_SETTINGS_STORE st_code_settings_store1 = ls.get(0);
Id = st_code_settings_store1.getCODE_ID();
Object o = session.get(ST_CODE_SETTINGS_STORE.class, Id);
ST_CODE_SETTINGS_STORE update = (ST_CODE_SETTINGS_STORE) o;
session.update(update);
}
tx.commit();
} catch (Exception e) {
if (tx != null) {
tx.rollback();
}
System.out.println("Error: " + e.getMessage());
connector.printStack(e);
throw e;
} finally {
session.close();
}
Sometimes if I commit after evicting, the data does not get saved in the database. Is it the right way to code??
The method evict() removes a single object from Session cache. So
before you call evict() the object should be there in the Session
cache. Therefore if you save the object first time, you have to save
the object via Session.save(object). Subsequent update calls should
follow through session.saveOrUpdate(object) or session.update(object)
before calling evict() to remove the loaded object from the cache.(reference )
From Hibernate Docs
Ending a Session usually involves four distinct phases:
flush the session
commit the transaction
close the session
handle exceptions
Do not use the anti-patterns session-per-user-session or
session-per-application (there are, however, rare exceptions to this
rule). Some of the following issues might also arise within the
recommended patterns, so ensure that you understand the implications
before making a design decision:
A Session is not thread-safe. Things that work concurrently, like HTTP
requests, session beans, or Swing workers, will cause race conditions
if a Session instance is shared. If you keep your Hibernate Session in
your HttpSession (this is discussed later in the chapter), you should
consider synchronizing access to your Http session. Otherwise, a user
that clicks reload fast enough can use the same Session in two
concurrently running threads.
An exception thrown by Hibernate means
you have to rollback your database transaction and close the Session
immediately (this is discussed in more detail later in the chapter).
If your Session is bound to the application, you have to stop the
application. Rolling back the database transaction does not put your
business objects back into the state they were at the start of the
transaction. This means that the database state and the business
objects will be out of sync. Usually this is not a problem, because
exceptions are not recoverable and you will have to start over after
rollback anyway.
The Session caches every object that is in a
persistent state (watched and checked for dirty state by Hibernate).
If you keep it open for a long time or simply load too much data, it
will grow endlessly until you get an OutOfMemoryException. One
solution is to call clear() and evict() to manage the Session cache,
but you should consider a Stored Procedure if you need mass data
operations.
I'm trying to implement locking on a JPA/Hibernate application, to detect when a client is trying to push changes on an out-of-date version of an entity.
I've choose to expose dto's (representing a subset of the domain) over REST services. At the moment, I can easily detect concurrent transactions updates, but I can not make it work to detect "old" entities updates. I'll explain:
#1 2 concurrent transactions manipulating the same entity, each attached to their entity manager, are correctly protected against dirty reads (the last to commit gets an OptimisticLockingException)
#2 2 concurrent users, manipulating the same entity with their frontend and committing two different, non-concurrent transactions, do NOT get any lock exception. Thats because, using DTO's, the update part of the code is something like that:
start a transaction
get the persisted entity to update from manager
copy what is relevant from the dto to that entity
commit
... but nothing (JPA, Hibernate or whatever) never checks for the consistency between the dto's version and the entity's one... (ps: trying to set the #Version field with the version given in the dto, as specified by JPA, lead to weird results)
Based on what I've seen and lots of debugging and docs read, I've ended up writing a code like that:
abstract class AbstractBusinessService {
private static final Logger LOG = LoggerFactory.getLogger(AbstractBusinessService.class);
protected final void checkEntityVersion(Long givenVersion, VersionedEntity<?> entity) {
if (givenVersion == null) {
LOG.warn("no version have been provided. unable to check for concurrent modification");
} else if (entity == null) {
LOG.warn("the given entity is null");
} else if (entity.getVersion() != givenVersion.longValue()) {
throw new LockingException("The persistent entity " + entity.getClass().getName() + "#" + entity.getId()
+ " has a newer version than expected (" + givenVersion + " vs. " + entity.getVersion() + ")");
}
}
}
... invoked before every "update" operation... It obviously works like a charm, but adds some complexity on the business layer, for a topic that is purely persistence-related and should not be visible on the business layer...
Am I right in the sense that this is a DIY thing and nothing is available out-of-the-box to implement #2? Am I missing something?? How do you address that problem in your development?
Thank you very much for reading,
SP
EDIT : as per indicated in comments, the best way seems to
get a persistent entity from context (A)
create a new, clean (detached/transient) entity (B)
copy every properties of (A) to (B)
set every properties in (B) with what comes from the DTO (conversion may be needed)
merge (B) --> get LockException if #Version mismatch
I have a JPA entity class User with a username #ID and no parent entity group. I need to make sure that when two parallel transactions try to persist a new User with the same username, only one is committed and the other one is rolled back.
Example:
User bob = new User("bob");
EntityTransaction transaction = em.getTransaction();
try {
transaction.begin();
User u = em.find(User.class, bob.id());
if (u == null) {
em.persist(bob);
} else {
// identical user already existed before the transaction
throw new UserExistsException();
}
transaction.commit();
} catch (RollbackException e) {
// identical user was created during the transaction
throw new UserExistsException();
}
According to the Datastore docs, transactions follow an optimistic locking approach:
"When a transaction starts, App Engine uses optimistic concurrency control by checking the last update time for the entity groups used in the transaction. Upon commiting a transaction for the entity groups, App Engine again checks the last update time for the entity groups used in the transaction. If it has changed since our initial check, App Engine throws an exception."
(https://developers.google.com/appengine/docs/java/datastore/transactions)
Will this work when persisting new (root) entities, which did not exist before the transaction? In my case, would App Engine check whether another transaction has meanwhile persisted a User with the same id? If so, do I need an explicit #Version field for that purpose?
To put some long overdue closure on the matter: The answer is "yes" and the above code should work as expected. In short, the optimistic concurrency control mechanism will compare the (new) entity group used in both transactions using the (root) entity's kind "User" and the given identifier "bob". The Datastore docs also explicitly address the creation case now:
When two or more transactions try to change the same entity group at the same time (either updating existing entities or creating new ones), the first transaction to commit will succeed and all others will fail on commit.
With JPA, you'd get a RollbackException in this case. The low-level API will raise a ConcurrentModificationException. If you're using Objectify (which I'd strongly recommend), the failed transaction will automatically be retried. You should therefore make sure that, within the transaction, you first check if the entity exists unless you want to overwrite it at the second attempt.
I have a base method that I'm writing in order to not repeat the same hibernate session/transaction logic over and over. It's fairly simple, but there's a specific issue that I'm not sure can be solved with this approach.
Imagine that you have a User entity and a Permission entity. If a request is made to save a user along with its matching permissions, then I think that it would make sense to perform both operations in a single transaction, since being able to save only one of those entities could be considered data corruption. For example, failing to save the user's permissions would warrant a rollback on previously inserted user data.
I made the following method to allow generic hibernate operations that could work with the current transaction if it were necessary, although I now think that in its current form it won't work since calling session.beginTransaction(); will probably return a new transaction even if the previous hasn't been commited (is this the case?). Suppose that I changed it in order to have it return the current session and transaction if it was specified that there would be more operations for the current transaction, do you think it would work? Would it be advisable to do something like this, or would you recommend a change of approach? Thanks
protected <T> void baseOperation(Class<T> entityClass, List<T> instances, BaseHibernateDAO.Operations operation, boolean isLastOperation) throws Exception
{
Session session = null;
Transaction transaction = null;
boolean caughtException = false;
//get session from factory
session = HibernateSessionFactory.getSession();
try
{
//get current transaction
transaction = session.beginTransaction();
for (Object instance : instances) //perform operation on all instances
{
log.debug(String.format("Will perform %s operation on %s instance.", operation.name(), entityClass.getName()));
switch (operation) //perform requested operation
{
case SAVE:
session.save(instance);
break;
case UPDATE:
session.update(instance);
break;
case SAVEORUPDATE:
session.saveOrUpdate(instance);
break;
case DELETE:
session.saveOrUpdate(instance);
break;
}
log.debug(String.format("%s operation on %s instance was succesful.", operation.name(), entityClass.getName()));
}
session.flush(); //synchronize
if (isLastOperation) //if this is the last operation of the transaction
{
transaction.commit();
log.debug("Transaction commited succesfully.");
}
}
catch (Exception e) //error occurred
{
caughtException = true;
//roll-back if transaction exists
if (transaction != null)
{
transaction.rollback();
}
//log and re-throw
log.error("An error occurred during transaction operation.", e);
throw e;
}
finally //cleanup tasks
{
if (isLastOperation || caughtException) //close session if there are no more pending operations or if an error occurred
{
HibernateSessionFactory.closeSession();
}
}
}
"Advisable" would be to stop trying to rewrite code that's already been written, debugged, dragged through the mud, debugged more, and deployed thousands of times. I.e, the issues and considerations you're encountering have been encountered and overcome before, and the solutions are proven. Further, having been extensively used and improved, they require much less effort to use than what you're putting into your custom solution. Check out Spring's Hibernate support, especially "Implementing DAOs based on plain Hibernate 3 API" and "Declarative transaction demarcation". For further reading, there's a whole chapter on transaction management.
I have a sample project on github where you can see a very simple example of using Spring to manage Hibernate Sessions and transactions in the context of a webapp (using Spring MVC).
Update: For those who come along later, so they don't have to dig through the comments:
There are three general ways to use Spring's transaction handling: declaratively defining which methods are transactional with XML, declaratively annotating methods as #Transactional, or programmatically using TransactionTemplate.
I'm trying to write a method that will return a Hibernate object based on a unique but non-primary key. If the entity already exists in the database I want to return it, but if it doesn't I want to create a new instance and save it before returning.
UPDATE: Let me clarify that the application I'm writing this for is basically a batch processor of input files. The system needs to read a file line by line and insert records into the db. The file format is basically a denormalized view of several tables in our schema so what I have to do is parse out the parent record either insert it into the db so I can get a new synthetic key, or if it already exists select it. Then I can add additional associated records in other tables that have foreign keys back to that record.
The reason this gets tricky is that each file needs to be either totally imported or not imported at all, i.e. all inserts and updates done for a given file should be a part of one transaction. This is easy enough if there's only one process that's doing all the imports, but I'd like to break this up across multiple servers if possible. Because of these constraints I need to be able to stay inside one transaction, but handle the exceptions where a record already exists.
The mapped class for the parent records looks like this:
#Entity
public class Foo {
#Id
#GeneratedValue(strategy = IDENTITY)
private int id;
#Column(unique = true)
private String name;
...
}
My initial attempt at writting this method is as follows:
public Foo findOrCreate(String name) {
Foo foo = new Foo();
foo.setName(name);
try {
session.save(foo)
} catch(ConstraintViolationException e) {
foo = session.createCriteria(Foo.class).add(eq("name", name)).uniqueResult();
}
return foo;
}
The problem is when the name I'm looking for exists, an org.hibernate.AssertionFailure exception is thrown by the call to uniqueResult(). The full stack trace is below:
org.hibernate.AssertionFailure: null id in com.searchdex.linktracer.domain.LinkingPage entry (don't flush the Session after an exception occurs)
at org.hibernate.event.def.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:82) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
at org.hibernate.event.def.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:190) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:147) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:219) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:1185) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1709) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
at org.hibernate.impl.CriteriaImpl.list(CriteriaImpl.java:347) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
at org.hibernate.impl.CriteriaImpl.uniqueResult(CriteriaImpl.java:369) [hibernate-core-3.6.0.Final.jar:3.6.0.Final]
Does anyone know what is causing this exception to be thrown? Does hibernate support a better way of accomplishing this?
Let me also preemptively explain why I'm inserting first and then selecting if and when that fails. This needs to work in a distributed environment so I can't synchronize across the check to see if the record already exists and the insert. The easiest way to do this is to let the database handle this synchronization by checking for the constraint violation on every insert.
I had a similar batch processing requirement, with processes running on multiple JVMs. The approach I took for this was as follows. It is very much like jtahlborn's suggestion. However, as vbence pointed out, if you use a NESTED transaction, when you get the constraint violation exception, your session is invalidated. Instead, I use REQUIRES_NEW, which suspends the current transaction and creates a new, independent transaction. If the new transaction rolls back it will not affect the original transaction.
I am using Spring's TransactionTemplate but I'm sure you could easily translate it if you do not want a dependency on Spring.
public T findOrCreate(final T t) throws InvalidRecordException {
// 1) look for the record
T found = findUnique(t);
if (found != null)
return found;
// 2) if not found, start a new, independent transaction
TransactionTemplate tt = new TransactionTemplate((PlatformTransactionManager)
transactionManager);
tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
try {
found = (T)tt.execute(new TransactionCallback<T>() {
try {
// 3) store the record in this new transaction
return store(t);
} catch (ConstraintViolationException e) {
// another thread or process created this already, possibly
// between 1) and 2)
status.setRollbackOnly();
return null;
}
});
// 4) if we failed to create the record in the second transaction, found will
// still be null; however, this would happy only if another process
// created the record. let's see what they made for us!
if (found == null)
found = findUnique(t);
} catch (...) {
// handle exceptions
}
return found;
}
You need to use UPSERT or MERGE to achieve this goal.
However, Hibernate does not offer support for this construct, so you need to use jOOQ instead.
private PostDetailsRecord upsertPostDetails(
DSLContext sql, Long id, String owner, Timestamp timestamp) {
sql
.insertInto(POST_DETAILS)
.columns(POST_DETAILS.ID, POST_DETAILS.CREATED_BY, POST_DETAILS.CREATED_ON)
.values(id, owner, timestamp)
.onDuplicateKeyIgnore()
.execute();
return sql.selectFrom(POST_DETAILS)
.where(field(POST_DETAILS.ID).eq(id))
.fetchOne();
}
Calling this method on PostgreSQL:
PostDetailsRecord postDetailsRecord = upsertPostDetails(
sql,
1L,
"Alice",
Timestamp.from(LocalDateTime.now().toInstant(ZoneOffset.UTC))
);
Yields the following SQL statements:
INSERT INTO "post_details" ("id", "created_by", "created_on")
VALUES (1, 'Alice', CAST('2016-08-11 12:56:01.831' AS timestamp))
ON CONFLICT DO NOTHING;
SELECT "public"."post_details"."id",
"public"."post_details"."created_by",
"public"."post_details"."created_on",
"public"."post_details"."updated_by",
"public"."post_details"."updated_on"
FROM "public"."post_details"
WHERE "public"."post_details"."id" = 1
On Oracle and SQL Server, jOOQ will use MERGE while on MySQL it will use ON DUPLICATE KEY.
The concurrency mechanism is ensured by the row-level locking mechanism employed when inserting, updating, or deleting a record, which you can view in the following diagram:
Code avilable on GitHub.
Two solution come to mind:
That's what TABLE LOCKS are for
Hibernate does not support table locks, but this is the situation when they come handy. Fortunately you can use native SQL thru Session.createSQLQuery(). For example (on MySQL):
// no access to the table for any other clients
session.createSQLQuery("LOCK TABLES foo WRITE").executeUpdate();
// safe zone
Foo foo = session.createCriteria(Foo.class).add(eq("name", name)).uniqueResult();
if (foo == null) {
foo = new Foo();
foo.setName(name)
session.save(foo);
}
// releasing locks
session.createSQLQuery("UNLOCK TABLES").executeUpdate();
This way when a session (client connection) gets the lock, all the other connections are blocked until the operation ends and the locks are released. Read operations are also blocked for other connections, so needless to say use this only in case of atomic operations.
What about Hibernate's locks?
Hibernate uses row level locking. We can not use it directly, because we can not lock non-existent rows. But we can create a dummy table with a single record, map it to the ORM, then use SELECT ... FOR UPDATE style locks on that object to synchronize our clients. Basically we only need to be sure that no other clients (running the same software, with the same conventions) will do any conflicting operations while we are working.
// begin transaction
Transaction transaction = session.beginTransaction();
// blocks until any other client holds the lock
session.load("dummy", 1, LockOptions.UPGRADE);
// virtual safe zone
Foo foo = session.createCriteria(Foo.class).add(eq("name", name)).uniqueResult();
if (foo == null) {
foo = new Foo();
foo.setName(name)
session.save(foo);
}
// ends transaction (releasing locks)
transaction.commit();
Your database has to know the SELECT ... FOR UPDATE syntax (Hibernate is goig to use it), and of course this only works if all your clients has the same convention (they need to lock the same dummy entity).
The Hibernate documentation on transactions and exceptions states that all HibernateExceptions are unrecoverable and that the current transaction must be rolled back as soon as one is encountered. This explains why the code above does not work. Ultimately you should never catch a HibernateException without exiting the transaction and closing the session.
The only real way to accomplish this it would seem would be to manage the closing of the old session and reopening of a new one within the method itself. Implementing a findOrCreate method which can participate in an existing transaction and is safe within a distributed environment would seem to be impossible using Hibernate based on what I have found.
The solution is in fact really simple. First perform a select using your name value. If a result is found, return that. If not, create a new one. In case the creation fail (with an exception), this is because another client added this very same value between your select and your insert statement. This is then logical that you have an exception. Catch it, rollback your transaction and run the same code again. Because the row already exist, the select statement will find it and you'll return your object.
You can see here explanation of strategies for optimistic and pessimistic locking with hibernate here : http://docs.jboss.org/hibernate/core/3.3/reference/en/html/transactions.html
a couple people have mentioned different parts of the overall strategy. assuming that you generally expect to find an existing object more often than you create a new object:
search for existing object by name. if found, return
start nested (separate) transaction
try to insert new object
commit nested transaction
catch any failure from nested transaction, if anything but constraint violation, re-throw
otherwise search for existing object by name and return it
just to clarify, as pointed out in another answer, the "nested" transaction is actually a separate transaction (many databases don't even support true, nested transactions).
Well, here's one way to do it - but it's not appropriate for all situations.
In Foo, remove the "unique = true" attribute on name. Add a timestamp that gets updated on every insert.
In findOrCreate(), don't bother checking if the entity with the given name already exists - just insert a new one every time.
When looking up Foo instances by name, there may be 0 or more with a given name, so you just select the newest one.
The nice thing about this method is that it doesn't require any locking, so everything should run pretty fast. The downside is that your database will be littered with obsolete records, so you may have to do something somewhere else to deal with them. Also, if other tables refer to Foo by its id, then this will screw up those relations.
Maybe you should change your strategy:
First find the user with the name and only if the user thoes not exist, create it.
I would try the following strategy:
A. Start a main transaction (at time 1)
B. Start a sub-transaction (at time 2)
Now, any object created after time 1 will not be visible in the main transaction. So when you do
C. Create new race-condition object, commit sub-transaction
D. Handle conflict by starting a new sub-transaction (at time 3) and getting the object from a query (the sub-transaction from point B is now out-of-scope).
only return the object primary key and then use EntityManager.getReference(..) to obtain the object you will be using in the main transaction. Alternatively, start the main transaction after D; it is not totally clear to me in how many race conditions you will have within your main transaction, but the above should allow for n times B-C-D in a 'large' transaction.
Note that you might want to do multi-threading (one thread per CPU) and then you can probably reduce this issue considerably by using a shared static cache for these kind of conflicts - and point 2 can be kept 'optimistic', i.e. not doing a .find(..) first.
Edit: For a new transaction, you need an EJB interface method call annotated with transaction type REQUIRES_NEW.
Edit: Double check that the getReference(..) works as I think it does.