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!
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'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 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
}
}