tutorial proyecto mvc example ejemplo crear spring service controller transactional

proyecto - Para la aplicación web de MVC Spring, ¿debería @Transactional ir en el controlador o servicio?



spring mvc example (3)

A veces es muy conveniente tener métodos de controlador @Transactional, especialmente cuando se realizan operaciones triviales con Hibernate. Para habilitar esto usando la configuración XML, agregue esto a su dispatch-servlet.xml:

<beans ... xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="... http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd"> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" /> .. </beans>

El propósito de proxy-target-class es usar los proxies CGLIB que se requieren para que AOP en los controladores funcione. Si no agrega esto, obtendrá un error al iniciar. Además, si tiene algún método final en sus controladores, tenga en cuenta que no pueden ser procesados ​​(en particular, se hacen transaccionales), y también recibirá una advertencia de CGLIB para cada uno de estos métodos, al iniciar.

Para WebApplicationContext, ¿debo poner anotaciones @Transactional en el controlador o en los servicios? Los documentos de primavera me tienen un poco confundido.

Aquí está mi 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>

Aquí está mi aplicación-contexto.xml que define un servlet de despachador de resortes:

<?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>

Aquí hay una interfaz de servicio:

public interface LayerService { public void createLayer(Integer layerListID, Layer layer); }

Aquí hay una implementación de servicio:

@Service public class LayerServiceImpl implements LayerService { @Autowired public LayerDAO layerDAO; @Transactional @Override public void createLayer(Integer layerListID, Layer layer) { layerDAO.createLayer(layerListID, layer); } }

Y aquí está mi controlador:

@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); } }

La documentación de primavera me tiene un poco confundido. Parece indicar que el uso de WebApplicationContext significa que solo se investigarán los controladores para las anotaciones @Transactional y no los servicios. Mientras tanto veo toneladas de recomendaciones para hacer servicios transaccionales y no controladores. Estoy pensando que usar <context:component-scan base-package="com..." /> en nuestro spring-servlet.xml anterior para que incluya los paquetes de servicios significa que los servicios son parte del contexto, y por lo tanto Será "investigado" por anotaciones transaccionales. ¿Es esto correcto?

Aquí está la propaganda de documentación de primavera que me confundió:

@EnableTransactionManagement y solo busca @Transactional en beans en el mismo contexto de aplicación en el que están definidos. Esto significa que, si coloca la configuración controlada por anotación en un WebApplicationContext para un DispatcherServlet, solo verifica los beans @Transactional en sus controladores, y no sus servicios

Además, ¿hay alguna implicación de rendimiento o "maldad" si defino un método de controlador como transaccional, y se llama un método transaccional en otra clase? Mi corazonada es no, según la documentación, pero me encantaría la validación de eso.


El Servicio es el mejor lugar para poner demarcaciones transaccionales. El servicio debe mantener el comportamiento de caso de uso de nivel de detalle para una interacción del usuario, lo que significa cosas que lógicamente irían juntas en una transacción. También de esa manera se mantiene una separación entre el código de la aplicación web y la lógica empresarial.

Hay una gran cantidad de aplicaciones CRUD que no tienen una lógica de negocios significativa, para ellas no es útil tener una capa de servicio que solo pase cosas entre los controladores y los objetos de acceso a datos. En esos casos, podría salirse con la colocación de la anotación de transacción en los objetos de acceso a datos.

Poner la anotación transaccional en el controlador puede causar problemas, consulte [la documentación de Spring MVC] [1], 17.3.2:

Un error común cuando se trabaja con clases de controlador anotadas ocurre cuando se aplica una funcionalidad que requiere la creación de un proxy para el objeto del controlador (por ejemplo, métodos @Transactional). Por lo general, se introducirá una interfaz para el controlador con el fin de utilizar proxies dinámicos JDK. Para hacer que esto funcione, debe mover las anotaciones de @RequestMapping, así como cualquier otro tipo y anotaciones a nivel de método (por ejemplo, @ModelAttribute, @InitBinder) a la interfaz, así como el mecanismo de mapeo solo puede "ver" la interfaz expuesta por apoderado. Alternativamente, puede activar proxy-target-class = "true" en la configuración para la funcionalidad aplicada al controlador (en nuestro escenario de transacción en). Hacerlo indica que se deben usar proxies de subclase basados ​​en CGLIB en lugar de proxies JDK basados ​​en interfaz. Para obtener más información sobre los diversos mecanismos de proxy, consulte la Sección 9.6, “Mecanismos de proxy”.

Los comportamientos de propagación de transacciones que configura en los atributos deciden qué sucede cuando un método transaccional llama a otro método transaccional. Puede configurarlo para que el método llamado use la misma transacción, o para que siempre use una nueva transacción.

Al tener varias llamadas a su servicio en el código de ejemplo, está rechazando el propósito transaccional del servicio. Las diferentes llamadas a su servicio se ejecutarán en diferentes transacciones si coloca las anotaciones transaccionales en el servicio.


No hay ningún requisito sobre si la anotación @Transactional debería ir en un Controlador o en un Servicio, pero normalmente iría en un Servicio que realizaría la lógica para una solicitud que lógicamente debería realizarse dentro de una transacción ACID.

En una aplicación Spring MVC típica, tendría, como mínimo, dos contextos: el contexto de la aplicación y el contexto del servlet. Un contexto es una especie de configuración. El contexto de la aplicación contiene la configuración que es relevante para toda la aplicación, mientras que el contexto del servlet mantiene la configuración relevante solo para sus servlets. Como tal, el contexto del servlet es un elemento secundario del contexto de la aplicación y puede hacer referencia a cualquier entidad en el contexto de la aplicación. Lo opuesto no es verdad.

En tu cita,

@EnableTransactionManagement y solo busca @Transactional en beans en el mismo contexto de aplicación en el que están definidos. Esto significa que, si coloca la configuración controlada por anotación en un WebApplicationContext para un DispatcherServlet, solo verifica los beans @Transactional en sus controladores, y no sus servicios

@EnableTransactionManagement busca @Transactional en beans en los paquetes declarados en la anotación @ComponentScan pero solo en el contexto ( @Configuration ) en el que están definidos. Entonces, si tiene un WebApplicationContext para su DispatcherServlet (este es un contexto de servlet), entonces @EnableTransactionManagement buscará @Transactional en las clases que le dijo a la exploración de componentes en ese contexto (clase @Configuration ).

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

Ya que sus clases @Service son parte del contexto de la aplicación, si desea hacerlas transaccionales, entonces necesita anotar su clase @Configuration para el contexto de la aplicación con @EnableTransactionManagement .

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

Utilice la configuración del contexto de la aplicación con un ContextLoaderListener y la configuración del contexto del servlet al crear una instancia de DispatcherServlet. ( Consulte el javadoc para una configuración basada en java completa, en lugar de xml, si aún no lo está haciendo).

Addendum: @EnableTransactionManagement tiene el mismo comportamiento que <tx:annotation-driven /> en una configuración de java. Marque aquí para usar ContextLoaderListener con XML.