1

Following is all my code. The database row is not created. No exception thrown.

package com.rishi.app.models;

import java.util.Collection;

import javax.management.Query;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnit;
import javax.persistence.TypedQuery;

@Entity
public class Customer {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;
    private String firstName;
    private String lastName;

    protected Customer() {}

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return String.format(
                "Customer[id=%d, firstName='%s', lastName='%s']",
                id, firstName, lastName);
    }

}


package com.rishi.app.repositories;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

import org.springframework.stereotype.Repository;

import com.rishi.app.models.Customer;

@Repository
public class CustomerRepository {
    @PersistenceContext
    private EntityManager em;

    @Transactional
    public void save(Customer c) {
        em.persist(c);
    }
}

package com.rishi.app.controllers;

import java.text.DateFormat;
import java.util.Date;
import java.util.Locale;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import com.rishi.app.models.Customer;
import com.rishi.app.repositories.CustomerRepository;

/**
 * Handles requests for the application home page.
 */
@Controller
public class HomeController {
    @PersistenceContext
    EntityManager entityManager;


    @Autowired
    EntityManagerFactory entityManagerFactory;

    @Autowired
    CustomerRepository customerRepository;

    private static final Logger logger = LoggerFactory.getLogger(HomeController.class);

    /**
     * Simply selects the home view to render by returning its name.
     * @throws Exception 
     */
    @Transactional
    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String home(Locale locale, Model model) {
        logger.info("Welcome home controller! The client locale is {}.", locale);

        Customer c = new Customer("Rishi", "Paranjape");
        customerRepository.save(c);

            //Following 4 lines work just fine. Database row is actually created.
        //EntityManager em = entityManagerFactory.createEntityManager();
        //em.getTransaction().begin();
        //em.persist(c);
        //em.getTransaction().commit();

        Date date = new Date();
        DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);

        String formattedDate = dateFormat.format(date);

        model.addAttribute("serverTime", formattedDate );
        //throw new Exception("bad stuff");
        return "home";
    }

}

My servlet context xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    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/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">

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

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

    <!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
    <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>

    <context:component-scan base-package="com.rishi.app" />


    <beans:bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
      <beans:property name="dataSource" ref="dataSource" />
      <beans:property name="packagesToScan" value="com.rishi.app.models" />
      <beans:property name="jpaVendorAdapter">
         <beans:bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
      </beans:property>
      <beans:property name="jpaProperties">
         <beans:props>
            <beans:prop key="hibernate.hbm2ddl.auto">validate</beans:prop>
            <beans:prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</beans:prop>
         </beans:props>
      </beans:property>
   </beans:bean>

   <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
      <beans:property name="driverClassName" value="com.mysql.jdbc.Driver" />
      <beans:property name="url" value="jdbc:mysql://localhost:3306/spring" />
      <beans:property name="username" value="rishi" />
      <beans:property name="password" value="password" />
   </beans:bean>

    <beans:bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <beans:property name="entityManagerFactory" ref="entityManagerFactory" />
   </beans:bean>
   <tx:annotation-driven mode="aspectj" transaction-manager="transactionManager" />


</beans:beans>

I have root context and servlet context. My all the beans are in servlet context (my root context is almost empty). My main problem is em.persist() is being called but does nothing. If I debug the application I can see save method from CustomerRepository being called. There is no exception thrown or error shown whatsoever. But yet, no database row is created.

hrishikeshp19
  • 8,838
  • 26
  • 78
  • 141
  • Could you post your web.xml also? – Angular University Jan 29 '14 at 10:50
  • Nice classes but I guess you also fell into the trap of duplicate component-scanning, which leads to both proxied and un-proxied instances of your `CustomerRepository` which basically renders all your AOP (including transactions) useless. – M. Deinum Jan 29 '14 at 10:53
  • With out your root and servlet context config files we're all guessing. Please post them. – MarkOfHall Jan 29 '14 at 20:22
  • I have added a second answer bellow after seeing the servlet context file. Did you try to remove mode="aspectj", how did it turn out in the end? – Angular University Feb 01 '14 at 10:47

