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.
Related
in updateUser method: If an exception occurs when calling one of the macro services (like : updateUserContact,updateAccountContact), the updateUser operation must be rollback.
How do I perform a transaction operation to create, update and delete manually in Java?
In the creation method, when an event exception occurs, I delete the related records.
But I do not know what to do in the update and delete.
if invoke userContactStub.grpcUpdate has Exception , i must rollback userAcount.
Does anyone have any suggestions about the rollback transaction in the update ?
I use jpa, grpc(To connect micro services),springBoot.
each micro service has a datasource.
//updateUser
AdminUser adminUser = findById();
adminUser.setFirstName(adminUserModel.getFirstName());
adminUser.setLastName(adminUserModel.getLastName());
adminUser.setPassword(PasswordEncoderGenerator.generate(adminUserModel.getPassword()));
adminUser.setUsername(adminUserModel.getUsername());
adminUser.setDateOfBirth(CalendarTools.getDateFromCustomDate(adminUserModel.getDateOfBirth()));
adminUser.setGender(etcItemService.findByIdAndCheckEntity(adminUserModel.getGender_id(), GenderEnum.class,null,true));
adminUser = adminUserRepository.save(adminUser);
//update userAcount For Admin
//call grpcUpdate
this.userAcountStub.grpcUpdate(createRequestModel);
//update UserContact For Admin
//call grpcUpdate
this.userContactStub.grpcUpdate(createRequestModel);
adminUserModel.setId(adminUser.getId());
return adminUserModel;
What framework are you using? Are u using JPA?
Assume you are using JPA, you don't have to worry about it. JPA shell pretty much guarantee your data integrity if exception occurred (usually it rollback manually).
However I am not sure about how to rollback a database translation if one of the micoservice you called has thrown an exception.
if you are using JPa (hibernate?), you can simply add #Transactional annotation for rollback on top of create , update methods etc. İt handles this job and solves the problem.
try(Connection conn = DriverManager.getConnection(dbURL,dbUser,dbPassword);){
conn.setAutoCommit(false);
// perform operations such as insert, update, delete here
// ..
// if everything is OK, commit the transaction
conn.commit();
} catch(SQLException e) {
// in case of exception, rollback the transaction
conn.rollback();
}
I have the following code (simplified for the sake of the question)
EntityManager em = EMF.get().createEntityManager();
TypedQuery<T> query = em.createQuery...
for(T result : em.getResultlist()) {
try {
em.getTransaction().begin();
// do some stuff, update the T object
em.getTransaction().commit();
} catch(Exception e) {
// something has gone wrong, rollback the current transaction
if(em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
}
}
em.close();
I am using JPA EclipseLink.
Basically, I want to update a set of tasks, take action and update their statuses. Sometimes the task action fails and I need to revert the change.
This works perfectly fine UNTIL something goes wrong with one of the transactions and the roll back is called. At that point any subsequent transaction commit IS NOT performed, ie the database is not updated.
I read "on rollback all objects managed are detached". I guess this is where the problem is... if correct how could I implement the desired behaviour?
Any help would be vastly appreciated!
I'm running certain process that inserts new elements in DB from a for loop. I'm using JPA (Eclipselink), and sometimes there's a problem with the transation status. This is the case:
One of the INSERTS doesn't work (Primary Key duplicated)
After that, all the inserts will fail (Exception Description: Transaction is currently active).
for (Element l:e){
try{
//Should I add: if(!em.getTransaction().isActive())
em.getTransaction().begin();
em.createNativeQuery("INSERT INTO...").executeUpdate();
em.getTransaction().commit();
}
catch(Exception ep)
{
//right now I don't do anything here
}
}
I get that what is happening is that, since the commit in 1) didn't work, the transaction didn't finish, so the next em.getTransaction().begin() will find an already active transaction.
I have two ideas:
A) Before em.getTransaction().begin(), check if the transcation is active, and only if it is not, call begin(); otherwise, create query and commit.
B) Do something within the catch block. And here's my doubt... Should I call clear()? flush()? close()?
Which one looks better?
Thanks!
An exception thrown by ElementManager.Query does not rollback the active transaction. I see two options here:
Rollback the transaction by yourself within the catch clause with use of em.getTransaction().rollback().
Instead of inserting data with query use the preferred way based on EntityManager.persist whose exceptions cause an automatic rollback (in your particular case this will lead to javax.persistence.EntityExistsException).
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 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).