3

From what I read, this exception happens when you try to begin a transaction before commiting a previous one. However, I don't understand why I get this exception in my case.

I have a web application with the following servlet:

@RestController
public class Hello {

/***** DAO's *******/
@Autowired
CompteDAO compteDAO;


@RequestMapping("/")
public String index() {

    String response = "";   

    /***** COMPTE TEST *****/

    response = response + "======== COMPTE TEST ========= \n";

    response = response + "Compte list : \n";


   ArrayList<Compte> comptes = (ArrayList<Compte>) compteDAO.getAllComptes();

   for(int i = 0; i < comptes.size(); i++) {
       response = response + comptes.get(i).getNomUtilisateur() + "\n";
   }

   response = response + "\n" + "Compte name = ";       
    return response;
}

This is my Compte object:

public class Compte {

    public int id;
    public String nomUtilisateur;
    public String motDePasse;
    public int typeCompte;
    public String courriel;
    public String cleAPI;
    public boolean visibleLorsDeLaCreation;
    public int joursDisponibilite;
    public int heuresDisponibilite;


    public Compte(){

    }

    public Compte(String nomUtilisateur, String motDePasse, int typeCompte, String courriel, String cleAPI,
            boolean visibleLorsDeLaCreation, int joursDisponibilite, int heuresDisponibilite) {
        this.nomUtilisateur = nomUtilisateur;
        this.motDePasse = motDePasse;
        this.typeCompte = typeCompte;
        this.courriel = courriel;
        this.cleAPI = cleAPI;
        this.visibleLorsDeLaCreation = visibleLorsDeLaCreation;
        this.joursDisponibilite = joursDisponibilite;
        this.heuresDisponibilite = heuresDisponibilite;
    }
    /** Getters and setters omitted **/
}

This is the interface of my DAO :

public interface CompteDAO {

    public List<Compte> getAllComptes();    
}

And this is its implementation :

@Repository
public class CompteDaoImpl implements CompteDAO {

@Autowired
public SessionFactory sessionFactory;

public CompteDaoImpl() {
}   


@Override
public List<Compte> getAllComptes() {
    Session currentSession = sessionFactory.getCurrentSession();
    List<Compte> comptes;
    comptes = new ArrayList<Compte>(currentSession.createCriteria(Compte.class).list());
    return comptes;
}   

Also, this is my Spring configuration:

@Configuration  
@EnableTransactionManagement  
@PropertySource({ "classpath:application.properties" })  
@ComponentScan({ "ca.etsmtl.gti525, ca.etsmtl.gti525.pojo" })  
public class PersistenceConfig  
{  
  @Autowired  
  private Environment env;  

  @Bean  
  public LocalSessionFactoryBean sessionFactory() {  
     LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();  
     sessionFactory.setDataSource(restDataSource());  
     sessionFactory.setPackagesToScan(new String[] { "ca.etsmtl.gti525" });  
     sessionFactory.setHibernateProperties(hibernateProperties());
     sessionFactory.setMappingResources(new String[] {  "mapping/Compte.hbm.xml" });

     return sessionFactory;  
  }  

 @Bean  
  public DataSource restDataSource() {  
     BasicDataSource dataSource = new BasicDataSource();  
     dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));  
     dataSource.setUrl(env.getProperty("jdbc.url"));  
     dataSource.setUsername(env.getProperty("jdbc.user"));  
     dataSource.setPassword(env.getProperty("jdbc.pass"));  

     return dataSource;  
}

  @Bean
  public CompteDaoImpl compteDAO () {
      CompteDaoImpl compteDAO = new CompteDaoImpl();
      return compteDAO;
  }

  @Bean
  public SessionManagerFilter sessionManagerFilter () {
      SessionManagerFilter sessionManagerFilter = new SessionManagerFilter();
      return sessionManagerFilter;
  }

  @Bean  
  @Autowired  
  public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {  
     HibernateTransactionManager txManager = new HibernateTransactionManager();  
     txManager.setSessionFactory(sessionFactory);  

     return txManager;  
  }  

  @Bean  
  public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {  
     return new PersistenceExceptionTranslationPostProcessor();  
  }  

  Properties hibernateProperties() {  
     return new Properties() {  
        {  
           setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));  
           setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));  
           setProperty("hibernate.globally_quoted_identifiers", "true");  
        }  
     };  
  }  
}

The associated application.properties file contains the following lines:

# jdbc.X
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/gti525
jdbc.user=root
jdbc.pass=vente

# hibernate.X
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=false
hibernate.hbm2ddl.auto=create-drop
hibernate.current_session_context_class=thread

Finally, I have the followed servlet Filter, where I begin the transaction :

@Transactional
public class SessionManagerFilter implements Filter {