4 Answers4

6

The likely cause is that the @Transactional is being applied to beans in the wrong spring context.

Many Spring MVC applications have two contexts: one root context usually used for common beans such as transactional services/repositories, and a web specific context containing among others the controllers, see this answer for further details.

What seems to be happening is that tx:annotation-driven is being applied to a context where neither the controller or the repository exist.

It looks like @Transactional is applied to the root context, and put all the beans in the dispatcher context.

If it's the case, move tx:annotation-driven to the XML files where the beans are defined or the corresponding component-scan. That will apply @Transactional in the context where the beans are for sure.

Usually as a general best practice we apply @Transactional not on the controller and neither on the repository, but in an intermediate service layer bean annotatted with @Service, which contains the business logic.

But repositories and controllers are just spring beans, so if you want to use @transactional that works too.

Community
  • 1
  • 1
Angular University
  • 42,341
  • 15
  • 74
  • 81
  • I have root context and servlet context. My all the beans are in servlet context (my root context is almost empty). My main problem is em.persist() is being called. If I debug the application I can see save method from CustomerRepository being called. There is no exception thrown or error shown whatsoever. But yet, no database row is created. – hrishikeshp19 Jan 29 '14 at 19:00
  • If tx:annotation-driven is in the root context only, that would explain why beans in the servlet context are not transactional. Try to add tx:annotation-driven in the servlet context as well. Otherwise if you post the servlet context xml and the root context xml, that would help pinpoint the solution. – Angular University Jan 29 '14 at 19:18
  • No you did not get me. tx annotation driven is in the servlet context not in the root context. – hrishikeshp19 Jan 29 '14 at 19:23
  • 1
    And the context:component-scan (or the xml bean definitions), is it also in the servlet context? Because if it's in the root context then it's normal that @Transactional does not work, have a look at this answer http://stackoverflow.com/questions/10019426/spring-transactional-not-working – Angular University Jan 29 '14 at 19:48
  • @khadesdev: please see the updated question. Sorry for not posting servlet context before. – hrishikeshp19 Jan 30 '14 at 17:19
  • This is awesome sir...!!! I was tired googling about the same but was not getting proper answer from anywhere...! but what you said is completely true and now mine is working as expected..! tons of thanks for this...! – Tushar J Dudhatra Feb 08 '15 at 10:10
4

You are using javax.transaction.Transactional, while you should be really using org.springframework.transaction.annotation.Transactional.

Mateusz Rasiński
  • 1,186
  • 1
  • 13
  • 15
2

Start off by writing an integration test for just your repository. It will be easier to narrow down whether the issue is in your controller or repository.

In theory, your @Transactional repository save() method isn't in an interface, so a JDK dynamic proxy won't be created for it unless you add proxy-target-class=true to your <tx:annotation-driven> element and add CGLIB to your classpath. If the proxy isn't created, there's no advice to perform the transactional work. You can check this by setting a breakpoint in the repository and looking at the stack frames to see if there's any mention of the TransactionInterceptor class.

In practice, if the transaction proxy were missing, I'd expect to get an exception that you don't have an open session (at least in Hibernate.) Since you're not getting an exception, I suspect Spring is decorating your controller's @Transactional and that this isn't a transaction issue.

To test if it is a transactional issue, call flush() after your save, put a breakpoint after the flush but before the transaction is closed, and in some other place (integration test, DB console, whatever) create a new transaction with READ_UNCOMMITTED isolation, and check if you can dirty read (see) the row.

Emerson Farrugia
  • 11,153
  • 5
  • 43
  • 51
0

Remove mode=aspectj, as this does not work without a JVM agent and <context:load-time-weaver/> (see also this answer).

Removing this will allows Spring to use the non-aspectj weaving mechanism (JDK proxies or CGLIB proxies if applicable), so @Transactional should then work any beans in the servlet context.

Community
  • 1
  • 1
Angular University
  • 42,341
  • 15
  • 74
  • 81