hrishikeshp19
hrishikeshp19

Reputation: 9028

@Transactional annotation does not work. Database row not created

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.

Upvotes: 1

Views: 10615

Answers (4)

Mateusz Rasiński
Mateusz Rasiński

Reputation: 1206

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

Upvotes: 4

Angular University
Angular University

Reputation: 43087

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.

Upvotes: 0

Emerson Farrugia
Emerson Farrugia

Reputation: 11353

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.

Upvotes: 2

Angular University
Angular University

Reputation: 43087

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.

Upvotes: 6

Related Questions