Spring Transactions not working - JDBCTemplate is reading uncommitted data - spring

The following Method inserts two records (but doesn't commits them at this point) and it then tries to read one of the uncommitted records from the previous statements. I wrapped the code with Transaction, and set the isolationLevel to "READ_COMMITTED" but this doesn't seems to be working. The read/"SELECT" statement is reading the uncommitted records.
How is this possible? Where am I going wrong? Please see the code below and help me out. I would be really thankful ~
Note :
I am using BoneCP to get the DataSource.
dbConnectionPool.initConnectionPool(dbName) , will fetch a BoneCPDataSource.
#Override public void testDBCalls() {
dBConnectionPool.initConnectionPool("titans");
DataSource dataSource = dBConnectionPool.getDataSource("titans");
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
definition.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
DataSourceTransactionManager txManager = new DataSourceTransactionManager(dataSource); TransactionStatus
transactionStatus = txManager.getTransaction(definition);
try {
try {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sql = "INSERT INTO groundwater(external_id,source_type) VALUES (12, 13);";
jdbcTemplate.update(sql);
System.out.println("Successfully inserted - 1");
String sql2 = "INSERT INTO groundwater(external_id, source_type,) VALUES(123,45);";
jdbcTemplate.update(sql2);
System.out.println("Successfully inserted - 2");
String sql3 = "select gw_id from groundwater where external_id= 123;";
System.out.println("Result : "+jdbcTemplate.queryForInt(sql3));
txManager.commit(transactionStatus);
System.out.println("Commiting the trasaction...");
} catch (Exception e) {
e.printStackTrace();
txManager.rollback(transactionStatus);
System.out.println("Rolling back the transaction");
}
} finally {
try {
dataSource.getConnection().close();
System.out.println("Closing the connection ...");
} catch (SQLException e) {
e.printStackTrace();
}
}
}

As #M.Denium explained in the comment, I was trying to do everything from a single transaction. Isolation Levels are meant for maintaining consistency across different transactions. I was still learning the concepts, so I took it in a wrong way.

Related

How to rollback a transaction on any Exception

#Override
#Transactional(rollbackFor=Exception.class)
public void deletePerson(String id) throws Exception{
PersonEntity personEntity = personrepository.findById(id);
if(personEntity == null){
throws new Exception("No Person found");
}
ElasticPersonEntity elasticPersonEntity = modelMapper.map(personEntity,ElasticPersonEntity.class);
try{
personrepository.delete(personEntity);
elasticpersonrepository.delete(elasticPersonEntity); // Error Occur as Elastic search is down
}
catch(Exception ex){
throws new RuntimeException();
}
}
In my above code, I have to save Person data at two places Cassandra and Elastic search. Hence I need to perform delete on both as well . However if due to some reason if my elastic search is down the above code does not rollback. i.e Data is deleted from Cassandra but is still present in Elastic. Any idea of how to do so. I need to do similar modification at update, create as well.
I dont understand why #Transactional(rollbackFor=Exception.class) dont do the job but as say in this topic you can try to run rollback manually
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

Mixing JdbcTemplate and raw JDBC

