abiieez
abiieez

Reputation: 3189

Spring MVC 3 with Hibernate 4 integration

My current goal is to enable transaction management in my web app with using Hibernate 4.

Firstly, my web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <!-- The definition of the Root Spring Container shared by all Servlets 
        and Filters -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
             /WEB-INF/spring/root-context.xml,
             /WEB-INF/spring/appServlet/*-context.xml
        </param-value>
    </context-param>


    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <listener>
        <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
    </listener>

    <!-- Processes application requests -->
    <servlet>
        <servlet-name>mvc-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>mvc-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

    <!-- Spring Security -->
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

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

    <session-config>
        <session-timeout>5</session-timeout>
    </session-config>
</web-app>

mvc-dispatcher-servlet.xml (renamed from servlet-context.xml):

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="sec://www.springframework.org/schema/mvc"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <!-- DispatcherServlet Context: defines this servlet's request-processing 
        infrastructure -->

    <context:component-scan base-package="com.moose.springmvc.controller" />

    <!-- Enables the Spring MVC @Controller programming model -->
    <mvc:annotation-driven />

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving 
        up static resources in the ${webappRoot}/resources directory -->
    <mvc:resources mapping="/resources/**" location="resources/" />

    <!-- Resolves views selected for rendering by @Controllers to .jsp resources 
        in the /WEB-INF/views directory -->
    <beans:bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <beans:property name="prefix" value="/WEB-INF/views/" />
        <beans:property name="suffix" value=".jsp" />
    </beans:bean>

    <mvc:interceptors>
        <beans:bean
            class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
            <beans:property name="paramName" value="lang" />
        </beans:bean>
    </mvc:interceptors>
</beans:beans>

My database-context.xml (renamed from spring-database.xml) configuration:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <context:component-scan base-package="com.moose.springmvc.service,com.moose.springmvc.dao" />

    <tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>

    <bean id="transactionManager"
        class="org.springframework.orm.hibernate4.HibernateTransactionManager"
        p:sessionFactory-ref="sessionFactory" />

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
        p:packagesToScan="com.moose.springmvc.entity" p:dataSource-ref="dataSource"
        p:configLocation="classpath:hibernate.cfg.xml">
    </bean>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/crud" />
        <property name="user" value="root" />
        <property name="password" value="mysql@123#" />
        <!--  TODO adjust pooling parameters... -->
    </bean>
</beans>

My DAO class:

@Repository
public class UserDAO
{
    @Autowired
    private SessionFactory sessionFactory;

    ...

    public int save(User user)
    {
        return (Integer) sessionFactory.getCurrentSession().save(user);
    }

    ...  
}

My Service class:

@Service
@Transactional
public class UserService {

    protected static Logger logger = LoggerFactory
            .getLogger(UserService.class);

    @Autowired
    private UserDAO userDAO;


    public Integer createUser(User user){

        return userDAO.save(user);
    }
}

Relevant code for AjaxBootstrapController class:

@Controller
public class AjaxBootstrapController {
    @Autowired
    private UserService userService;

        ....

    @RequestMapping(value = "/userAjaxCustomTag", method = RequestMethod.POST)
    public String processFormAjax(
            @ModelAttribute(value = "user") @Valid User user,
            BindingResult result) {
        if (!result.hasErrors()) {
            userService.createUser(user);
        }
        return "userForm";
    }

Edit

hibernate.cfg.xml config:

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <!-- Database connection settings -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/crud</property>
        <property name="connection.username">root</property>
        <property name="connection.password">mysql@123#</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.MySQLDialect</property>

        <!-- Disable the second-level cache -->
        <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Drop and re-create the database schema on startup
        <property name="hbm2ddl.auto">create</property>
         -->
    </session-factory>
</hibernate-configuration>

I get new exception :

org.hibernate.HibernateException: save is not valid without active transaction
    at org.hibernate.context.internal.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:348)
    at $Proxy31.save(Unknown Source)
    at com.moose.springmvc.dao.UserDAO.save(UserDAO.java:45)
    at com.moose.springmvc.service.UserService.createUser(UserService.java:23)
    at com.moose.springmvc.service.UserService$$FastClassByCGLIB$$60239454.invoke(<generated>)
    at net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:191)
    at org.springframework.aop.framework.Cglib2AopProxy$CglibMethodInvocation.invokeJoinpoint(Cglib2AopProxy.java:689)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
    at com.moose.springmvc.service.UserService$$EnhancerByCGLIB$$419f68a0.createUser(<generated>)
    at com.moose.springmvc.controller.AjaxBootstrapController.processFormAjax(AjaxBootstrapController.java:60)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

Is there anything wrong with configuration or annotation ?

Upvotes: 1

Views: 5262

Answers (2)

anshumn
anshumn

Reputation: 33

I have couple of suggestions, you can check them -

Are you using CGLIB to generate the proxy for the Service to use transactions? Otherwise, your service should implement an interface so that jdk proxies are created which create a proxy using the interface.

additionally specify the followings in your tx:annotation-driven tag -

<tx:annotation-driven transaction-manager="transactionManager" mode="proxy"/>

You can configure Hibernate configuration file in your SessionFactory bean in spring-database.xml. With this you can see the SQL logs if you have configured show-sql flag as true, to better understand the issue

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"
    p:packagesToScan="com.moose.springmvc.dao" p:dataSource-ref="dataSource"
    p:configLocation="specify-path-location-of-hibernate.cfg.xml e.g. /WEB-INF/hibernate/hibernate.cfg.xml">
</bean>

Please check with these.

Upvotes: 0

Adrian Shum
Adrian Shum

Reputation: 40036

From your stack trace, it seems that there is no proxy around your userService instance, which is quite abnormal as there should be a proxy around it which is the aspect for transaction handling (assume you are using Load Time Weaving, which is the most common way for AOP in Spring)

Not sure if it is your problem, but I recently faced a similar problem and the issue turned out to be quite tricky.

I assume that you are using component-scan feature of Spring

Seems that you are using SpringMVC, there is another app context file loaded by the dispatcher servlet. (normally named YOUR_SERVLET_NAME-servlet.xml)

Spring is loading it as a separate child app context of the main one.

If, in that servlet app context, you have declared component-scan again, it will load it a separate UserService instance, and, if you don't have any transaction setting in that app context, this UserService instance will not have the corresponding proxy (for tranasaction control) generated.

The method to solve is easy in such case, just make sure in the servlet app context, restrict your component scan to not to scan for beans already exists in the main app context.

Upvotes: 2

Related Questions