Transaction with binded thread connection in Spring - 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...

Related

Can't get CompletableFuture and Spring REST to work together

I've been trying to make a simple backend for user login.
It works fine if I don't use threads, but when I try to implement multi-threading it breaks for some reason.
For now, I'm just trying to do something as simple as retrieving all users' info from the User table in JSON format. The problem is that the Rest controller returns nothing to Postman even though dbActions.getAll() returns the correct List.
There's no error, no exception, nothing. I could really use some help. The parts of my code I'm trying to get to work are below.
Rest controller:
#Async
#RequestMapping(value="/view", method =RequestMethod.GET)
public List<User> viewAll() {
try {
List<User> list = new ArrayList<>();
list = dbActions.getAll();
return list;
}catch(Exception e) {
e.printStackTrace();
return null;
}
}
dbActions service:
public List<User> getAll() {
List<User> results = new ArrayList<>();
CompletableFuture<Void> future;
try {
future = CompletableFuture.runAsync(new Runnable() {
public void run() {
userRepo.findAll().forEach(results::add);
}
});
future.get();
return results;
} catch (Exception e) {
e.printStackTrace();
return results;
}
}
Try removing the #Async annotation from your viewAll() method

Spring data jpa and JdbcTemplate - Should I close connection?

Should I close connection or Spring will handle it?
#Autowired
MyRepository myRepository;
#Autowired
#Qualifier("myJdbc")
JdbcTemplate myJdbc;
#GetMapping("/v1/controlla-abilitazione")
public Set<String> controlloAbilitazione() {
try {
Connection conn = myJdbc.getDataSource().getConnection();
//Here I use previous connection to call an oracle PL/SQL via basic oracle jdbc
//Should I close connection?
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
(I know that I can use Spring to handle PL/SQL, but Spring doesn't have native support for Oracle Type as return from PL/SQL)
Not tried but if you execute the SQL or PL/SQL query from the Connection object, you don't use Spring JDBC features for executing your query, so you should not expect that Spring closes the connection for you. It is not aware of the datasource provider activity about that.
So Connection.close() should be probably required.
It is an theory but you could check it easily enough. Store the Connection in a field of the bean and do a check at the beginning of the method.
Connection conn;
#GetMapping("/v1/controlla-abilitazione")
public Set<String> controlloAbilitazione() {
if (conn != null && !conn.isClosed){
throw new RuntimeException("Oh it was not closed");
}
try {
conn = myJdbc.getDataSource().getConnection();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
Now invoke your controller once and another time. If you get the exception, you know why.

Java Streams to iterate over a ResultSet object

I have the following code snippet
ResultSet rs = stmt.executeQuery();
List<String> userIdList = new ArrayList<String>();
while(rs.next()){
userIdList.add(rs.getString(1));
}
Can I make use of Java streams/Lambda expressions to perform this iteration instead of a while loop to populate the List?
You may create a wrapper for the ResultSet making it an Iterable. From there you can iterate as well as create a stream. Of course you have to define a mapper function to get the iterated values from the result set.
The ResultSetIterable may look like this
public class ResultSetIterable<T> implements Iterable<T> {
private final ResultSet rs;
private final Function<ResultSet, T> onNext;
public ResultSetIterable(ResultSet rs, CheckedFunction<ResultSet, T> onNext){
this.rs = rs;
//onNext is the mapper function to get the values from the resultSet
this.onNext = onNext;
}
private boolean resultSetHasNext(){
try {
hasNext = rs.next();
} catch (SQLException e) {
//you should add proper exception handling here
throw new RuntimeException(e);
}
}
#Override
public Iterator<T> iterator() {
try {
return new Iterator<T>() {
//the iterator state is initialized by calling next() to
//know whether there are elements to iterate
boolean hasNext = resultSetHasNext();
#Override
public boolean hasNext() {
return hasNext;
}
#Override
public T next() {
T result = onNext.apply(rs);
//after each get, we need to update the hasNext info
hasNext = resultSetHasNext();
return result;
}
};
} catch (Exception e) {
//you should add proper exception handling here
throw new RuntimeException(e);
}
}
//adding stream support based on an iteratable is easy
public Stream<T> stream() {
return StreamSupport.stream(this.spliterator(), false);
}
}
Now that we have our wrapper, you could stream over the results:
ResultSet rs = stmt.executeQuery();
List<String> userIdList = new ResultSetIterable(rs, rs -> rs.getString(1)).stream()
.collect(Collectors.toList())
}
EDIT
As Lukas pointed out, the rs.getString(1) may throw a checked SQLException, therefor we need to use a CheckedFunction instead of a java Function that would be capable of wrapping any checked Exception in an unchecked one.
A very simple implementation could be
public interface CheckedFunction<T,R> extends Function<T,R> {
#Override
default R apply(T t) {
try {
return applyAndThrow(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
R applyAndThrow(T t) throws Exception;
}
Alternatively you could use a library with such a function, i.e. jooλ or vavr
If using a third party library is an option, you could use jOOQ, which supports wrapping JDBC ResultSet in jOOQ Cursor types, and then stream them. For example, using DSLContext.fetchStream()
Essentially, you could write:
try (ResultSet rs = stmt.executeQuery()) {
DSL.using(con) // DSLContext
.fetchStream(rs) // Stream<Record>
.map(r -> r.get(0, String.class)) // Stream<String>
.collect(toList());
}
Disclaimer: I work for the vendor.
Try library: abacus-jdbc
List<String> userIdList = StreamEx.<String> rows(resultSet, 1).toList(); // Don't forget to close ResultSet
Or: If you want to close the ResultSet after toList.
StreamEx.<String> rows(resultSet, 1).onClose(() -> JdbcUtil.closeQuitely(resultSet)).toList();
Or: If you use the utility classes provided in abacus-jdbc:
String sql = "select user_id from user";
// No need to worry about closing Connection/Statement/ResultSet manually. It will be took care by the framework.
JdbcUtil.prepareQuery(dataSource, sql).stream(String.class).toList();
// Or:
JdbcUtil.prepareQuery(dataSource, sql).toList(String.class);
Disclaimer:I'm the developer of abacus-jdbc.

Spring Transactions not working - JDBCTemplate is reading uncommitted data

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.

CMT rollback : how to roll back the transaction

The following code does not help in roll back even if I throw null pointer exception at update() method. Everytime it inserts values into the database if I run the code. Please help me how can I roll back the transaction if null pointer is thrown at update() method. Am I missing something in the code?
#TransactionManagement(value = TransactionManagementType.CONTAINER)
public class Bean implements RemoteIF {
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void insertIntoDb() {
insert();
update();
}
private Integer update() {
val=0;
try {
Connection con = DbConn.getConnection();
Statement st = con.createStatement();
val1 = st.executeUpdate("INSERT INTO tab VALUES('ab')");
st.close();
throw new NullPointerException();
} catch (Exception e) {
System.out.println(e);
}
return val;
}
private Integer insert() {
int val = 0;
try {
Connection con = DbConn.getConnection();
Statement st = con.createStatement();
val = st.executeUpdate("INSERT INTO tab VALUES('bnm')");
st.close();
} catch (Exception e) {
System.out.println(e);
}
return val;
}
}
Couple things that stick out at me as suspect.
No #Stateless, #Stateful or #Singleton annotation on the Bean class. Unless you've declared the bean in an ejb-jar.xml file, then this is not getting recognized as an EJB. Definitely double check that.
The DbConn.getConnection() looks suspiciously like you might be trying to manage database connections yourself. If you have any code that uses the DriverManager or does new FooDataSource(), then that is definitely the problem. If you want transaction management to work you have to get all resources from the container via either
Injection via a #Resource DataSource datasource field in the EJB class
JNDI lookup of java:comp/env/yourDataSource, where yourDataSource is the name of a datasource you configured in the ejb-jar.xml or declared on the bean class via using #Resource(type=DataSource.class, name="youDataSource") -- that annotation goes on the class itself rather than a method or field.
See also these answers for some insight as to how transaction management works:
How does UserTransaction propagate?
Programming BMT - UserTransaction

Resources