I am working with JDBC in setAutoCommit(false) mode.
Inside the same transaction I execute multiple Statements for insert, update and select.
The question is: should these changes be visible inside the same transaction for subsequent operations? What specific rules are there? Is it vendor-specific? Is it driver-specific? Or whatever?
I work with mysql-connector-java 8.0.11, JDBC 4.2, java 8 and in my case no changes are visible, e.g.
try (Connection conn = dataSource.getConnection()) {
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
conn.setAutoCommit(false);
try (PreparedStatement statement = conn.prepareStatement(sqlInsert, PreparedStatement.RETURN_GENERATED_KEYS)) {
statement.setString(1, "testData");
statement.executeUpdate();
System.out.println("After insertion:");
// jdbc findAll impl.
findAll().forEach(System.out::println);
conn.commit();
} catch (SQLException ex) {
conn.rollback();
}
} catch (SQLException e) {
throw new RuntimeException();
}
Here, the inserted uncommitted data is actually NOT visible inside same transaction!
However, if I do the same stuff with Spring's jdbcTemplate and Spring's DataSourceTransactionManager, like this:
final DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
defaultTransactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
TransactionStatus transaction = txManager.getTransaction(defaultTransactionDefinition);
try {
// jdbcTemplate insert impl.
insert("testData");
System.out.println("After insertion:");
// jdbcTemplate findAll impl.
findAll().forEach(System.out::println);
txManager.commit(transaction);
} catch (Exception e) {
txManager.rollback(transaction);
}
The inserted uncommitted data is actually visible inside same transaction!
I found no explanation in JDBC specification, and am confused with it.
Please, explain this behavior.
P.S. I am aware of different isolation levels, but they basically apply to concurrent transactions, not to the same one.
With the additional information in your comments that the findAll creates a new connection, the problem is that you are using one connection+transaction to update the data, and another connection+transaction to select the data.
A select query is affected by a transaction: the transaction determines what is visible or not. You have connection 1 with transaction 1 where you modified data (not yet committed), and connection 2 with transaction 2 that performs a select. Given transaction 1 is not yet committed, transaction 2 cannot see the data changed by transaction 1.
If you use JdbcTemplate and a Spring transaction manager, things change, because Spring does additional work to ensure that it uses the same connection and transaction for both the update and the select.
Related
#GetMapping("trans")
#Transactional()
public String primaryTrans() {
User u1 = new User(0,"test","test#email.com");
us.save(u1);
User u2 = new User(0,"test1","test1#email.com");
us.save(u2);
secondaryTrans();
return "index";
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
private void secondaryTrans() {
// TODO Auto-generated method stub
User u2 = new User(0,"test2","test3#email.com".repeat(300));
us.save(u2);
}
Here i am manually raising DATA TOO LONG exception from secondary transaction, But it causes primary transaction also rolled back. How can we make sure that primary transaction to be committed irrespective of secondary transaction
In this case, since the second method is called from the same class, the second transaction is most likely not created. Springs transactional support uses AOP proxies to create transactions. The docs contain a description on why this will not work.
The simplest way is to catch the exception thrown from secondaryTrans() method, so just wrap secondaryTrans() into try-catch block:
try {
secondaryTrans();
} catch (Exception e) {
//...
}
I have created an asynchronous service for a long running stored procedure call. Things work good but the transaction is not getting timed out after the specified value given in the timeout attribute of the transactional annotation..The structure of the code is given below (not the real one...just skeleton...ignore semantics/syntax)
//asynchronous service
#override
#async("myCustomTaskExecutor")
#Transactional(rollbackfor=Exception.class,timeout=600)
public void serviceMethod(){
//repository method is invoked.
repository.callStoredProcedure();
}
//Repository method in the Repository class
#Transactional(rollbackfor=Exception.class,timeout=600)
public void callStoredProcedure(){
//Stored procedure is called from the private method using hibernate doWork implementation.
privateCallmethod();
}
private void privateCallmethod() throws ApplicationException{
Session session = null;
try{
session = entityManager.unwrap(Session.class);
session.doWork(new Work(){
#Override
public void execute(Connection connection) throws SQLException {
OracleCallableStatement statement =null;
try{
//using hibernate 4.x and ref cursors are used...so went on with this approach..
//suggest if there is some better approach.
String sqlString =“{begin storProcName(?,?)}”;
statement = connection.prepareCall(sqlString);
statement.setInt(1,5);
statement.setString(2,“userName5”);
statement.executeUpdate();
}
catch(Exception e){
throw RunTimeException(e.getMessage);
}
finally{
if(statement != null)
statement.close();
}
}
}
});
}
catch(Exception e){
throw ApplicationException(e.getMessage);
}
//Not using Final block to close the session.Is it an issue ?
}
delay is happening in the stored procedure side(Thread.sleep(700) are not used) yet transaction is not timed out...
Questions :
I guess #Transactional is enough on the service method alone...give little bit insight on correct approach of using #Transactional annotation
for this code setup.
Will the #Transactional works for the JDBC calls inside the doWork Interface implementation...is that whats the issue is ?
Some article suggest to use oracle.jdbc.readTimeout or setQueryTimeout in the CallableStatement... Is it the right way to achieve this.
Kindly point out the mistakes and explain the causes
If #Transactional Annotated method is not the entry point to the class, it will not be transactional unless you enable load time weaving (Spring default is Compile time weaving) https://stackoverflow.com/a/17698587/6785908
You should invoke callStoredProcedure() from outside this class, then it will be transactional. If you invoke serviceMethod() which in turn invokes callStoredProcedure(), then it will not be transactional
I used setQueryTimeout() approach to resolve the issue as #Transactional timeout does not work with the hibernate dowork() method...I guess its due to the hibernate work executes in different thread and it low level JDBC methods to invoke the store procedures...
NOTE: This particular application uses very spring 3.x version and hibernate 4.x with JPA 2.0 spec...little outdated versions
I have a grails (2.5.2) app, with a mysql and a NoSQL interaction. There's a main/principal service method that call 2 other methods:
class mainService {
static transactional = false
NoSQLDataAccessService noSQLDataAccessService
// main/principal method
#Transactional
void save(json){
// (1) creating domain entities from json
addNewDomainEntities(entities)
// (2)
noSQLDataAccessService.set(json)
}
#Transactional
void addNewDomainEntities(entities){
// save the entities in a mysql schema and use save(flush:true)
// because i need the generated id's
}
}
As you can see, this mainService creates new domain entities (1), flushing the session to get the id's. Then, i call other service method (2) that store the json in a NoSQL schema:
class NoSQLDataAccessService(){
static transactional = false
void set(json){
try{
// save the json in a NoSQL schema
} catch(Exception ex){
// if fails, i log the exception and throws it again
throws ex
}
}
}
But, sometimes the noSQLDataAccessService.set() fails by external causes and the entities created before still persist in the mysql db.(this is the problem)
The save method, that contains all this program execution, is marked like #Transactional, so if the noSQLDataAccessService.set() throws an exception, all the changes made should be not applied because the rollback. I'm right?
You probably have to throw an RuntimeException, not an Exception, to force a rollback as per this StackOverflow conversation. Instead of:
throws ex
you might try:
throw new RuntimeException(ex)
Further, I would recommend you be explicit about your transaction isolation. Perhaps something like:
#Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.SERIALIZABLE)
I need to manage 2 Dao methods in a single transaction ,failure of either of them should rollback the other. The calling method is in the service layer. The technology used in Spring and Hibernate native sql query. Is there a way to achieve this?
Calling Method::
#Transactional(propagation= Propagation.REQUIRED)
public String save(AllowFileTypesForm formBeanObj,Hashtable global)
Called Method1::
public boolean deleteData( String strTableName,String strWhereClause) {
Session session = sessionFactory.getCurrentSession();
String strSqlQuery = null;
boolean deleted=false;
strSqlQuery = "DELETE FROM Persons where" + strWhereClause;
try {
Query query=session.createSQLQuery(strSqlQuery);
if (query.executeUpdate() <= 0) {
throw new SQLException("No row deleted from table " +strTableName);
}
else{
deleted=true;
}
}
catch(Exception e){
e.printStackTrace();
}
return deleted;
}
Similar to this method there is another method which deletes data from some other table.
It will not rollback because you are catching the exceptions.
Spring's transaction management works through exceptions exiting the transaction boundary which are detected by the #Transactional AOP.
If you catch a sql exception for logging or something you must rethrow or throw a new exception to kick off rollback.
Add #Transactional on the service method from where you are calling those DAO methods. This article nicely sums up how it works.
I'm using Spring 2.5 transaction management and I have the following set-up:
Bean1
#Transactional(noRollbackFor = { Exception.class })
public void execute() {
try {
bean2.execute();
} catch (Exception e) {
// persist failure in database (so the transaction shouldn't fail)
// the exception is not re-thrown
}
}
Bean2
#Transactional
public void execute() {
// do something which throws a RuntimeException
}
The failure is never persisted into DB from Bean1 because the whole transaction is rolled back.
I don't want to add noRollbackFor in Bean2 because it's used in a lot of places which don't have logic to handle runtime exceptions properly.
Is there a way to avoid my transaction to be rolled back only when Bean2.execute() is called from Bean1?
Otherwise, I guess my best option is to persist my failure within a new transaction? Anything else clean I can do?
This is one of the caveats of annotations... your class is not reusable!
If you'd configure your transactions in the XML, if would have been possible.
Assuming you use XML configuration: if it's not consuming expensive resources, you can create another instance of bean2 for the use of the code you specified. That is, you can configure one been as you specified above, and one with no roll back for exception.