2011-10-25 24 views
6

Me está pasando algo extraño y no puedo entender por qué. La mejor manera de describir esto es proporcionar un simple ejemplo:La excepción se propaga después de que ya está atrapado

@Service 
@Transactional 
public class Foo{ 
    public ModelAndView delete(@ModelAttribute("abc") Long id) { 
     ModelAndView mav = new ModelAndView(); 
     try { 
      getDaoService().delete(id); //Calls Bar.delete() 
     } catch (final Exception e) { 
      // Add a custom error message to the mav for the user to see 
      mav.getModelMap().addAttribute(blah, blah); 
     } 
     return mav; 
    } 
} 

@Service 
@Transactional 
public class Bar { 
    public void delete(final E entity) throws HibernateException { 
     if (null != entity) { 
      try { 
       sessionFactory.getCurrentSession().delete(entity); 
      } finally { 
       sessionFactory.getCurrentSession().flush(); 
      } 
     } 
    } 
} 

En este caso particular, estoy tratando de eliminar un objeto que tiene una violación de restricción (ORA-02292). Espero que la eliminación falle debido a esto. Cuando la eliminación falla, deseo mostrarle al usuario un mensaje personalizado apropiado.

En lugar de ser capaz de mostrar al usuario un mensaje personalizado, la llamada falla y muestra el siguiente en la pantalla:

org.springframework.transaction.UnexpectedRollbackException: Transacción deshace porque se ha marcado como rollback-only

Cuando uso un depurador, puedo ver que el error está apropiadamente atrapado y que el objeto ModelAndView tiene el mensaje personalizado dentro de él. Por lo tanto, no tengo idea de por qué todavía se lanza una excepción después de haber sido atrapada y tratada. ¿Alguien tiene una idea de por qué está sucediendo esto?

Respuesta

5

En la anotación @Transactional, puede indicar si desea deshacer la transacción debido a una excepción determinada utilizando el atributo noRollbackForClassName. Puedes hacerlo de manera similar a esto.

@Service 
@Transactional(noRollbackForClassName = "java.lang.Exception") 
public class YourClass { 
    ... 
} 

Sin embargo, cabe destacar que sólo decir noRollbackForClassName = "java.lang.Exception" significaría que no va a deshacer ninguna excepción (o de sus subclases), por lo tanto, no es una buena práctica.

Lo que debe hacer es averiguar qué excepción es realmente lanzada primero (puede ser imprimiendo el e.getClass().getName()), luego configure ese nombre de clase como el valor noRollbackForClassName.

En cuanto a la razón, esto sucede porque si se produce una excepción al intentar eliminar(), la transacción actual se marca automáticamente como retrotraer solamente, y si se intenta comprometer, se lanzará la excepción que ve . La manera de superar esto es declarar explícitamente que esta determinada excepción no debería causar un retroceso.

+0

impresionante, funcionó como un encanto! ¡¡¡Gracias!!! – user973479

1

La excepción se está lanzando en Bar # delete y está atrapada en Foo # delete. Hay una anotación @Transactional en Bar # delete que se cruza antes de capturar la excepción. Esta transacción interna está participando en la transacción externa y, por lo tanto, toda la transacción está marcada para su reversión.

Para evitar esto, puede eliminar la anotación @Transactional para Bar # delete. Este método ya se llama dentro del alcance de la otra transacción.

+0

¡Impresionante, trabajado como un encanto! ¡¡¡Gracias!!! – user973479

4

El problema se debe a que una vez que se lanza una excepción, Spring marca internamente el tx como rollback-only. Esto está completamente separado del manejo de excepciones de Java. Tiene varias opciones:

  • Asegúrese de que su excepción esperada no arroje excepciones que extiendan RuntimeException; El resorte solo retrocede tx cuando es un tipo RuntimeException (see this page, sección 10.5.3). HibernateException extends RuntimeException, por eso es por lo que obtienes el marcador de reversión.
  • Ejecute cada tx en su propia transacción moviendo el método transaccional a su propia clase y anotándolo usando @Transactional(propagation=Propagation.REQUIRES_NEW).Entonces cada llamada se ejecutará en su propio tx y no afectará el tx general.
  • Use el estilo noRollbackForClassName venushka mencionado. Pero use con precaución, por la razón mencionada.
+0

Entonces, ¿qué puedo hacer cuando la excepción amplía RuntimeException? – sebnukem

0

añadir una propiedad "globalRollbackOnParticipationFailure" a la definición de frijol hibernateTransactionManager de la siguiente manera.

<bean id="hibernateTransactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> 
    <property name="sessionFactory" ref="hibernateSessionFactory" /> 
    **<property name="globalRollbackOnParticipationFailure" value="false" />** 
</bean> 
Cuestiones relacionadas