Update database in the middle of the transaction, possible? - java

#Transactional
public void start() {
...
...
int result = entityManager
.createQuery("update Users set name=" + value + " where user.id=5").executeUpdate();
.....
}
Above code gives javax.persistence.TransactionRequiredException exception.
Update database in the middle of the transaction, possible ?
Any suggestions ?
Thanks.
A.
I just wonder if

This is a runtime exception which is thrown by the persistence provider when a transaction is required but is not active. A transaction is required because the start method is annotated as transactional. To get rid of the exception, you'll have to investigate why the line is called out of a transaction context.
A database update may be possible during a (different) transaction. Depends on the tables that are locked by the active transaction and on the transaction strategy. But in this case, it looks like you need to activate a transaction before you enter the start method.
With JPA you'd do something like this:
em = emf.createEntityManager();
tx = em.getTransaction();
tx.begin(); // now a transaction is active
start(); // call your method
// call other methods...
tx.commit(); // now the update is actually done
em.close();
Note - this is close to pseudo code, exception handling is missing in this example.

Related

rollback when occured Exception manually

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();
}

JPA update query without transaction - is transaction mandatory?

I'm trying to do insert via a native query with JPA, but I don't want to create a transaction:
Query query = em.createNativeQuery("INSERT INTO person (id, firstname, lastname) VALUES ('1','Ronnie','Dio')");
int count = query.executeUpdate();
this ends up with the TransactionRequiredException:
javax.persistence.TransactionRequiredException: Executing an update/delete query
at org.hibernate.ejb.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:99)
However, if I unwrap the Hibernate session and execute the same query - it works:
Session session = em.unwrap(Session.class);
Query query = session.createSQLQuery("INSERT INTO person (id, firstname, lastname) VALUES ('1','Ronnie','Dio')");
int count = query.executeUpdate();
So my question is - what am I doing wrong in the first sample? Is transaction really required to execute insert/update/delete queries in JPA?
If it is, is there any link to documentation that specifies it clearly? If it's not - what am I doing wrong?
It seems you are building an application that does not run inside a container supporting JTA managed transactions. In such an environment, you have to handle/manage transactions for yourself, i.e., you have to control when transactions are opened, committed or rolled back. This scenario is referred to as resource-local entity manager.
In section 7.5.2 Resource-local EntityManagers of the official JPA 2.2 specification (p. 345) we find:
An entity manager whose transactions are controlled by the application through the EntityTransaction
API is a resource-local entity manager. A resource-local entity manager transaction is mapped
to a resource transaction over the resource by the persistence provider. Resource-local entity managers
may use server or local resources to connect to the database and are unaware of the presence of JTA
transactions that may or may not be active
Further down in the spec document the EntityTransaction interface is given. It allows you to call
begin() to "Start a resource transaction"
commit() to "Commit the current resource transaction, writing any
unflushed changes to the database."
rollback() to "Roll back the current resource transaction." in case something went wrong on the database side while committing changes.
That's for the theory part.
For your code example, you might want to change it as follows:
EntityTransaction tx = null;
try {
tx = em.getTransaction();
// start a new transaction, i.e. gather changes from here on...
tx.begin();
// do your changes here
Query query = em.createNativeQuery("INSERT INTO person (id, firstname, lastname) VALUES ('1','Ronnie','Dio')");
int count = query.executeUpdate();
// write changes to database via commit
tx.commit();
} catch(RuntimeException re) {
if(tx != null && tx.isActive()) {
// ensure no semi-correct changes are pending => cleanup
tx.rollback();
}
// handle exception, log it somewhere...
}
This should avoid the TransactionRequiredException you encounter. Moreover, you should avoid the use createNativeQuery, as you are mis-using a fundamental concept of an Object-Relational-Mapper (ORM), i.e. the mapper will transform objects into tuples and vice versa for you. This - in general - should ease the pain of writing insert/update queries for a large amount of domain entities.
Have a look into section 3.1.1 EntityManager Interface (p. 65) of the spec document linked above and make use of the methods
persist(..) - "Make an instance managed and persistent." or
merge(..) - "Merge the state of the given entity into the current persistence context."
For more infos on the difference of both approaches see the posts here and here.
Hope it helps.
Instead of creating a native query , I would recommend to create a JPA entity for the person and with JPARepository you can use a save method for the person to insert any record.

JPA multiple transactions - commit not handled properly after a 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!

Hibernate insert in catch block

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.

Nested transactions - Rollback scenario

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.

Categories