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

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

Related

Why hibernate is saving values without transaction?

i'm using spring boot(2.1.4) with hibernate(5.3.9).
public class BaseDao{
#Autowired
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
#Autowired
private EntityManagerFactory entityManagerFactory;
#PersistenceContext
private EntityManager entityManager;
public SessionFactory getSessionFactory() {
return sessionFactory;
}
public EntityManagerFactory getEntityManagerFactory() {
return entityManagerFactory;
}
public EntityManager getEntityManager() {
return entityManager;
}
public Session getSession() throws Exception{
if(session == null) {
session = getSessionFactory().openSession();
}
if(transaction == null) {
transaction = session.beginTransaction();
}
return session;
}
public void commit() throws Exception{
if(transaction != null) {
transaction.commit();
transaction = null;
}
if(session != null) {
session.close();
session = null;
}
}
public void rollback() throws Exception{
if(transaction != null) {
transaction.rollback();
transaction = null;
}
if(session != null) {
session.close();
session = null;
}
}
protected void save(Object object) throws Exception {
getSessionFactory().openSession().save(object); //saves data in db
getSession().save(object); //is not saving data
}
getSessionFactory().openSession().save(object); this code is saving data to db even without commit
getSession().save(object); this code required commit to be called as txn is created but not commited
hibernate log
i see below log for both the line of code
insert
into
TEST_ENTITY
(CREATED_BY, CREATED_DATE, ENABLED, LAST_MODIFIED_BY, LAST_MODIFIED_DATE, NAME)
values
(?, ?, ?, ?, ?, ?)
i have few questions on this behavior.
I know write operation will not happen without commit, so any idea what is wrong here or what causing commit in first scenario ?
Is it ok to use above code i.e. first scenario ?
If first approach is not right then do i need to create and commit txn for each object, any better approach so that even if i have to commit txn, i don't want to replicate the txn.commit() in every new method i write in BaseDao.java i.e. say i have create(), update(),delete() methods can i move this txn.commit() out of methods ?
Few places i'm using spring data jpa for fetching/saving record (given below), how txn is being handled in spring data jpa ? any references ?
#Repository
public interface TestEntitytRepo extends JpaRepository<TestEntity, Long> {
...
}
Please let me know if i missed any details to capture here.
Thanks in advance.
In hibernate, the Save() method stores an object into the database. It will Persist the given transient instance, first assigning a generated identifier. It returns the id of the entity created. When a session in hibernate is created using SessionFactory.openSession(), no transaction is created, so all the operations are executed outside of the transaction context !! In order to ensure the data gets saved into the database, a new transaction needs to be created.
I am a bit skeptical about the behaviors explained by you above. Seems like auto-commit option is enabled. If so, then this is not an issue as save() method is done, commit automatically happens backstage !!

Calling Stored Procedure using Spring Data JPA

I want to know whether it is possible to call stored procedure using Spring Data JPA which is having resultset and multiple out parameter.
I found Git issue for same https://github.com/spring-projects/spring-data-examples/issues/80
If it is resolved, could someone provide one example with Spring Boot?
The way I've accomplished this in the past is to add custom behavior to a Spring Data JPA repository (link). Inside that I get the EntityManager and use java.sql.Connection and CallableStatement
Edit: Adding high level sample code. Sample makes the assumption that you are using Hibernate but idea should be applicable to others as well
Assuming you have an EntityRepository
public interface EntityRepositoryCustom {
Result storedProcCall(Input input);
}
public class EntityRepositoryImpl implements EntityRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public Result storedProcCall(Input input) {
final Result result = new Result();
Session session = getSession();
// instead of anonymous class you could move this out to a private static class that implement org.hibernate.jdbc.Work
session.doWork(new Work() {
#Override
public void execute(Connection connection) throws SQLException {
CallableStatement cs = null;
try {
cs = connection.prepareCall("{call some_stored_proc(?, ?, ?, ?)}");
cs.setString(1, "");
cs.setString(2, "");
cs.registerOutParameter(3, Types.VARCHAR);
cs.registerOutParameter(4, Types.VARCHAR);
cs.execute();
// get value from output params and set fields on return object
result.setSomeField1(cs.getString(3));
result.setSomeField2(cs.getString(4));
cs.close();
} finally {
// close cs
}
}
});
return result;
}
private Session getSession() {
// get session from entitymanager. Assuming hibernate
return em.unwrap(org.hibernate.Session.class);
}
}

