sirve que para mvc modelandview formulario example ejemplo configurar java spring-mvc controller transactional

java - que - ¿Por qué no deberíamos hacer un controlador Spring MVC @Transactional?



spring mvc netbeans (3)

En ocasiones, desea deshacer una transacción cuando se lanza una excepción, pero al mismo tiempo que desea manejar la excepción, cree una respuesta adecuada en el controlador.

Si coloca @Transactional en el método del controlador, la única forma de aplicar la reversión es lanzar la transacción desde el método del controlador, pero luego no puede devolver un objeto de respuesta normal.

Actualización: una reversión también se puede lograr programáticamente, como se indica en la respuesta de Rodério .

Una mejor solución es hacer que su método de servicio sea transaccional y luego manejar una posible excepción en los métodos del controlador.

El siguiente ejemplo muestra un servicio de usuario con un método createUser , ese método es responsable de crear al usuario y enviar un correo electrónico al usuario. Si falla el envío del correo, queremos deshacer la creación del usuario:

@Service public class UserService { @Transactional public User createUser(Dto userDetails) { // 1. create user and persist to DB // 2. submit a confirmation mail // -> might cause exception if mail server has an error // return the user } }

Luego, en su controlador puede envolver la llamada a createUser en try / catch y crear una respuesta adecuada para el usuario:

@Controller public class UserController { @RequestMapping public UserResultDto createUser (UserDto userDto) { UserResultDto result = new UserResultDto(); try { User user = userService.createUser(userDto); // built result from user } catch (Exception e) { // transaction has already been rolled back. result.message = "User could not be created " + "because mail server caused error"; } return result; } }

Si pones @Transaction en tu método de controlador, eso simplemente no es posible.

Ya hay algunas preguntas sobre el tema, pero ninguna respuesta proporciona argumentos para explicar por qué no deberíamos hacer un controlador Spring MVC Transactional . Ver:

¿Entonces por qué?

