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.
Related
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.
Using Hibernate I need to update multiple objects by calling for eg. updateDetails() function shown below:
function updateDetails(){
Session session = this.getSessionFactory().openSession();
Employee emp=(Employee )session.load(Employee.class, empId);
emp.salary(2000);
Account acc=(Account)session.load(Account.class, accId);
account.setTotal(2000);
Transaction tx=session.beginTransaction();
session.update(emp);
session.update(acc);
tx.commit();
result=true;
session.close();
}
what is the best way of doing this ?
does Updating multiple objects of same or different type in same session will cause any problem in hibernate?
Why not. You can do that. Hibernate allows.
Since you are using Transaction management the answer is depends on your context. If you want to save both Objects regardless of saving another make them update in individual transactions.
If you want to save the whole info successfully and want to revert everything if any of the update fail keep them in the current format.
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).
My question is related to Transactions and Exceptions
Requirements:
I have 10 records to insert into database table. And after inserting every record, I insert data into another table. So if inserting to second table fails, I want to rollback that record.
Ex.
Say handle cash transfer (from one account to account) for 10 persons at a time.
pseudo code:
------------- Start of EJB method
for(int i = 0; i < TransferRecords.length; i++)
{
try
{
//Deduct cash from TransferRecord.accountFrom --- Includes use of Hibernate Session
//Add cash in TransferRecord.accountTo -- Includes use of Hibernate Session
} catch(AppException exception)
{
//Rollback the transaction only for this particular transfer (i)
// But here when I go for next record it says session is closed
}
}
---------End of EJB method
Here AppException is created with #ApplicaitonException(rollback=true) annotion.
The functionality we want is: Even if the transaction fails for TransferRecord (say 2), I want the data to be committed for record 0, record 1, record 3, record 4 (etc... and but not for record 2)
But the issue here is: when TransferRecord 2 fails and when I move to TransferRecord 3, I get "Session Closed" error.
My doubts are:
1. Is this a right approach? or should I run the for loop(for each TransferRecord) outside of the EJB
2. How can I make sure that session is not closed but transaction is rolled back (only for that for particular failed transaction)
Thank you in advance.
I am using EJB3, Hibernate 3.x, Jboss 4.2.x and I am using Container Managed Transaction.
Is this a right approach?
No, with CMT, you method is your transactional unit. So here, all your TransferRecord and handled in a same and unique transaction.
By the way, how do you rollback the transaction? Do you propagate a RuntimeException or do you call setRollbackOnly()? I'm just curious.
Or should I run the for loop (for each TransferRecord) outside of the EJB?
Why outside? Nothing forces you to do that. If you want to process each TransferRecord in its own transaction, you should pass them to another EJB method (the code below is inspired by this answer):
// supposing processRecords is defined on MyStatelessRemote1 and process defined on MyStatelessLocal1
#Stateless
#TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
#EJB
private MyStatelessLocal1 myBean;
public void processRecords(List<TransferRecord> objs) {
// No transactional stuff so no need for a transaction here
for(Object obj : objs) {
this.myBean.process(obj);
}
}
#TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
public void process(TransferRecord transferRecord) {
// Transactional stuff performed in its own transaction
// ...
}
}
How can I make sure that session is not closed but transaction is rolled back (only for that for particular failed transaction)
I think I covered that part.
The only option you have here is either to use user transaction instead of container managed transaction of loop outside the bean so that everytime you enter the bean you get fresh entity manager with associated transaction and connection (basically session)
I think that you can create two separated transactions, the first for the TransferRecord(1) (doing a commit once everything is fine) and then starting other TX for all the TransferRecord(i+1).
Another approach is using savepoints, being able to rollback and discard everything past that savepoint (but I like prefer the first approach).
Regards.