Save an Object after an error Occured in the current Transaction

Hi i have the following construction, which runs a raw query and then saves the result. But when an exception occoured on the raw queries, i cannot save my Object any more to indicate that an error happened
[c3p0] A PooledConnection that has already signalled a Connection error is still in use!
The classes:
#Service("myService")
#Transactional(propagation = Propagation.REQUIRED)
public class MyService {
cron(){
Job job = myDao.nextJob();
try {
myDao.myFunction(job);
job.setStatus(Status.COMPLETE);
} catch (Exception e) {
job.setStatus(Status.ERROR);
}
myDao.save(job);
}
}
#Repository("myDao")
public class MyDao extends HibernateDaoSupport {
#Autowired
public void setHibernateSessionFactory(#Qualifier("sessionFactory") SessionFactory sessionFactory) {
setSessionFactory(sessionFactory);
}
public void myFunction(final Job job) {
final Session session = getCurrentSession();
session.flush();
session.doWork(new Work() {
#Override
public void execute(Connection con) throws SQLException {
// do something which creates an SQL error
}
}
}
}
what can i do to get a new working session to get my object saved?
1) It looks like you are not releasing the connection to the connection pool after an error, and trying to reuse it.
2) Instead of final Session session = getCurrentSession() create a new session using openSession() as below:
SessionFactory sessionFactory = HibernateUtil.getSessionAnnotationFactory();
final Session session = sessionFactory.openSession();
Try to create new session for each transaction, if you use getCurrentSession and any error occurs or not in the transaction, then the maintainence(closing/flushing etc) of the session object is done by hibernate and not by the programmer. Make use of getCurrentSession for single threaded environment.
3) By default a Connection object is in auto-commit mode, which means that it automatically commits changes after executing each statement. Check whether have you altered its property? Use Connection.commit() to commit the 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...

TransactionTemplate vs JdbcTemplate

The Spring framework provides two means of programmatic transaction management:
Using the TransactionTemplate.
Using a PlatformTransactionManager implementation directly.
The above is described here: http://static.springsource.org/spring/docs/2.0.8/reference/transaction.html
The Spring site hasnot mentioned JdbcTemplate here. As per my understanding JdbcTemplate also manages the transaction internally and this is all done in programme too.
So what's the basic difference between TransactionTemplate and JdbcTemplate?
JdbcTemplate is not a transaction manager. It's merely a helper class for native JDBC operations:
This is the central class in the JDBC core package. It simplifies the use of JDBC and helps to avoid common errors. It executes core JDBC workflow, leaving application code to provide SQL and extract results. This class executes SQL queries or updates, initiating iteration over ResultSets and catching JDBC exceptions and translating them to the generic, more informative exception hierarchy defined in the org.springframework.dao package.
TransactionTemplate by the way is also not a transaction manager, it's a
Template class that simplifies programmatic transaction demarcation and transaction exception handling.
The PlatformTransactionManager (and other subclasses of AbstractPlatformTransactionManager) is a transaction manager, as in it
determines if there is an existing transaction;
applies the appropriate propagation behavior;
suspends and resumes transactions if necessary;
checks the rollback-only flag on commit;
applies the appropriate modification on rollback (actual rollback or setting rollback-only);
triggers registered synchronization callbacks (if transaction synchronization is active).
So this class is responsible for the actual transaction handling, as opposed to the TransactionTemplate, which is to be used if you instead of declarative transaction handling you want to implement it programmetically. (see this blog, though quite outdated, you will see the difference between declarative and manual)
Quotes from Spring 3 Reference.
Note: Throughout the Spring Framework you will find other *Template classes as well: HibernateTemplate, JmsTemplate etc. They all follow the same pattern: template classes which radically reduce the amount of code you need to write, because all the so-called boilerplate code will be handled by them. Example (from here):
Without JdbcTemplate:
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void insert(Customer customer){
String sql = "INSERT INTO CUSTOMER " +
"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";
Connection conn = null;
try {
conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, customer.getCustId());
ps.setString(2, customer.getName());
ps.setInt(3, customer.getAge());
ps.executeUpdate();
ps.close();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {}
}
}
}
And with JdbcTemplate:
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public void insert(Customer customer){
String sql = "INSERT INTO CUSTOMER " +
"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";
jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update(sql, new Object[] { customer.getCustId(),
customer.getName(),customer.getAge()
});
}

Resources