    @Autowired
    SessionFactory sessionFactory;


    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
         try {  
                Session currentSession = sessionFactory.getCurrentSession();
                currentSession.beginTransaction();  

                // Call the next filter (continue request processing)  
                chain.doFilter(request, response);  

                // Commit and cleanup  
                sessionFactory.getCurrentSession().getTransaction().commit(); 

            } catch (StaleObjectStateException staleEx) {  
                // Rollback, close everything, possibly compensate for any permanent changes  
                // during the conversation, and finally restart business conversation. Maybe  
                // give the user of the application a chance to merge some of his work with  
                // fresh data... what you do here depends on your applications design.  
                throw staleEx;  
            } catch (Throwable ex) {  
                // Rollback only  
                ex.printStackTrace();  
                try {  
                    if (sessionFactory.getCurrentSession().getTransaction().isActive()) {  
                        sessionFactory.getCurrentSession().getTransaction().rollback();  
                    }  
                } catch (Throwable rbEx) {  

                }  

                throw new ServletException(ex);  
            }  
    }

    @Override
    public void init(FilterConfig arg0) throws ServletException {

    }

}

Which is mapped in the web.xml file like this :

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
  <display-name>sitevente2</display-name>

     <filter>  
        <filter-name>SessionManagerFilter</filter-name>  
        <filter-class>ca.etsmtl.gti525.SessionManagerFilter</filter-class> 
        <!-- <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>    -->
    </filter>  

    <filter-mapping>  
        <filter-name>SessionManagerFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping> 


</web-app>

When the method beginTransaction() is called in the SessionManagerFilter's doFilter() method, I get the following Stack Trace :

org.hibernate.TransactionException: nested transactions not supported
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.begin(AbstractTransactionImpl.java:154)
at org.hibernate.internal.SessionImpl.beginTransaction(SessionImpl.java:1435)
at ca.etsmtl.gti525.SessionManagerFilter.doFilter(SessionManagerFilter.java:41)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:302)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:99)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:281)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:208)
at com.sun.proxy.$Proxy73.doFilter(Unknown Source)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:87)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.springframework.boot.context.web.ErrorPageFilter.doFilter(ErrorPageFilter.java:120)
at org.springframework.boot.context.web.ErrorPageFilter.access$000(ErrorPageFilter.java:61)
at org.springframework.boot.context.web.ErrorPageFilter$1.doFilterInternal(ErrorPageFilter.java:95)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.boot.context.web.ErrorPageFilter.doFilter(ErrorPageFilter.java:113)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:522)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1095)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:672)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Unknown Source)
2016-03-14 22:14:32.120 ERROR 7284 --- [nio-8080-exec-2]     o.s.t.i.TransactionInterceptor           : Application exception overridden by commit exception

I don't know what I am doing wrong. There must be something about the framework that I don't understand. I will really appreciate it if you could give me a hand.

Thanks! :)

alexbt
  • 16,415
  • 6
  • 78
  • 87

2 Answers2

1

Ok there are a couple of things wrong with your code as above. You seem to be confusing a Transactional session with a HTTP session. They are two different things. A Transactional session is a database call. You wrap database calls inside a transaction so that if something goes wrong the entire thing is rolled back. A HTTP session is not something that you should be using if you are going for a RESTful architecture, because a HTTP session implies that you are storing state somewhere in your application. REST operations are supposed to be idempotent (independent and repeatable). With a RESTful architecture, you should be able to perform the same call thousands of times and get the same result and not effecting any other part of your application.

In Spring, it is usual to have 3 separate layers. Your controllers manage HTTP requests and responses for specific objects, so you would have a CompteController and controllers for your other objects. These controllers are concerned with RESTful manipulation of these objects. GET, POST, PUT and DELETE requests are handled by the controllers. Controllers will generally do this by calling service classes. Service classes are used to manage transactions, manipulate objects, and encapsulate business logic. DAO classes are purely for database access.

You are getting your exception because you are trying to use a HTTP session alongside your transactional session. This is wrong for many different reasons and will cause a multitude of problems because this is not how @Transcational is supposed to be used.

I would restructure your code as follows:

Controller:

@RestController
public class CompteController { 

   @Autowired
   private CompteService compteService;

   @RequestMapping(value="/", method=RequestMethod.GET)
   public List<Compte> getAllCompte() throws Exception {
       /** Because you've used `@RestController` you've indicated that you want your object return in JSON format by this method. **/
       return compteService.getAllCompte();
   }
   /** methods here to handle GET, POST, PUT and DELETE requests**/

}

Service Layer

public interface CompteService {

    public Compte getCompte(int id);

    public List<Compte> getAllCompte();

    /** Other methods to create, update, manipulate and delete Compte objects **/
}

Service Implementation

@Service
public class CompteServiceImpl implements CompteService {

    @Autowired
    private CompteDAO compteDao;

    @Override
    @Transctional
    public Compte getCompte(int id) {
        return compteDao.getCompte(id);
    }
    @Override
    @Transcational
    public List<Compte> getAllCompte() {
        return compteDao.getAllCompte();
    }

}

