Versión rápida: Estamos buscando una forma de forzar una reversión de una transacción cuando se producen situaciones específicas durante la ejecución de un método en un bean de respaldo, pero nos gustaría que revertir para que ocurra sin tener que mostrarle al usuario una página de error genérica de 500. En su lugar, nos gustaría que el usuario vea el formulario que acaba de enviar y un FacesMessage que indica cuál fue el problema.Forzar una transacción para deshacer errores de validación en Seam
Versión larga: Tenemos algunos respaldos que usan componentes para realizar algunas operaciones relacionadas en la base de datos (usando JPA/Hibernate). Durante el proceso, puede producirse un error después de que se hayan producido algunas de las operaciones de la base de datos. Esto podría deberse a varias razones diferentes, pero para esta pregunta, supongamos que ha habido un error de validación que se detecta después de que se produjeron algunas grabaciones de DB que no fueron detectables antes de que se produjeran las escrituras. Cuando esto suceda, nos gustaría asegurarnos de que todos los cambios de db hasta este punto se retrotraerán. Seam puede manejar esto porque si arrojas una RuntimeException de la FacesRequest actual, Seam revertirá la transacción actual.
El problema es que al usuario se le muestra una página de error genérica. En nuestro caso, nos gustaría que se muestre al usuario la página en la que se encontraba con un mensaje descriptivo sobre lo que salió mal, y que tenga la oportunidad de corregir la información incorrecta que causó el problema. La solución que hemos llegado con es lanzar una excepción de la componente que descubre el problema de validación con la anotación:
@ApplicationException(rollback = true)
Entonces nuestro bean de respaldo puede detectar esta excepción, asumir el componente que arrojó ha publicado la FacesMessage apropiado, y simplemente devuelve null para llevar al usuario a la página de entrada con el error mostrado. La anotación ApplicationException le dice a Seam que deshaga la transacción y no mostramos al usuario una página de error genérica.
Esto funcionó bien para el primer lugar en el que lo usamos, que pasaba a ser solo insertos. El segundo lugar donde tratamos de usarlo, tenemos que eliminar algo durante el proceso. En este segundo caso, todo funciona si no hay un error de validación. Si se produce un error de validación, se lanza la excepción de retrotracción y la transacción se marca para deshacer. Incluso si han ocurrido ninguna modificación de bases de datos que revertirse, cuando el usuario corrige los malos datos y vuelve a presentar la página, que van a obtener:
java.lang.IllegalArgumentException: Removing a detached instance
La instancia separada se carga con pereza de otro objeto (hay una relación de muchos a uno). Ese objeto principal se carga cuando se crea una instancia del bean de respaldo. Debido a que la transacción se retrotrajo después del error de validación, el objeto ahora está separado.
Nuestro próximo paso fue cambiar esta página del alcance de la conversación al alcance de la página. Cuando hicimos esto, Seam ni siquiera puede renderizar la página después del error de validación porque nuestra página debe presionar DB para representar y la transacción se ha marcado para deshacer.
Así que mi pregunta es: ¿cómo se manejan otras personas que manejan los errores de manera limpia y manejan las transacciones al mismo tiempo? Mejor aún, me encantaría poder utilizar todo lo que tenemos ahora si alguien puede detectar algo que estoy haciendo mal que sería relativamente fácil de solucionar.
He leído el artículo de Seam Framework en Unified error page and exception handling pero esto se dirige más hacia errores más genéricos que su aplicación pueda encontrar.
Actualización: aquí hay un código de psudo y detalles del flujo de la página.
En este caso, supongamos que estamos editando la información de algunos usuarios (en este caso no estamos tratando con un usuario, pero no vamos a publicar el código real).
archivo edit.page.xml de la funcionalidad de edición contiene un patrón re-escritura simple para una URL REST y dos reglas de navegación:
- Si el resultado fue un éxito de edición, redirigir al usuario a la página de vista correspondiente a ver la información actualizada.
- Si el usuario presiona el botón cancelar, redirige al usuario a la página de vista correspondiente.
El edit.xhtml es bastante básico con campos para todas las partes de un usuario que se pueden editar.
El bean de respaldo tiene las siguientes anotaciones:
@Name("editUser")
@Scope(ScopeType.PAGE)
Hay algunos componentes inyectados como el usuario:
@In
@Out(scope = ScopeType.CONVERSATION) // outjected so the view page knows what to display
protected User user;
Tenemos un método de ahorrar en el bean de respaldo que delega el trabajo para el user save:
public String save()
{
try
{
userManager.modifyUser(user, newFName, newLName, newType, newOrgName);
}
catch (GuaranteedRollbackException grbe)
{
log.debug("Got GuaranteedRollbackException while modifying a user.");
return null;
}
return USER_EDITED;
}
Nuestra GuaranteedRollbackException se parece a:
@ApplicationException(rollback = true)
public class GuaranteedRollbackException extends RuntimeException
{
public GuaranteedRollbackException(String message) {
super(message);
}
}
UserManager.modifyUser ve algo como esto:
public void modifyUser(User user, String newFName, String newLName, String type, String newOrgName)
{
// change the user - org relationship
modifyUser.modifyOrg(user, newOrgName);
modifyUser.modifyUser(user, newFName, newLName, type);
}
ModifyUser.modifyOrg hace algo como
public void modifyOrg(User user, String newOrgName)
{
if (!userValidator.validateUserOrg(user, newOrgName))
{
// maybe the org doesn't exist something. we don't care, the validator
// will add the appropriate error message for us
throw new GauaranteedRollbackException("couldn't validate org");
}
// do stuff here to change the user stuff
...
}
ModifyUser.modifyUser es similar a modifyOrg.
Ahora (vas a tener que dar este paso porque no necesariamente suena como un problema con este escenario de usuario pero es por lo que estamos haciendo) asumiendo que cambiar la organización causa la modifyUser no se puede validar, pero es imposible validar este error antes de tiempo. Ya hemos escrito la actualización de la organización en el db en nuestro txn actual, pero como el usuario no puede validar la modificación, GuaranteedRollbackException marcará la transacción que se revertirá. Con esta implementación, no podemos usar el DB en el ámbito actual cuando estamos visualizando la página de edición nuevamente para mostrar el mensaje de error agregado por el validador. Mientras renderizamos, pulsamos el archivo db para que aparezca algo en la página y eso no es posible porque la sesión no es válida:
Causado por org.hibernate.LazyInitializationException con el mensaje: "no se pudo inicializar el servidor proxy - no Session"
Te puedo ayudar pero ** puedes mostrarlo en código simple ** El primer escenario y El segundo: Componentes, tu página, reglas de navegación y el controlador de excepciones –
@Arthur Ronald FD Garcia, agregué más información junto con algunos fragmentos de código. Esto golpea demasiado código para publicar todo, pero espero que esto te brinde lo que necesitas saber. –