I am experiencing some strange behaviour which I can't easily explain. The following code runs fine:
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
statement.executeUpdate("DELETE FROM product");
} catch (Exception ex) {
throw new RuntimeException(ex);
}
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
statement.executeUpdate("INSERT INTO product ...");
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
However this code causes a deadlock:
jdbcTemplate.update("DELETE FROM product");
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
statement.executeUpdate("INSERT INTO product ...");
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
The exception is
java.sql.SQLException: Lock wait timeout exceeded; try restarting transaction
Both jdbcTemplate and dataSource are created by Spring boot and autowired
#Autowired
private DataSource dataSource;
#Autowired
private JdbcTemplate jdbcTemplate;
The statements form part of a method in a service (with the #Transactional annotation)
Can anyone explain why this happens?
If you want to use your own JDBC code that plays nice with the connections managed by Spring's transaction management you should use DataSourceUtils to obtain the connection -- see http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/jdbc/datasource/DataSourceUtils.html#getConnection-javax.sql.DataSource-
In your example, depending on the transaction configuration, the first statement using the JdbcTemplate might not be committed yet, so it would block the next JDBC statement from a different connection. Using the DataSourceUtils, both statements would be using the same connection.

Transaction with binded thread connection in Spring

I want to bind a connection to a thread, and use that connection for any JdbcTemplate calls, to finally commit the changes or do a rollback.
I'm declaring all sentences from a Groovy script, so I can't control how many SQL query will be call, that's why I have to used this method and not a TransactionalTemplate or something like that. this script will call a helper class that will use that connection and JdbcTemplate, let's call that class SqlHelper.
Right now my non-working-as-needed solution is call from groovy script to that SqlHelper to initialize a transaction:
initTransaction(ecommerce)
which calls
public void initTransaction(DataSource dataSource) {
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setReadOnly(false);
transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
// dataSourceTransactionManager.setDataSource(dataSource);
// dataSourceTransactionManager.setRollbackOnCommitFailure(true);
// dataSourceTransactionManager.setTransactionSynchronization(TransactionSynchronization.STATUS_COMMITTED);
// dataSourceTransactionManager.setDataSource(dataSource);
try {
Connection connection = DataSourceUtils.getConnection(dataSource);
connection.setAutoCommit(false);
DataSourceUtils.prepareConnectionForTransaction(connection, transactionDefinition);
} catch (CannotGetJdbcConnectionException e) {
throw new RuntimeException(e);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
After that the script will call some SQL operations, like
sqlUpdate(ecommerce, insertSentence, insertParams)
which calls
public Integer update(DataSource dataSource, String sql, Map<String, Object> paramMap) {
return new NamedParameterJdbcTemplate(dataSource).update(sql, paramMap);
}
Finally I want to finish the transaction committing the changes using
commitTransaction(dataSource)
which calls
public void commitTransaction(DataSource dataSource) {
Connection connection = DataSourceUtils.getConnection(dataSource);
try {
connection.commit();
} catch (Exception e) {
rollbackTransaction(dataSource);
}
// DataSourceUtils.resetConnectionAfterTransaction(connection, TransactionDefinition.ISOLATION_DEFAULT);
// SimpleTransactionStatus transactionStatus = new SimpleTransactionStatus(false);
// try {
// dataSourceTransactionManager.commit(transactionStatus);
// jta.commit(transactionStatus);
// } catch (TransactionException e) {
// dataSourceTransactionManager.rollback(transactionStatus);
// throw new RuntimeException(e);
// }
}
private void rollbackTransaction(DataSource dataSource) {
Connection connection = DataSourceUtils.getConnection(dataSource);
try {
connection.rollback();
} catch (SQLException e) {
throw new RuntimeException(e);
}
DataSourceUtils.resetConnectionAfterTransaction(connection, TransactionDefinition.ISOLATION_DEFAULT);
}
I left commented blocks of some testing to show you what approaches I tried. I don't know very well how Spring transaction works, so I'm just trying different things and trying to learn how all this stuff works... I will provide you more information if you want ;)
Thank you in advance.
UPDATE
As M. Denium suggested, that's what I have for now:
I declared the variable, using the TransactionStatus as ThreadSafe and finally solved as:
private DataSourceTransactionManager dataSourceTransactionManager = null;
private DefaultTransactionDefinition transactionDefinition = null;
private static final ThreadLocal<TransactionStatus> transactionStatus = new ThreadLocal<TransactionStatus>() {
#Override
protected TransactionStatus initialValue() {
return null;
}
};
And then using the same call from Groovy script, using the helper methods:
public void initTransaction(DataSource dataSource) {
dataSourceTransactionManager = new DataSourceTransactionManager();
transactionDefinition = new DefaultTransactionDefinition();
transactionDefinition.setReadOnly(false);
transactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
dataSourceTransactionManager.setRollbackOnCommitFailure(true);
dataSourceTransactionManager.setTransactionSynchronization(TransactionSynchronization.STATUS_UNKNOWN);
dataSourceTransactionManager.setDataSource(dataSource);
transactionStatus.set(dataSourceTransactionManager.getTransaction(null));
}
public void commitTransaction() {
try {
LOG.info("Finishing transaction...");
dataSourceTransactionManager.commit(transactionStatus.get());
dataSourceTransactionManager.getDataSource().getConnection().close();
LOG.info("Done.");
} catch (Throwable e) {
dataSourceTransactionManager.rollback(transactionStatus.get());
throw new RuntimeException("An exception during transaction. Rolling back.");
}
}
You are trying to reimplement the things that are already implemented by the transaction abstraction of Spring. Simply use the proper PlatformTransactionManager (you can probably grab that from an ApplicationContext) keep a reference to the TransactionStatus instead of a DataSource and use that to commit/rollback.
public TransactionStatus initTransaction() {
return transactionManager.getTransaction(null);
}
public void commit(TransactionStatus status) {
transactionManager.commit(status);
}
Instead of passing the TransactionStatus around you could also store it in a ThreadLocal and retrieve it in the commit method. This would ease the pain.
Another tip you shouldn't be creating JdbcTemplates and NamedParameterJdbcTemplates those are heavy objects to create. Upon construction they consult a connection to determine which database and version this is needed for the exception conversion. So performance wise this isn't a smart thing to do. Create a single instance and reuse, the templates are thread safe so you would only be needing a single instance.
However I would strongly argue that you should actually be using Groovy and not to try to work around it. Groovy has the Sql class that can help you. You already have access to the DataSource so doing something like this would be all that is needed.
def sql = new Sql(dataSource);
sql.withTransaction {
sql.execute "INSERT INTO city (name, state, founded_year) VALUES ('Minneapolis', 'Minnesota', 1867)"
sql.execute "INSERT INTO city (name, state, founded_year) VALUES ('Orlando', 'Florida', 1875)"
sql.execute "INSERT INTO city (name, state, founded_year) VALUES ('Gulfport', 'Mississippi', 1887)"
}
This is plain Groovy, no need to develop additional classes or to write extensive documentation to get it working. Just Groovy...

Is transaction required to close statement connection?

Having a Spring DAO method
public List<T> findByQuery(String query, Object[] values, int indexStart, int page) throws DefaultException{
try{
final Query query_ = this.em.createQuery(query).setFirstResult(indexStart);
....
return query_.getResultList();
}catch(Exception e){
throw new DefaultException(e.getMessage(), e);
}
}
removing the Transactional annotation, keep the connection Opened after the statement closed
So my question is : is the transaction required for a simple Select Query ?
in this exemple a transaction is created for a simple select query

Spring #Transational no rollback for jdbctemplate.update(PreparedStatementCreator, KeyHolder)

I'm newbie with spring framework. I used spring.xml to define the datasouce and DataSourceTransactionManager, so that I could insert data with jdbctemplate object.
And now I want to add the rollback to the transaction.
Unfortunately this rollback only works for JdbcTemplate.updata (String SQL), not for JdbcTemplate.update(PreparedStatementCreator, Keyholder), which I used to get the generated ID by insert.
#Override
#Transactional("txManagerTest")
public SQLQueryObjectIF process(SQLQueryObjectIF queryObj) {
KeyHolder keyHolder = new GeneratedKeyHolder();
for (final String query : queryObj.getQueries()) {
System.out.println(query);
// Rollback works fine for the "update" below.
//jdbcTemplate.update(query);
// Rollback doesn't work for the "update" below. Don't why...
jdbcTemplate.update(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
jdbcTemplate.getDataSource().getConnection().setAutoCommit(false);
PreparedStatement ps = jdbcTemplate.getDataSource().getConnection().prepareStatement(query,Statement.RETURN_GENERATED_KEYS);
return ps;
}
}, keyHolder);
//log.info(keyHolder.getKeys().toString());
}
//just for rollback test
if (keyHolder.toString().length()>-1){
throw new RuntimeException("Test Error");
}
return queryObj;
}
That code should be used like this (you need to use the connection given as parameter), otherwise with your code you will get a connection that Spring doesn't know about, by directly accessing the DataSource instance (if Spring doesn't know about it, it will not know to rollback in case of exception):
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement(query,Statement.RETURN_GENERATED_KEYS);
return ps;
}

Resources