Athanor
Athanor

Reputation: 955

Unable to perform insert/update/delete

I want to use a transaction manager in my application using Hibernate and JPA. I started by configuring applicationContext.xml (I also want to use c3p0 but i'll configure it later):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:component-scan base-package="dao,business" />
    <!-- Configuration du transaction manager -->
    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="entity" />

        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
        </property>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.connection.driver_class">com.mysql.jdbc.Driver</prop>
                <prop key="hibernate.hbm2ddl.auto">validate</prop>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                <prop key="current_session_context_class">thread</prop>
                <!-- <prop key="hibernate.cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</prop> -->
                <!-- used for debug -->
                <prop key="hibernate.show_sql">true</prop>
                <!-- EhCache -->
                <!-- <prop key="hibernate.cache.provider_configuration_file_resource_path" 
                    value="classpath:ehcache.xml</prop> -->
                <!-- <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
                <prop key="hibernate.cache.provider_class">org.hibernate.cache.SingletonEhCacheProvider</prop>
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
                <prop key="hibernate.cache.use_query_cache">true</prop>
                <prop key="hibernate.generate_statistics">true</prop> -->
                <!-- configuration pool via c3p0, see https://community.jboss.org/wiki/HowToConfigureTheC3P0ConnectionPool -->
                <!-- <prop key="hibernate.connection.provider_class">org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider</prop> 
                    <prop key="hibernate.c3p0.acquire_increment">1</prop> <prop key="hibernate.c3p0.max_size">5</prop> 
                    <prop key="hibernate.c3p0.max_statements">100</prop> <prop key="hibernate.c3p0.min_size">1</prop> 
                    <prop key="hibernate.c3p0.timeout">100</prop> <prop key="hibernate.checkoutTimeout">1000</prop> 
                    <prop key="hibernate.c3p0.idleConnectionTestPeriod">30</prop> <prop key="hibernate.c3p0.preferredTestQuery">SELECT 
                    1</prop> -->
                <!-- <prop key="c3p0.testConnectionOnCheckout">true</prop> -->
                <!-- <prop name="eclipselink.jdbc.bind-parameters">false</prop> -->
            </props>
        </property>
    </bean>
    <!-- Configuration de la BDD -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://localhost:3306/my_db?autoReconnect=true" />
        <property name="username" value="user" />
        <property name="password" value="password" />
    </bean>
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>
    <tx:annotation-driven />
    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />
</beans>

My DAO look like this:

package dao.impl;
import dao.CategoryDao;
import entity.Category;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Repository
@Transactional
public class CategoryDaoJpa implements CategoryDao {
    @PersistenceContext
    private EntityManager em;
    @Override
    public int save(Category entity) {
        em.persist(entity);
        return entity.getId();
    }
    @Override
    public void remove(Category entity) {
        em.remove(entity);
    }
    @Override
    public void remove(int id) {
        em.createQuery("DELETE FROM Category WHERE id=:id").setParameter("id", id).executeUpdate();
    }
    @Override
    public void update(Category entity) {
        em.merge(entity);
    }
    @Override
    public Category load(int id) {
        return em.find(Category.class, id);
    }
    @Override
    public List<Category> loadAll() {
        return em.createQuery("FROM Category ORDER BY type,sortIndex", Category.class).getResultList();
    }
    @Override
    public void update(int id, String value) {
        em.createNativeQuery("UPDATE category SET titre=:t WHERE id=:id")
                .setParameter("t", value)
                .setParameter("id", id)
                .executeUpdate();
    }
}

I call it in my business object:

package business;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import dao.CategoryDao;
import entity.Category;

@Service
@Transactional
public class CategoryService {
    @Autowired
    private CategoryDao dao;

    public void update(int id, String title) {
        Category c=dao.load(id);
        c.setTitre(title);
        dao.update(c);
    }
}

When I call the update(Category) function, nothing is happening :( If I call update(int, String), I got this error:

Grave: Servlet.service() for servlet [mvc-dispatcher] in context with path [/my-app] threw exception [Request processing failed; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query] with root cause
javax.persistence.TransactionRequiredException: Executing an update/delete query
    at org.hibernate.jpa.spi.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:71)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:333)
    at com.sun.proxy.$Proxy156.executeUpdate(Unknown Source)
    at dao.impl.CategoryDaoJpa.update(CategoryDaoJpa.java:53)
    at business.CategoryService.update(CategoryService.java:53)
    at controller.CategoryController.pageListe(CategoryController.java:31)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:606)
    at org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:175)
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:446)
    at org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:434)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:647)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:728)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at filter.AuthFilter.doFilter(AuthFilter.java:36)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:100)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1041)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:603)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:310)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)

