I am trying to use Hibernate for a multi threaded application wherein each thread retrieves an object and tries to insert it into a table. My code looks like below.
I have local hibernate Session objects per thread and in each InsertData I do beginTransaction and commit.
The problem I am facing is that many times I get "org.hibernate.TransactionException: nested transactions not supported"
Since I am new to hibernate I don't know if what I am doing is correct or not? Please let me know what is the correct way to use hibernate in multi threaded app and how to avoid the above mentioned exception.
Thanks
public class Worker extends Thread {
private Session session = null;
Worker() {
SessionFactory sf = HibernateUtil.getSessionFactory(); // Singleton
session = sf.openSession();
session.setFlushMode(FlushMode.ALWAYS);
}
public void run() {
// Some loop which will run thousand of times
for (....)
{
InsertData(b);
}
session.close();
}
// BlogPost Table has (pk = id AutoGenerated), dateTime, blogdescription etc.
private void InsertData(BlogPost b) {
session.beginTransaction();
Long id = (Long) session.save(b);
b.setId(id);
session.getTransaction().commit();
}
}
My hibernate config file has c3p0.min_size=10 and c3p0.max_size=20
With session-objects-per-thread, as long as you are not sharing session objects between multiple threads, you will be fine.
The error you are receiving is unrelated to your multithreaded usage or your session management. Your usage of session.save() as well as explicitly setting the ID is not quite right.
Without seeing your mapping for BlogPost its hard to tell, but if you have told Hibernate to use the id field as the primary key, and you are using the native generator for primary keys, the all you need to do is this:
session.beginTransaction();
session.persist(b);
session.flush(); // only needed if flush mode is "manual"
session.getTransaction().commit();
Hibernate will fill in the ID for you, persist() will cause the insert to happen within the bounds of the transaction (save() does not care about transactions). If your flush mode is not set to manual then you don't need to call flush() as Transaction.commit() will handle that for you.
Note that with persist(), the BlogPost's ID is not guaranteed to be set until the session is flushed, which is fine for your usage here.
To handle errors gracefully:
try {
session.beginTransaction();
try {
session.persist(b);
session.flush(); // only needed if flush mode is "manual"
session.getTransaction().commit();
} catch (Exception x) {
session.getTransaction().rollback();
// log the error
}
} catch (Exception x) {
// log the error
}
By the way, I suggesting making BlogPost.setId() private, or package visible. It is most likely an implementation error if another class sets the ID explicitly (again assuming native generator, and id as primary key).
Related
So i was trying to implement proper multithreading for saving and update operation in hibernate.
Each of my threads uses one newly created session with its own transaction.
Some Entities are shared over multiple sessions.
Update "Chunk:1" in session 1 on thread 1
Update "Chunk:1" in session 2 on thread 2
I always get a "Illegal attempt to associate a collection with two open session in Chunk.inChunk" exception, is there a way to ignore that exception and simply force the sessions to save/update the shared entities ? I have no idea what hibernate would prevent from doing so.
This code basically gets executed on multiple threads at the "same" time.
var session = database.openSession(); // sessionFactory.openSession()
session.beginTransaction();
try {
// Save my entities
for (var identity : identities)
session.update(identity);
session.flush();
session.clear();
session.getTransaction().commit();
} catch (Exception e){
GameExtension.getInstance().trace("Update");
GameExtension.getInstance().trace(e.getMessage());
session.getTransaction().rollback();
}
session.close();
#Entity
#Table(...)
public class Chunk{
public List<Player> inChunk;
}
Any idea how i could solve this issue ?
I just answered a similar question a few days ago. Here the link: Multithreading Exception : "Illegal attempt to associate a collection with two open sessions"?
The issue you are facing is that you load an entity with a collection with session 1 and then somehow this object is passed to session 2 while the object is still associated to an open session 1. This simply does not work, even in a single threaded environment.
Either detach the object from the session 1 or reload the object in session 2.
I have an Equipment table that I am trying to populate with data. The table has one auto-increment id and a manufacturer column.
I stored all the manufacturer data in a List called manufacturerList.
I then looped through the entire list and for each entry create a new Equipment object with that entry and store in a variable temp. In the same loop, I try to save temp to my table using hibernate session.
However, when I run this, I am getting this error
java.lang.IllegalStateException: Session/EntityManager is closed
What is correct way to implement this loop while using hibernate?
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Equipment.class)
.buildSessionFactory();
Session session = factory.getCurrentSession();
try{
List<String> manufacturerList = new List<String>();
//populate the list here
//...
for (String manufacturer:manufacturerList) {
System.out.println(manufacturer);
Equipment temp = new Equipment(manufacturer);
session.beginTransaction();
session.save(temp);
session.getTransaction().commit();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
factory.close();
}
Thanks.
Playing around inserting and removing thing from the scope of the loop, I believe the correct ordering this particular implementation of hibernate using Session to ensure that session is not closed prematurely is as below:
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Equipment.class)
.buildSessionFactory();
Session session = factory.getCurrentSession();
try{
List<String> manufacturerList = new List<String>();
//populate the list here
//...
session.beginTransaction();
for (String manufacturer:manufacturerList) {
System.out.println(manufacturer);
Equipment temp = new Equipment(manufacturer);
session.save(temp);
}
session.getTransaction().commit();
} catch (IOException e) {
e.printStackTrace();
} finally {
factory.close();
}
A transaction should be started before anything is saved to session. And after all saves should the transaction be committed.
After changing to this, the program runs without error.
Please feel free to let me know if there is anything I am missing or could optimize.
You don't show how you acquire the session value. Apparently the session you have is closed, or was never opened properly, but that part of your code is not shown. Also, you show the closing of a factory but that variable is not involved in the snippet you show.
Generally you can keep an EntityManagerFactory around for a long time, but the EntityManager instances should have fairly short lifespans. Don't reuse the entity manager (or Session) for multiple sessions; kill it after each one and start a new one when you need it. This will make it much easier to reason about what is open or closed, and will prevent the infamous session overfill.
We have to assume that Equipment is a properly declared and managed entity type, since that isn't shown here either and seems unrelated to your error message.
So the correct way to implement your loop is to initialize an EntityManager somewhere in the class that has the loop, use it for the loop, and close it some time after the loop, but to leave the EntityManagerFactory to its own logic flow.
Also, you don't "populate a table" with Hibernate or any JPA solution. Hibernate/JPA are for an object model, not a table model. Write your entity types as object types (no artificial "ID" key field!). Let JPA handle the mapping instead of your code doing it. Think objects, not tables.
Bulk operations are often better done with JDBC than JPA. JPA is for modeling entities, not managing data.
I'm questioning my implementation of the lock in various scenarios and I'd like to have some suggestion by user more expert than me.
I'm using two support class, named HibernateUtil and StorageManager.
HibernateUtil
Simply returns a singleton instance of session factory; obviously, it creates the session factory on the first call.
StorageManager
Encloses the common operations between the various entities. On its creation, it gets the session factory from HibernateUtil and store it into a static variable.
This class implements the session-per-request (or maybe session-per-operation) pattern and for this reason for every kind of request it basically does this things in sequence:
open a new session (from the session factory previously stored)
begin a new transaction
execute the specific request (depends on the specific methods of StorageManager invoked
commit transaction
close session
Of course, comment on this style are really appreciated.
Then, there are basically 3 categories of operations that implements point 3
Insert, Update or Delete entity
session.save(entity);
// OR session.update(entity) OR session.delete(entity)
session.buildLockRequest(LockOptions.UPGRADE).lock(entity);
Get entity
T entity = (T) session.byId(type).with(LockOptions.READ).load(id);
// There are other forms of get, but they are pretty similar
Get list
List<T> l = session.createCriteria(type).list();
// Same here, various but similar forms of get list
Again, don't know if it is the right way to implement the various actions.
Also, and this is the real problem, whenever an error occurred, it is impossible to access to the datastore in any way, even from command line, until I manually stop the application that caused the problem. How can I solve the problem?
Thanks in advance.
EDIT
Some more code
This is the code for the parts listed above.
private void createTransaction() // Parts 1 and 2 of the above list
{
session = sessionFactory.openSession();
transaction = null;
try
{
transaction = session.beginTransaction();
}
catch (HibernateException exception)
{
if (transaction != null) transaction.rollback();
exception.printStackTrace();
}
}
private void commitTransaction() // Part 4 of the above list
{
try
{
transaction.commit();
}
catch (HibernateException exception)
{
if (transaction != null) transaction.rollback();
exception.printStackTrace();
}
}
private void closeSession() // Part 5 of the above list
{
try
{
// if (session != null)
{
session.clear();
session.close();
}
}
catch (HibernateException exception)
{
exception.printStackTrace();
}
}
public void update(T entity) // Example usage for part 3 of the above list
{
try
{
this.createTransaction();
session.update(entity);
// session.buildLockRequest(LockOptions.UPGRADE).lock(entity);
this.commitTransaction();
}
catch (HibernateException exception)
{
exception.printStackTrace();
}
finally
{
this.closeSession();
}
}
Your error case (the real problem) indicates you are not following the typical transaction usage idiom (from the Session Javadoc):
Session sess = factory.openSession();
Transaction tx = null;
try {
tx = sess.beginTransaction();
//do some work, point 3
tx.commit();
} catch (Exception e) {
if (tx!=null) tx.rollback();
throw e;
} finally {
sess.close();
}
Note the catch and finally blocks which ensure any database resources are released in case of an error. (*)
I'm not sure why you would want to lock a database record (LockOptions.UPGRADE) after you have changed it (insert, update or delete entity). You normally lock a database record before you (read and) update it so that you are sure you get the latest data and no other open transactions using the same database record can interfere with the (read and) update.
Locking makes little sense for insert operations since the default transaction isolation level is "read committed"(1) which means that when a transaction inserts a record, that record only becomes visible to other database transactions AFTER the transaction that inserts the record commits. I.e. before the transaction commit, other transactions cannot select and/or update the newly "not yet comitted" inserted record.
(1) Double check this to make sure. Search for "hibernate.connection.isolation" in the Hibernate configuration chapter. Value should be "2" as shown in the Java Connection constant field values.
(*) There is a nasty corner case when a database connection is lost after a database record is locked. In this case, the client cannot commit or rollback (since the connection is broken) and the database server might keep the lock on the record forever. A good database server will unlock records locked by a database connection that is broken and discarded (and rollback any open transaction for that broken database connection), but there is no guarantee (e.g. how and when will a database server discover a broken database connection?). This is one of the reasons to use database record locks sparsely: try to use them only when the application(s) using the database records cannot prevent concurrent/simultaneous updates to the same database record.
Why don't you use Spring Hibernate/JPA support. You can then have a singleton SessionFactory and transaction boundaries are explicitly set by using #Transactional.
The session is automatically managed by the Transactioninterceptor so no matter how many DAOs you call from a service, all of those will use the same thread-bound Session.
The actual Spring configuration is much easier than having to implement your current solution.
If you don;t plan on using Spring then you have to make sure you are actually implementing the session-per-request patterns. If you are using session-per-operation anti-pattern then you won't be able to include two or more operations into a single unit-of-work.
The session-per-request pattern requires an external interceptor/AOP aspect to open/close and bind the current session to the current calling thread. You might want to configure this property also:
hibernate.current_session_context_class=thread
so that Hibernate can bind the current Session into the current thread local storage.
I am using Jax-Rs web service and hibernate. I am doing an insert, and if that insert fails, I should catch the exception and write a log in the database. Here is the code below
#PersistenceContext(unitName = "primary")
private EntityManager em;
#GET
#Produces("application/json")
#Path("/siminsert")
public String testSimInsert()
{
try
{
Query query = em.createNativeQuery("insert into sim(iccid, imsi, msisdn, state) values ('890000000000010000512223334', '001010000100004', '50100005', 'A')");
query.executeUpdate();
}
catch(Exception ex)
{
Query query1 = em.createNativeQuery("insert into notifications(message) values ('An error occured')");
query1.executeUpdate();
}
return "OK";
}
With this code, the first insert will fail since we are inserting more than allowed characters in the iccid field. I want to be able to log that error by doing an insert in the notifications table as shown in the code but i get the error:
javax.persistence.TransactionRequiredException: Executing an update/delete query
Any ideas on what can be done?
The problem is with the transaction handling. Although it is not visible from your code, to insert the sim record, you need to begin a transaction. Now since an error has occurred, this transaction will rollback. You need to write your log statement in a separate transaction. So in the catch block, you need to starta new transaction, write the log and commit the transaction, without touching the transaction state of the previous transaction. Hence you cannot use the same hibernate session.
The first problem is that you don't open a Transaction and this is mandatory for DML operations (UPDATE/INSERT/DELETE):
Whenever a Hibernate Session operation throws an exception you have to discard the current Session and start anew. This is because the failing operation might have left the Session in an inconsistent state.
What you can do is to discard all changes:
em.clear();
and run your second query. Make sure you operate with 2 transactions. If one fails you start with a new one to insert into the notification table.
EntityTransaction tx = null;
try {
tx = em.getTransaction();
tx.begin();
Query query = em.createNativeQuery("insert into sim(iccid, imsi, msisdn, state) values ('890000000000010000512223334', '001010000100004', '50100005', 'A')");
query.executeUpdate();
tx.commit();
}
catch(Exception ex) {
if(tx != null) {
tx.rollback();
em.clear();
try {
tx.begin();
Query query1 = em.createNativeQuery("insert into notifications(message) values ('An error occured')");
query1.executeUpdate();
tx.commit();
} catch(Exception ex) {
tx.rollback();
}
}
}
finally {
em.close();
}
A better approach is to have all the business logic encapsulated in #Transactional services. So you have a :
SimService.insert(Sim sim);
NotificationService.error(ErrorMessage message);
Both methods are #Transactional so if the first one fails with an exception, your controller can call the NotificationService.
First of all, as other answers have pointed, please use transactions, rollback, etc.
For better understanding, do read about Container Managed transactions and bean managed transactions. Vald's code should help.
On side notes,
You should use finally block to clean up resources, which you seem to missing.
You seem to use #GET for inserting data. I assume that you have just written this for convenience of this SO question. If not, a brief intro to REST and HTTP methods should help.
For better understanding and maintenance of this code a couple of years down the line, I would suggest that you separate 'normal insert' and 'exceptional insert' statements in maybe two different methods/places. As such in both scenarios you seem to be returning "OK". And probably it would be better to separate them (normal and exceptional scenarios) if they are located in different places.
Consider this simple Hibernate scenario:
session = getHibernateSession();
tx = session.beginTransaction();
SomeObject o = (SomeObject) session.get(SomeObject.class, objectId);
tx.commit();
This code produces the following exception:
org.hibernate.TransactionException: Transaction not successfully started
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:100)
at com.bigco.package.Clazz.getSomeData(Clazz.java:1234)
What's going on?
Well, it looks like once we reach the tx.commit() line, the transaction has already been committed. My only guess is that Hibernate already commits the transaction when get()ing the object.
The fix for this is simple:
// commit only if tx still hasn't been committed yet (by hibernate)
if (!tx.wasCommitted())
tx.commit();
This is a really old question and I figure you've already solved it (or given up on Hibernate) but the answer is tragically simple. I'm surprised no one else picked it up.
You haven't done a session.save(o), so there is nothing in the transaction to commit. The commit may still not work if you haven't changed anything in the object, but why would you want to save it if nothing has changed?
BTW: It is also perfectly acceptable to do the session.get(...) before the session.beginTransaction().
I got to know that this is already solved; even though I am posting my answer here.
I haven't found wasCommitted() method on the transaction.
But the following code worked for me:
// commit only, if tx still hasn't been committed yet by Hibernate
if (tx.getStatus().equals(TransactionStatus.ACTIVE)) {
tx.commit();
}
One situation this can happen in is when the code is in an EJB/MDB using container-managed transactions (CMT), either intentionally or because it's the default. To use bean-managed transactions, add the following annotation:
#TransactionManagement(TransactionManagementType.BEAN)
There's more to it than that, but that's the beginning of the story.
remove session.close(); from your program as few of the bigger transaction require more time and while closing the connection problem get occurred. use session.flus() only.
You should check weather you have used this session.getTransaction().commit(); or rollback command as higher version of hibernate removed manual code interaction by using
#Transactional(propagation = Propagation.SUPPORTS, readOnly = false, rollbackFor = Exception.class) annotation you can avoid any transaction related exception.
Above solutions were not helpful for me and that is why I want to share my solution.
In my case, I was not using #Column annotation properly in one of my entity. I changed my code from
#Column(columnDefinition = "false")
private boolean isAvailable;
to
#Column(columnDefinition = "boolean default false")
private boolean isAvailable;
And it worked.
My create method in dao
public int create(Item item) {
Session session = sessionFactory.getCurrentSession();
try {
int savedId = (int) session.save(item);
return savedId;
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
return 0; //==> handle in custom exception
}
}