I use Google Guice and jOOQ in my project. Currently I decided to introduce transaction handling using Spring JDBC.
So I did the following.
I set a data source and a transaction manager in Guice module.
#Provides
#Singleton
DataSource provideDataSource(IExternalSettings settings) {
Jdbc3PoolingDataSource dataSource = new Jdbc3PoolingDataSource();
// configuring DataSource
return dataSource;
}
#Provides
#Singleton
DataSourceTransactionManager provideDataSourceTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(new TransactionAwareDataSourceProxy(dataSource));
}
Then I inject my transaction manager to a persistence facade
#Inject
public PersistenceFacade(final DataSourceTransactionManager transactionManager) {
this.dataSource = transactionManager.getDataSource();
this.transactionManager = transactionManager;
}
Later, I use this data source to create jOOQ factory: new Factory(dataSource, ...).
Finaly I run my database access code:
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
try {
// db code in transaction
transactionManager.commit(transaction);
return result;
} catch (Exception e) {
transactionManager.rollback(transaction);
throw e;
}
So far, so good. It works as expected.
So, my next step is to introduce #Transactional annotation using Guice AOP. I created an interceptor
class TransactionalMethodInterceptor implements MethodInterceptor {
#Inject
private DataSourceTransactionManager transactionManager;
#Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
try {
Object result = invocation.proceed();
transactionManager.commit(transaction);
return result;
} catch (Exception e) {
transactionManager.rollback(transaction);
throw e;
}
}
}
And configured it in configure() method of Guice module:
TransactionalMethodInterceptor transactionalMethodInterceptor = new TransactionalMethodInterceptor();
requestInjection(transactionalMethodInterceptor);
bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transactional.class), transactionalMethodInterceptor);
And now the issues begin. I can see, using debugger, that control flow reaches interceptor. In particular, it reaches transactionManager.rollback(...) invocation. But the transaction is not actually roll backed.
I have no clue what's the reason. Any ideas? I'll be grateful. Thanks!
I finally managed to come back to this one and I think I found a solution.
First of all, I need to mention I introduced a bit of misunderstanding, when I said it all works without AOP. Today I couldn't reproduce it and I noticed that the connection that is rolled back is different than the one that jOOQ is using. So Alan Vrecko (see comment above) was right!
Then I found this answer and this snippet. I decided to give them a try and it worked! However, it appeared that all previous steps are valid and still need to be there (including Google Guice's interceptor).
The only I change I had to introduce was to remove DataSourceUtils.releaseConnection(con, dataSource); from SpringExceptionTranslationExecuteListener. exception(ExecuteContext ctx). So finally the method looks like
#Override
public void exception(ExecuteContext ctx) {
SQLException ex = ctx.sqlException();
Statement stmt = ctx.statement();
Connection con = ctx.getConnection();
DataSource dataSource = ctx.getDataSource();
JdbcUtils.closeStatement(stmt);
ctx.exception(getExceptionTranslator(dataSource).translate("jOOQ", ctx.sql(), ex));
}
Then all seems to work properly. Thank you all for comments. Of course I'm still open to new/better solutions.
Related
I have Spring Boot application which works with PostgreSQL database.
I have only one datasource which is using for Spring Data JPA operations and for Liquibase migrations (I guess).
spring:
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://*************/*****
username: *******
password: *******
type: com.zaxxer.hikari.HikariDataSource
connection-test-query: SELECT 1;
idle-timeout: 30000
maximum-pool-size: 100
minimum-idle: 7
jpa:
hibernate:
ddl-auto: validate
database-platform: org.hibernate.dialect.PostgreSQLDialect
show-sql: false
properties:
hibernate:
default_schema: public
format_sql: true
enable_lazy_load_no_trans: true
generate_statistics: false
jdbc.batch_size: 100
order_inserts: true
order_updates: true
jdbc.batch_versioned_data: true
query.fail_on_pagination_over_collection_fetch: true
liquibase:
liquibase-schema: public
default-schema: public
change-log: classpath:db/changelog/db.changelog-master.xml
I also have Spring Boot configuration class that specifies which one transaction manager I wanna have in my application.
#Configuration
public class TransactionManagerConfig {
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(SessionFactory sessionFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(sessionFactory);
return transactionManager;
}
}
When it comes to using my database, I have to different ways to communicate with my data.
First one is using service method annoteted with #Transactional annotation. For example for simple reading I use next approach:
#Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED)
public List<Dto> fetchAll() {...
And another way is a little bit more complicated but still usual. For example for update I am interacting with transaction manually:
#Service
public class EntityService {
#Autowired
private DocumentRepository documentRepository;
#Autowired
private PlatformTransactionManager transactionManager;
public Long update(Long id, Entity entity) {
DefaultTransactionDefinition transForCursor = new DefaultTransactionDefinition();
transForCursor.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transForCursor.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus cursorTransactionStatus = transactionManager.getTransaction(transForCursor);
try {
//...
//here I am interactiong with S3 File storage
//...
savedEntity = entityRepository.save(entity);
} catch (Exception e) {
//...
//if I catch any exception I should delete files from S3 File storage
//which I just uploaded and only then I should rollback database transaction
//...
if (!(e instanceof RuntimeException)) {
transactionManager.rollback(cursorTransactionStatus);
}
throw e;
}
try {
transactionManager.commit(cursorTransactionStatus);
} catch (Exception e) {
//...
//if any exception happens when I was trying to commit I still should delete files
//...
throw new TransactionException();
}
return savedEntity.getId();
}
}
And the problem is that sometimes I get this exception when I am calling read or update method:
org.springframework.transaction.IllegalTransactionStateException: Pre-bound JDBC Connection found! JpaTransactionManager does not support running within DataSourceTransactionManager if told to manage the DataSource itself. It is recommended to use a single JpaTransactionManager for all transactions on a single DataSource, no matter whether JPA or JDBC access.
But sometimes it's just working fine without any errors.
And the most incredibly interestion thing here that last time when I got this problem I had this exception every tenth call of my read method.
Does anyone have any idea why it can be happening and how to fix it?
Thank you very much for your time and your answers!
I tried to fix it by adding #Transactional annotation to my read method. And I added #Primay annotation to my configuration class where I define transaction manager. But unfortunately it didn't help me. I was try to find the same problem here but I didn't meet the same conditions.
That pattern:
TransactionStatus tx = transactionManager.getTransaction(...);
try {
...
} catch (Exception e) {
if (!(e instanceof RuntimeException)) {
transactionManager.rollback(tx);
}
throw e;
}
try {
transactionManager.commit(tx);
} catch (Exception e) {
...
throw new TransactionException();
}
return savedEntity.getId();
is definitely incorrect, the rule of thumb is: you must finish (either commit or rollback) transaction in the same place where you have started it. However in your case that is not true, so you are encountering resource leaks and other related consequences.
If you need to cleanup some external resources upon transaction rollback you have following options:
spring-tx way:
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
#Override
public void afterCompletion(int status) {
if (STATUS_ROLLED_BACK== status) {
// do something
}
}
});
hibernate way:
SessionImplementor session = entityManager.unwrap(SessionImplementor.class);
session.addEventListeners(new BaseSessionEventListener() {
#Override
public void transactionCompletion(boolean success) {
if (!success) {
// do something
}
}
});
another hibernate way:
SessionImplementor session = entityManager.unwrap(SessionImplementor.class);
session.getActionQueue().registerProcess((success, sess) -> {
if (!success) {
// do something
}
});
I want to call different methods that interact with my database in one method.
something like this :
#Autowired
EnteteService es; // service for jpaRepository
#Autowired
SystemOracleServiceJpa so; // service using jdbcTemplate
#Autowired
DetailService ds; // service for jpaRepository
#Transactional
public void configure(EnteteAR entete) throws ConfigurationException {
try{
this.es.save(entete); // first methode
List<DetailAR> details = this.so.getStructure(entete.getTableName());
if(details.size()>0){
this.ds.saveAllDetails(details); // second
this.so.CreateTable(details, entete.getTableName(), "DEM");//third
this.so.createPkIdxDem(entete.getTableName()); // fourth
this.so.CreateTable(details, entete.getTableName(), "BACK"); // fifth
}
else{
throw new ConfigurationException("Configuration error");
}
}catch(Exception e){
throw new ConfigurationException(e.getMessage());
}
}
I want to commit only if no errors appears in all this methods inside my main method "configure".
I was thinking that #transactionnal annotation work for this, but that commit after each method inside.
Exemple :
if this.es.save work and this.ds.saveAllDetails dont, I find data of es.save on database :(
Someone can help my please ?
thank with advance for your reading and your potential help.
#Transactional will automatically invoke a rollback if an unchecked exception is thrown from the executed method.
ConfigurationException in your case is a checked exception and hence it does not work.
You can make it work by modifying your annotation to
#Transactional(rollbackOn = ConfigurationException.class)
public void configure(EnteteAR entete) throws ConfigurationException {
try{ ....
I'm using Spring JdbcTemplate on one of my projects and now, when there are really very much requests with it - I started to face this exception:
org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback;
uncategorized SQLException for SQL [{? = call API.get_data_for_nb(?, ?)}];
SQL state [99999]; error code [17009]; Closed Statement;
nested exception is java.sql.SQLException: Closed Statement
So the Closed Statement exception is received when you try to execute statement that is already closed, but in my case I don't close it by myself - I use JdbcTemplate exactly for that. So, firstly, what could be the reason for that?
The JdbcTemplate object itself is contained in #Stateless EJB in this way:
#Stateless(name = "NbEdwServiceEJB")
public class NbEdwServiceBean implements NbEdwServiceLocal, NbEdwServiceRemote {
#Resource(mappedName = JNDI)
private DataSource dataSource;
private static volatile JdbcTemplate jdbcTemplate;
#PostConstruct
protected void construct() {
synchronized (NbEdwServiceBean.class) {
if (jdbcTemplate == null) {
jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
}
}
}
private String getDataFromDB(final String request, final int isDigitalSignVerified) {
String response = null;
try {
response = jdbcTemplate.execute(SQL_GET_DATA, new CallableStatementCallback<String>() {
public String doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
cs.registerOutParameter(1, Types.VARCHAR);
cs.setInt(2, isDigitalSignVerified);
cs.setString(3, request);
cs.executeUpdate();
return cs.getString(1);
}
});
} catch (DataAccessException ex) {
LOGGER.error("getDataFromDB()", ex);
}
return response;
}
}
I know that this is maybe not the strictly right way to do it, I could just create instance of JdbcTemplate for every stateless bean - so I might do just that. So, secondly, why is this ever happening? My suppose was that JdbcTemplate's execute method isn't thread safe, but can someone give the full explanation on what is going on?
I have JEE version 5 running on WebLogic 10.3.5 if it's matter.
#Tolegen Izbassar I'm sorry that you're stuck with EE5.
Concerning the Singleton and EE5 there were some alternatives out there. One is vendor specific extensions, for example JBoss 5.x had service beans providing Singleton+JMX. A second solution is to use a earlier version of Jboss Seam compatible with EE5. A third alternative is to use the ServerContext from the Servlet API.
What you're trying to do in #PostConstuct is definitely not good. Non final statics in SLSB is a no go.
I suggest to have a look, at section 29.3 from Spring framework reference which describes EJB - Spring integration, an example from that section:
#Stateless
#Interceptors(SpringBeanAutowiringInterceptor.class)
public class MyFacadeEJB implements MyFacadeLocal {
// automatically injected with a matching Spring bean
#Autowired
private MyComponent myComp;
// for business method, delegate to POJO service impl.
public String myFacadeMethod(...) {
return myComp.myMethod(...);
}
I have buit a Webservice using these technologies + c3p0 for database handling. It works ok most of the time but I have a 3-5% ratio (sometimes even a 10%) of failed acces due to this error.
I am using Hibernate this way:
-Session Factory
private static SessionFactory buildSessionFactory() {
try {
Configuration configuration = new Configuration();
configuration.configure();
serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
// Create the SessionFactory from hibernate.cfg.xml
return configuration
.buildSessionFactory(serviceRegistry);
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
//reabrimos la sesion si esta cerrada al liberar los recursos
if(sessionFactory.isClosed())
{
System.out.println("Reopen session");
sessionFactory.openSession();
}
return sessionFactory;
}
Then in my hibernate.cfg.xml I have the following line:
<property name="current_session_context_class">thread</property>
Finally in my endpoints I have defined a hibernate_session class which I use as follows:
#Path("/projects")
public class ProjectServiceImpl {
#Context
SecurityContext security;
Session hibernate_session = null;
#POST
#Path("sync.json")
#Produces(value = {"application/json",
"application/vnd.myapp-v1+json",
"application/vnd.myapp-v2+json"})
public Response syncProjects(
#DefaultValue("") #FormParam("projects") String in_projects_str,
#DefaultValue("0") #FormParam("last_sync") long last_sync,
#Context Request request) {
//...
hibernate_session = HibernateUtil.getSessionFactory()
.getCurrentSession();
if (hibernate_session == null) {
ResponseMessage rm = new ResponseMessage();
rm.setCode(Status.INTERNAL_SERVER_ERROR.getStatusCode());
rm.setMessage("Hibernate Session is Null");
rm.setType("ERROR");
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(rm)
.type("application/json").build();
}
try {
hibernate_session.beginTransaction();
//Database work...
hibernate_session.flush();
hibernate_session.getTransaction().commit();
}catch (RuntimeException | IllegalAccessException
| InvocationTargetException e) {
try {
if (hibernate_session.getTransaction() != null) {
hibernate_session.getTransaction().rollback();
}
} catch (RuntimeException rbe) {
System.err.println("Couldn’t roll back transaction");
}
e.printStackTrace();
ResponseMessage rm = new ResponseMessage();
rm.setCode(Status.INTERNAL_SERVER_ERROR.getStatusCode());
rm.setMessage(e.getMessage());
rm.setType("ERROR");
return Response.status(Status.INTERNAL_SERVER_ERROR).entity(rm)
.type("application/json").build();
}
}
return Response.ok().entity(result_entity)
.type("application/json").build();
}
My hibernate_session is a class attribute, do I have to change it to a local variable?. As far as I know the end points will be executed in different threads so I have assumed that I am working with different instances of my endpoint container class and these class attributes will not get overriden by multiple request.
Any light you can shed on this topic will be appreciated,
Thanks in advance
Thanks all for your replies. I finally managed to solve the problem.
In one of my multiple entries there was a begin transaction (necessary to create criterias) but was not commited. The result was that a reused thread that had called that method before would throw a nested exception. By commiting the transaction the problem was solved :)
You aren't using openSession and getCurrentSession properly.
public static SessionFactory getSessionFactory() {
//reabrimos la sesion si esta cerrada al liberar los recursos
//change this: if(sessionFactory.isClosed()) to this:
if(sessionFactory == null || sessionFactory.isClosed())
{
System.out.println("Reopen session"); // Really setup session factory
//change this: sessionFactory.openSession(); to this:
sessionFactory = buildSessionFactory();
}
return sessionFactory;
}
That's not the problem though, your code there just isn't doing what it's supposed to. You need to change:
hibernate_session = HibernateUtil.getSessionFactory().getCurrentSession();
to
hibernate_session = HibernateUtil.getSessionFactory().openSession();
As per the SessionFactory Javadoc:
Obtains the current session. The definition of what exactly "current" means controlled by the CurrentSessionContext impl configured for use.
It's safe to assume your CurrentSessionContext is not thread safe.
It seems that a transaction is started, and before the transaction gets commited an attempt to start a new transaction is made.
This explains the error message that says that a nested transaction (the second transaction inside the ongoing transaction) is not supported.
This could be caused for example by incorrect error handling, for example starting a transaction, not catching an exception or catch and ignore and then try to begin a second transaction without having done either commit or rollback.
An idiom similar to this one should be used when doing programmatic transaction mananagement:
try {
sess.getTransaction().begin();
// do some work
sess.getTransaction().commit()
}
catch (RuntimeException e) {
sess.getTransaction().rollback();
throw e;
}
Also important to bear mind is that after a rollback the session cannot be reused, as it's in an inconsistent state.
If using a framework like Spring, the use of the annotation #Transactional for declarative transaction management solves most of these problems for us and leads to more maintainable code, EJB3 has also similar functionality.
Below is what I did, I need to implement rollback, using #transactional annotation, but not working as expected, what else need to be done for proper rollback to happen ?? I want that when the code is executed result in db should be "testingOne" , currently it is set to "notRollBacked". Can you please point my mistake.
public Response deleteUser(Request argVO)throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setUserName("testingOne");
UsersLocalServiceUtil.updateUsers(users);
try
{
testRollbackFunction();
}
catch (Exception ex)
{
}
return new Response();
}
#Transactional(isolation = Isolation.PORTAL, rollbackFor =
{PortalException.class, SystemException.class})
private void testRollbackFunction() throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setUserName("notRollbacked");
UsersLocalServiceUtil.updateUsers(users);
throw new PortalException();
}
****************Edit 1*************
I did what was mentioned in answers:
I did taken bean from context
and written a class/bean as
#Transactional(isolation = Isolation.PORTAL, rollbackFor =
{PortalException.class, SystemException.class})
public class RollBack
{
#Transactional(isolation = Isolation.PORTAL, rollbackFor =
{PortalException.class, SystemException.class})
public void thisWillRollBack() throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setBarringReason("notRollbacked");
UsersLocalServiceUtil.updateUsers(users);
throw new PortalException();
}
}
spring xml file bean refrence set as :
<bean id="rollBackBean" class="com.alepo.RollBack">
</bean>
public Response myMethod(Request argVO)throws Exception
{
Users users = UsersLocalServiceUtil.getUsers("sagar");
users.setBarringReason("testingOne");
UsersLocalServiceUtil.updateUsers(users);
try
{
Test test = new Test();
Object obj = test.getBean();
RollBack rollBack = (RollBack)obj;
rollBack.thisWillRollBack();
}
catch (Exception ex)
{
ex.printStackTrace();
}
return new Response();
}
#################EDIT 4
now calling rollback function as :
RollBack rollBack = (RollBack)PortalBeanLocatorUtil.getBeanLocator().locate("rollBackBean");
rollBack.thisWillRollBack();
No Test class in picture now ...no new anywhere ...
still NOT WORKING .......
If you have a #Transactional annotation on method, Spring wraps the call to this method with aspect handling the transaction.
So:
1) Only public methodes can be wrapped in aspect
2) You call wrapped code only if you call the method on a bean taken from / injected by Spring container.
In your case:
1) The code isn't wrapped in transactional aspect because it is not public method
2) Event if it was, it is called directly from within the class, so you wouldn't call wrapped version anyway.
So the solution is to make separate bean with #Transactional method, inject it into and call it from Response class.
Of course you need <tx:annotation-driven/> in your spring-xml or instruct Spring otherwise to process #Transactional annotations (see the reference).
The issue is you are outside the application context. You are creating a new instance of a class, NEW is bad in Spring, very bad. Get an instance of Test from the application context, not by creating a new instance unless you start your application context in Test. Try to Autowire test in your class you mention above or inject it from Spring and then let me know, but the code you are showing above will never work with transaction management.