The @Transactional annotation here, will be picked up the the HibernateTransactionManager which is responsible for creating, opening, maintaining and closing your database connection. This happens around the method that you annotate @Transactional. If you put @Transactional on the class, then all the class methods will be wrapped in a transaction. If an exception is thrown then the HibernateTransactionManager will roll back the transaction and propagate the exception back to the controller.

DAO:

public interface CompteDAO {

    public Compte findCompte(int id);

    public List<Compte> findAllCompte();

}

DAO Implementation:

@Repository
public class CompteDAOImpl implements CompteDAO {

    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public List<Compte> getAllComptes() {
        Session currentSession = sessionFactory.getCurrentSession();
        List<Compte> comptes = currentSession.createCriteria(Compte.class).list()
        return comptes;
    }
}   

You don't need to have to declare CompteDAOImpl as a @Bean because you are using @ComponentScan which will pick up classes annotated with @Controller, @RestController, @Service, @Repository, @Component and a few others. These classes will be available for autowiring into your other classes.

The above code separation makes it very easy to test your DAO, service and controller in isolation. It also separates the logic of your application from fetching objects from the database. If you ever think you need to explicitly manage your database connection, then something has gone wrong.

If you specifically want to catch StaleObjectStateException and do something different when that happens then you can do the following:

@ControllerAdvice
public class ExceptionHandlerController {

        protected static final Logger logger = LogManager.getLogger(ExceptionHandlerController.class);

    @ExceptionHandler(StaleObjectStateException.class)
    public ResponseEntity<String> handleStaleObjectStateException(StaleObjectStateException  e){
        logger.error("A stale object state exception has been thrown", e);
        /** Will return a HTTP 500 if you throw this exception **/
        return new ReponseEntity<String(HttpStatus.INTERNAL_SERVER_ERROR);
     }
}

Further reading on Hibernate and Spring (Disclosure: Q&A's I've created on the topic):

Community
  • 1
  • 1
JamesENL
  • 6,400
  • 6
  • 39
  • 64
  • Start the transaction in the Controller level. This style does not guarantee that a rest call is either commited as a whole or nothing. So it behaves different than the version of the question. – mh-dev Mar 15 '16 at 03:19
  • How so? If an exception occurs, then let Spring roll back the transaction and handle the exception either inside this controller or a dedicated exception handling controller annotated with `@ControllerAdvice` – JamesENL Mar 15 '16 at 03:21
  • I learned that developers tend to do more than one thing in controller. This includes calling different service methods. So for example I call a delete x method which works fine and than an add y method which throws an exception. In this situation you might end with corrupted data. – mh-dev Mar 15 '16 at 03:25
  • Surely though X & Y are separate entities and the fact that Y failed doesn't effect X in any way. If not, I agree that you would need to wrap that in the same transaction. Also, if they are separate entities, then it should be separate REST calls to create each entity. – JamesENL Mar 15 '16 at 03:29
  • If the Controller is implemented in lets say "correct way" than your right. But if seen to much in life to assume that :) – mh-dev Mar 15 '16 at 03:31
  • Hence the detailed example and explanation showing the "correct way" and explaining the reasoning behind why it's correct. – JamesENL Mar 15 '16 at 03:32
  • Thanks for the explanation! It was very clear. I get it now and it's all working :) – Xavier Jordi Mar 15 '16 at 03:51
0

You are using two different transaction management systems in your application.

In detail:

  1. Use of @Transactional in SessionManagerFilter
  2. currentSession.beginTransaction();

I never tested @Transactional on a Filter, since I usually start my transaction in a @RestController, but thats up to you.

mh-dev
  • 5,264
  • 4
  • 25
  • 23
  • I commented the currentSession.beginTransaction() line and it gives me a null pointer exception in this line in the same class: Session currentSession = sessionFactory.getCurrentSession(); – Xavier Jordi Mar 15 '16 at 03:07
  • So if I use @Transactional, the transaction is going to be created on its own? – Xavier Jordi Mar 15 '16 at 03:08
  • It seems for me that your following either a bad tutorial or you rmixing different once together. The (at)EnableTransactionManagement activates the spring transaction management. Which will get triggered by the (at)Transactiontal method. Which does basically means that each method invokation tracked by Spring (for example these are not class internal calls) starts a transaction depending on the propagation (default is start one if there's none). But I am not sure what will get handled by Spring in a Filter. – mh-dev Mar 15 '16 at 03:13
  • Filters are used for filtering and manipulating different types of web requests. You can have a HTTP filter, a FTP filter, a SSH filter etc, they are **not** supposed to be used for transaction management. See my answer for details. – JamesENL Mar 15 '16 at 03:15
  • If he wants something like that in the more correct way Google for OpenSessionInView. This is considered as an antipattern, but basically that what he does. – mh-dev Mar 15 '16 at 03:20