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.
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 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.
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).
A(){
con.begin;
.........
.........
B();
........
........(con.rollback;)
con.commit;
}
B{
con.begin;
.......
.......
con.commit;
}
In the above code, I begin a new DB transaction at A(). It executes some transaction successfully. After that B() starts executing and it also executes some transaction successfully and now the control returns to A(). At this point some exception occurs and I do a rollback. I would like to know whether the transaction which succeeded in B() will rollback or not.
The short answer, no. The long answer is as follows.
Support for nested transactions in Java depends on various variables at play.
Support for Nested transactions in JTA
First and foremost, if you are using JTA, it is upto to the Transaction Manager to support nested transactions. Any attempt to begin a transaction may result in a NotSupportedException being thrown by a Transaction Manager (that does not support nested transactions) if there is an attempt to start a new transaction in a thread that is already associated with a transaction.
From the Java Transaction API 1.1 specification:
3.2.1 Starting a Transaction
The TransactionManager.begin method starts
a global transaction and associates
the transaction context with the
calling thread. If the Transaction
Manager implementation does not
support nested transactions, the
TransactionManager.begin
methodthrowsthe NotSupportedException
whenthe calling thread is already
associated with a transaction.
Support for Nested transactions in JDBC
JDBC 3.0 introduces the Savepoint class, which is more or less similar to the concept of savepoints in the database. Savepoints have to be initialized using the Connection.setSavepoint() method that returns an instance of a Savepoint. One can roll back to this savepoint at a later point in time using the Connection.rollback(Savepoint svpt) method. All of this, of course, depends on whether you are using a JDBC 3.0 compliant driver that supports setting of savepoints and rolling back to them.
Impact of Auto-Commit
By default, all connections obtained are set to auto-commit, unless there is a clear deviation on this front by the JDBC driver. This feature, if enabled, automatically rules out the scope of having nested transactions, for all changes made in the database via the connection are committed automatically on execution.
If you disable the auto-commit feature, and choose to explicitly commit and rollback transactions, then committing a transaction always commits all changes performed by a connection until that point in time. Note, that the changes chosen for commit cannot be defined by a programmer - all changes until that instant are chosen for commit, whether they have been performed in one method or another. The only way out is to define savepoints, or hack your way past the JDBC driver - the driver usually commits all changes performed by a connection associated with a thread, so starting a new thread (this is bad) and obtaining a new connection in it, often gives you a new transaction context.
You might also want to check how your framework offers support for nested transactions, especially if you're isolated from the JDBC API or from starting new JTA transactions on your own.
Based on the above description of how nested transaction support is possibly achieved in various scenarios, it appears that a rollback in your code will rollback all changes associated with the Connection object.
That looks like poor transaction management i'm afraid. It would be good if you handle the commits and rollbacks from the callers to A and B instead.
A()
{
//business code A
B();
//more business code A
}
B()
{
//business code B
}
DoA()
{
try
{
con.begin();
A();
con.commit();
}
catch(Exception e)
{
con.rollback();
}
}
DoB()
{
try
{
con.begin();
B();
con.commit();
}
catch(Exception e)
{
con.rollback();
}
}
As per your code, in A() you are starting transaction. Then jump to B() where you start transaction again, which in turn will commit all previous transaction. Then at end of B(), transaction is explicitly committed. At this point, all your code is committed. Now the code return to A() and remaining code is processed. In case of exception, only this part after B() call will be rolled back.
You can use Java.SQL's built-in SavePoint function in Postgres 8 and up.
Connection conn = null;
Savepoint save = null;
DatabaseManager mgr = DatabaseManager.getInstance();
try {
conn = mgr.getConnection();
proc = conn.prepareCall("{ call writeStuff(?, ?) }");
//Set DB parameters
proc.setInt(1, stuffToSave);
proc.setString(2, moreStuff);
//Set savepoint here:
save = conn.setSavepoint();
//Try to execute the query
proc.execute();
//Release the savepoint, otherwise buffer memory will be eaten
conn.releaseSavepoint(save);
} catch (SQLException e) {
//You may want to log the first one only.
//This block will attempt to rollback
try {
//Rollback to the Savepoint of prior transaction:
conn.rollback(save);
} catch (SQLException e1) {
e1.printStackTrace();
}
}
When a SQL-exception occurs, the current transaction is rolled-back to the SavePoint, and the remaining transactions may occur. Without the roll-back, subsequent transactions will fail.