For web MVC Spring app should @Transactional go on controller or service?

controller, service, spring, transactional

Solution

There is no requirement for whether the `@Transactional` annotation should go on a Controller or on a Service, but typically it would go on a Service that would perform the logic for one request that logically should be performed within one ACID transaction.

In a typical Spring MVC application, you would have, minimally, two contexts: the application context and the servlet context. A context is a sort of configuration. The application context holds the configuration that is relevant for your entire application, whereas the servlet context holds configuration relevant only to your servlets. As such, the servlet context is a child of the application context and can reference any entity in the application context. The reverse is not true.

In your quote,

@EnableTransactionManagement and only looks for @Transactional on beans in the same application context they are defined in. This means that, if you put annotation driven configuration in a WebApplicationContext for a DispatcherServlet, it only checks for @Transactional beans in your controllers, and not your services.

`@EnableTransactionManagement` looks for `@Transactional` in beans in packages declared in the `@ComponentScan` annotation but only in the context (`@Configuration`) they are defined in. So If you have a `WebApplicationContext` for your `DispatcherServlet` (this is a servlet context), then `@EnableTransactionManagement` will look for `@Transactional` in classes you told it to component scan in that context (`@Configuration` class).

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "my.servlet.package")
public class ServletContextConfiguration {
    // this will only find @Transactional annotations on classes in my.servlet.package package
}

Since your `@Service` classes are part of the Application context, if you want to make those transactional, then you need to annotate your `@Configuration` class for the Application Context with `@EnableTransactionManagement`.

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "my.package.services")
public class ApplicationContextConfiguration {
    // now this will scan your my.package.services package for @Transactional
}

Use your Application Context configuration with a `ContextLoaderListener` and your Servlet Context configuration when instantiating your DispatcherServlet. (See the javadoc for a full java based config, instead of xml, if you aren't doing it already.)

Addendum: `@EnableTransactionManagement` has the same behavior as `<tx:annotation-driven />` in a java configuration. Check here for using `ContextLoaderListener` with XML.

Problem

For WebApplicationContext, should I put `@Transactional` annotations in the controller or in services? The Spring docs have me a bit confused. Here is my web.xml: ``` <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>Alpha v0.02</display-name> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>*.json</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app> ``` Here is my application-context.xml defining a spring dispatcher servlet: ``` <?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:mvc="http://www.springframework.org/schema/mvc" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:annotation-config /> <mvc:annotation-driven /> <tx:annotation-driven /> <context:component-scan base-package="com.visitrend" /> <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"/> <property name="suffix" value=".jsp"/> </bean> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> <property name="driverClass" value="org.postgresql.Driver" /> <property name="jdbcUrl" value="jdbc:postgresql://localhost:5432/postgres" /> <property name="user" value="someuser" /> <property name="password" value="somepasswd" /> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="configLocation" value="classpath:test.hibernate.cfg.xml" /> </bean> <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="dataSource" ref="dataSource" /> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans> ``` Here's a service interface: ``` public interface LayerService { public void createLayer(Integer layerListID, Layer layer); } ``` Here's a service implementation: ``` @Service public class LayerServiceImpl implements LayerService { @Autowired public LayerDAO layerDAO; @Transactional @Override public void createLayer(Integer layerListID, Layer layer) { layerDAO.createLayer(layerListID, layer); } } ``` And here's my controller: ``` @Controller public class MainController { @Autowired private LayerService layerService; @RequestMapping(value = "/addLayer.json", method = RequestMethod.POST) public @ResponseBody LayerListSetGroup addLayer(@RequestBody JSONLayerFactory request) { layerService.createLayer(request.getListId(), request.buildLayer()); return layerService.readLayerListSetGroup(llsgID); } } ``` The Spring documentation has me a bit confused. It seems to indicate that using a WebApplicationContext means only controllers will be investigated for @Transactional annotations and not services. Meanwhile I see tons of recommendations to make services transactional and not controllers. I'm thinking that using `<context:component-scan base-package="com..." />` in our spring-servlet.xml above so that it includes the services packages means the services are part of the context, and therefore will be "investigated" for transactional annotations. Is this accurate? Here's the Spring documentation blurb that got me confused: @EnableTransactionManagement and only looks for @Transactional on beans in the same application context they are defined in. This means that, if you put annotation driven configuration in a WebApplicationContext for a DispatcherServlet, it only checks for @Transactional beans in your controllers, and not your services. Further, is there any performance implications or "badness" if I define a controller method as transactional, and it calls a transactional method in a another class? My hunch is no, based on the documentation, but would love validation on that.

Original source

Related problems