  • ¿Hay problemas técnicos insuperables ?
  • ¿Hay problemas arquitectónicos?
  • ¿Hay problemas de rendimiento / interbloqueo / concurrencia?
  • A veces se requieren múltiples transacciones por separado? En caso afirmativo, ¿cuáles son los casos de uso? (Me gusta el diseño simplificador, que las llamadas al servidor tienen éxito completo o fallan por completo. Suena como un comportamiento muy estable)

Antecedentes: trabajé hace unos años en un equipo en un software ERP bastante grande implementado en C # / NHibernate / Spring.Net. El viaje de ida y vuelta al servidor se implementó exactamente así: la transacción se abrió antes de ingresar cualquier lógica del controlador y se comprometió o revertió después de salir del controlador. La transacción se gestionó en el marco para que nadie tuviera que preocuparse por ello. Era una solución brillante: estable, simple, solo unos pocos arquitectos tenían que preocuparse por los problemas de transacción, el resto del equipo acababa de implementar las funciones.

Desde mi punto de vista, es el mejor diseño que he visto en mi vida. Como traté de reproducir el mismo diseño con Spring MVC, entré en una pesadilla con problemas de carga y carga lenta y siempre la misma respuesta: no haga que el controlador sea transaccional, pero ¿por qué?

¡Gracias de antemano por sus respuestas fundadas!


He visto ambos casos en la práctica, en aplicaciones web comerciales de tamaño mediano a grande, utilizando varios marcos web (JSP / Struts 1.x, GWT, JSF 2, con Java EE y Spring).

En mi experiencia, es mejor delimitar las transacciones al más alto nivel, es decir, al nivel de "controlador".

En un caso, teníamos una clase BaseAction extendía la clase de Action Struts, con una implementación para el método de execute(...) que manejaba la gestión de sesión de Hibernate (guardada en un objeto ThreadLocal ), inicio / compromiso / retrotracción de transacción y el mapeo de excepciones a los mensajes de error fáciles de usar. Este método simplemente revertiría la transacción actual si alguna excepción se propagara hasta este nivel, o si se marcó solo para la reversión; de lo contrario, comprometería la transacción. Esto funcionó en todos los casos, donde normalmente hay una única transacción de base de datos para todo el ciclo de solicitud / respuesta HTTP. Los casos raros en los que se necesitaban transacciones múltiples se manejarían en el código específico del caso de uso.

En el caso de GWT-RPC, una implementación similar de Servlet GWT implementó una solución similar.

Con JSF 2, hasta ahora solo he utilizado la demarcación de nivel de servicio (usando beans EJB Session que automáticamente tienen propagación de transacción "REQUERIDA"). Aquí hay desventajas, a diferencia de las transacciones demarcadas a nivel de los beans de respaldo de JSF. Básicamente, el problema es que, en muchos casos, el controlador JSF necesita realizar varias llamadas de servicio, cada una accediendo a la base de datos de la aplicación. Con las transacciones a nivel de servicio, esto implica varias transacciones separadas (todas comprometidas, a menos que ocurra una excepción), que gravan más el servidor de la base de datos. No es solo una desventaja de rendimiento, sin embargo. Tener múltiples transacciones para una única solicitud / respuesta también puede conducir a errores sutiles (ya no recuerdo los detalles, solo que tales problemas ocurrieron).

Otra respuesta a esta pregunta se refiere a "la lógica necesaria para identificar el alcance de una base de datos / transacción comercial". Este argumento no tiene sentido para mí, ya que no hay ninguna lógica asociada a la demarcación de transacciones, normalmente. Ni las clases de controlador ni las clases de servicio necesitan "conocer" realmente las transacciones. En la gran mayoría de los casos, en una aplicación web cada operación comercial ocurre dentro de un par de solicitud / respuesta HTTP, siendo el alcance de la transacción todas las operaciones individuales que se ejecutan desde el momento en que se recibe la solicitud hasta que finaliza la respuesta.

De vez en cuando, un servicio comercial o controlador puede necesitar manejar una excepción de una manera particular, entonces probablemente marque la transacción actual para reversión solamente. En Java EE (JTA), esto se hace llamando a UserTransaction#setRollbackOnly() . El objeto UserTransaction se puede inyectar en un campo @Resource u obtenerse programáticamente de algún ThreadLocal . En Spring, la anotación @Transactional permite especificar la reversión para ciertos tipos de excepciones, o el código puede obtener un TransactionStatus subprocesos y llamar a setRollbackOnly() .

Entonces, en mi opinión y experiencia, hacer que el controlador sea transaccional es el mejor enfoque.


TLDR : esto se debe a que solo la capa de servicio en la aplicación tiene la lógica necesaria para identificar el alcance de una transacción de base de datos / negocio. El controlador y la capa de persistencia, por diseño, no pueden / no deben saber el alcance de una transacción.

El controlador se puede hacer @Transactional , pero de hecho es una recomendación común hacer que la capa de servicio sea transaccional (la capa de persistencia tampoco debe ser transaccional).

La razón de esto no es la viabilidad técnica, sino la separación de las preocupaciones. La responsabilidad del controlador es obtener las solicitudes de parámetros, y luego llamar a uno o más métodos de servicio y combinar los resultados en una respuesta que luego se envía de vuelta al cliente.

Entonces, el controlador tiene una función de coordinador de la ejecución de la solicitud y un transformador de los datos de dominio a un formato que el cliente puede consumir, como los DTO.

La lógica comercial reside en la capa de servicio, y la capa de persistencia solo recupera / almacena datos de la base de datos.

El alcance de una transacción de base de datos es realmente un concepto de negocio tanto como un concepto técnico: en una transferencia de cuenta, una cuenta solo se puede cargar si se acredita la otra, etc., así que solo la capa de servicio que contiene la lógica comercial puede conocer realmente alcance de una transacción de transferencia de cuenta bancaria.

La capa de persistencia no puede saber en qué transacción se encuentra, tome por ejemplo un método customerDao.saveAddress . ¿Debería ejecutarse en su propia transacción por separado siempre? no hay forma de saberlo, depende de la lógica comercial que lo llame. A veces debería ejecutarse en una transacción separada, a veces solo guardar sus datos si el saveCustomer también funcionó, etc.

Lo mismo se aplica al controlador: ¿deberían saveCustomer y saveErrorMessages en la misma transacción? Es posible que desee guardar el cliente y, si eso falla, intente guardar algunos mensajes de error y devolver un mensaje de error adecuado al cliente, en lugar de deshacer todo, incluidos los mensajes de error que desea guardar en la base de datos.

En los controladores no transaccionales, los métodos que regresan de la capa de servicio devuelven entidades separadas porque la sesión está cerrada. Esto es normal, la solución es usar OpenSessionInView o realizar consultas que OpenSessionInView los resultados que el controlador sabe que necesita.

Habiendo dicho eso, no es un crimen hacer que los controladores sean transaccionales, simplemente no es la práctica más utilizada.