Upvotes: 4

Views: 3995

Answers (4)

Serge Ballesta
Serge Ballesta

Reputation: 148880

Stack trace gives elements : update outside of a transaction, and dao.impl.CategoryDaoJpa directly injected in CategoryService without the transactional proxy and CategoryService directly injected in CategoryController. So the reason of the error is that spring ignores the @Transactional annotation on CategoryDaoJpa and on CategoryService.

First, you should set either the service or the dao to be transactional, but not both. It is more common to have the service to be transactional, because an single method of service could call many methods in dao in a single transaction.

I do not really understand why spring ignores your @Transactional annotations. Spring Reference Manual says : <tx:annotation-driven/> only looks for @Transactional on beans in the same application context it is defined in.. Maybe you should try to explicitely define in applicationContext.xml the beans that should exhibit the transactional behaviour.

Upvotes: 1

Athanor
Athanor

Reputation: 955

I found the solution, it was because my filter was not @Transactionnal

package filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component //was missing
@Transactional(propagation=Propagation.REQUIRED) //was missing
public class AuthFilter implements Filter {
    @Override
    //no use so not implemented
    public void destroy() {
        //Ignore
    }
    @Override
    //no use so not implemented
    public void init(FilterConfig config) throws ServletException {
        //Ignore
    }
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
        if(((HttpServletRequest)req).getSession().getAttribute("userAccount")==null) {
            HttpServletResponse response=(HttpServletResponse)resp;
            response.sendRedirect(((HttpServletRequest)req).getContextPath()+"/index.html");
            return;
        }
        chain.doFilter(req, resp);
    }


}

Upvotes: 0

geoand
geoand

Reputation: 63991

The problem turns out to be very subtle!

In addition to applicationContext.xml you have the file mvc-dispatcher-servlet.xml where you configure the Web application context. The problem is that file you have enabled component scanning on

<context:component-scan base-package="dao" />
<context:component-scan base-package="business" />

in that file.

That is clearly wrong since those beans should only be present in the root application context (and they are since you have actually enabled component scanning for those packages in applicationContext.xml).

What was causing the problem was that the service and dao beans from the Web application context where being used, and in that context there was no transaction manager, and there for no proxy was being created.

If you simply remove

<context:component-scan base-package="dao" />
<context:component-scan base-package="business" />

from mvc-dispatcher-servlet.xml then everything will work as expected because the beans from the root application context will be used (and of course will be proxied due to the existence of the transaction manager).

Upvotes: 3

Vlad Mihalcea
Vlad Mihalcea

Reputation: 153710

You should remove this:

<prop key="hibernate.connection.driver_class">com.mysql.jdbc.Driver</prop>

As you already provide a Datasource, of which Hibernate is already aware of:

<property name="dataSource" ref="dataSource" />

I don't see the TransactionInterceptor in your stacktrace which indicates you have no transaction available.

Also there is no proxying involved:

    at dao.impl.CategoryDaoJpa.update(CategoryDaoJpa.java:53)
    at business.CategoryService.update(CategoryService.java:53)
    at controller.CategoryController.pageListe(CategoryController.java:31)

The Service is called by the controller directly without using any proxy, like it's the case with any Spring Bean. This is why you don't see the TransactionInterceptor in your stacktrace.

How are you injecting the service in the web Controller? You should make sure you use dependency injection and not instantiate the bean manually or something.

Upvotes: 3

Related Questions