Reputation: 9028
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
Reputation: 1206
You are using javax.transaction.Transactional
, while you should be really using org.springframework.transaction.annotation.Transactional
.
Upvotes: 4
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
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
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