2012-01-04 9 views
7

Tenemos una aplicación Java ejecutándose en JBoss 5.1 y en algunos casos necesitamos evitar que una transacción se cierre en caso de que un método subyacente arroje un JDBCException.Evitar la reversión de transacción en JBoss + Hibernate

Tenemos un método EJB que se parece a la siguiente

@PersistenceContext(unitName = "bar") 
public EntityManager em; 

public Object foo() { 
    try { 
    insert(stuff); 
    return stuff; 
    } (catch PersistenceException p) { 
    Object t = load(id); 
    if (t != null) { 
     find(t); 
     return t; 
    } 
    } 
} 

Si insert falla debido a un PersistenceException (que se ajusta un JDBCException causado por una violación de restricción), queremos continuar la ejecución con load dentro del misma transacción.

No podemos hacerlo ahora porque el contenedor cierra la transacción. Esto es lo que vemos en los registros:

clase
org.hibernate.exception.GenericJDBCException: Cannot open connection 
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: Cannot open connection 
    at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:614) 

    ... 

Caused by: javax.resource.ResourceException: Transaction is not active: tx=TransactionImple < ac, BasicAction: 7f000101:85fe:4f04679d:182 status: ActionStatus.ABORT_ONLY > 

El EJB está marcado con las siguientes anotaciones

@Stateless 
@TransactionManagement(TransactionManagementType.CONTAINER) 
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 

¿Hay alguna forma adecuada para prevenir la transacción ruede hacia atrás en tan sólo este caso concreto?

Respuesta

5

Realmente no deberías tratar de hacer eso. Como se menciona en otra respuesta, y citando Hibernate Docs, ninguna excepción lanzada por Hibernate debe tratarse como recuperable. Esto podría llevarle a algunos problemas difíciles de encontrar/depurar, especialmente con la verificación sucia automática de hibernación.

Una manera limpia de resolver este problema es verificar esas restricciones antes de insertar el objeto. Use una consulta para verificar si se está violando una restricción de la base de datos.

public Object foo() { 
    if (!objectExists()) { 
     insertStuff(); 
     return stuff(); 
    } 
    // Code for loading object... 
} 

Sé que esto parece un poco doloroso, pero esa es la única manera usted sabrá a ciencia cierta lo que se violó la restricción (no se puede obtener esa información de excepciones Hibernate). Creo que esta es la solución más limpia (más segura, al menos).


Si aún desea recuperarse de la excepción, tendría que hacer algunas modificaciones a su código.

Como se mencionó, puede administrar las transacciones manualmente, pero no lo recomiendo. La API de JTA es realmente engorrosa. Además, si usa Bean Managed Transaction (BMT), tendría que crear manualmente las transacciones para cada método en su EJB, es todo o nada.

Por otro lado, podría refactorizar sus métodos para que el contenedor use una transacción diferente para su consulta.Algo como esto:

@Stateless 
public class Foo { 
    ... 
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) 
    public Object foo() { 
     try { 
      entityManager.insert(stuff); 
      return stuff; 
     } catch (PersistenceException e) { 
      if (e.getCause() instanceof ConstraintViolationException) { 
       // At this point the transaction has been rolled-backed. 
       // Return null or use some other way to indicate a constrain 
       // violation 
       return null; 
      } 
      throw e; 
     } 
    } 

    // Method extracted from foo() for loading the object. 
    public Object load() { 
     ... 
    } 
} 

// On another EJB 
@EJB 
private Foo fooBean; 

public Object doSomething() { 
    Object foo = fooBean.insert(); 
    if (foo == null) { 
     return fooBean.load(); 
    } 

    return foo; 
} 

Cuando se llama foo(), se suspenderá la transacción actual (T1) y el contenedor va a crear una nueva (T2). Cuando se produce el error, T2 se respaldará y T1 se restaurará. Cuando se invoca load(), usará T1 (que aún está activo).

Espero que esto ayude!

2

No creo que sea posible.

Puede depender de su proveedor de JPA, pero, por ejemplo, Hibernate establece explícitamente que cualquier excepción deja la sesión en un estado incoherente y, por lo tanto, no debe tratarse como recuperable (13.2.3. Exception handling).

Supongo que lo mejor que puede hacer es deshabilitar la gestión automática de transacciones para este método y crear una nueva transacción después de la excepción manualmente (usando UserTransaction, por lo que recuerdo).

Cuestiones relacionadas