EJB3 Propagación de Transacciones


Tengo un frijol sin estado algo así como:

@Stateless
public class MyStatelessBean implements MyStatelessLocal, MyStatelessRemote {
    @PersistenceContext(unitName="myPC")
    private EntityManager mgr;

    @TransationAttribute(TransactionAttributeType.SUPPORTED)
    public void processObjects(List<Object> objs) {
        // this method just processes the data; no need for a transaction
        for(Object obj : objs) {
            this.process(obj);
        }
    }

    @TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void process(Object obj) {
        // do some work with obj that must be in the scope of a transaction

        this.mgr.merge(obj);
        // ...
        this.mgr.merge(obj);
        // ...
        this.mgr.flush();
    }
}

El uso típico entonces es que el cliente llamaría a processObjects(...), que en realidad no interactúa con el entity manager. Hace lo que necesita hacer y llama a process(...) individualmente para cada objeto a procesar. La duración del proceso(...) es relativamente corto, pero processObjects(...) podría tomar mucho tiempo para correr a través de todo. Por lo tanto, no quiero que mantenga una transacción abierta. I do need the proceso individual(...) operaciones para operar dentro de su propia transacción. Esta debe ser una nueva transacción para cada llamada. Por último, me gustaría mantener la opción abierta para que el cliente llame al proceso (...) directamente.

He probado varios tipos de transacciones diferentes: never, not supported, supported (on processObjects) y required, requires new (on process) pero obtengo TransactionRequiredException cada vez que se llama a merge ().

He sido capaz de hacer que funcione mediante la división de la métodos en dos frijoles diferentes:

@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
    @EJB
    private MyStatelessBean2 myBean2;

    public void processObjects(List<Object> objs) {
        // this method just processes the data; no need for a transaction
        for(Object obj : objs) {
            this.myBean2.process(obj);
        }
    }
}

@Stateless
public class MyStatelessBean2 implements MyStatelessLocal2, MyStatelessRemote2 {
    @PersistenceContext(unitName="myPC")
    private EntityManager mgr;

    @TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void process(Object obj) {
        // do some work with obj that must be in the scope of a transaction

        this.mgr.merge(obj);
        // ...
        this.mgr.merge(obj);
        // ...
        this.mgr.flush();
    }
}

Pero todavía tengo curiosidad si es posible lograr esto en una clase. Me parece que el administrador de transacciones solo opera a nivel de bean, incluso cuando los métodos individuales reciben anotaciones más específicas. Así que si marca un método de una manera para evitar que la transacción comience a llamar a otros métodos dentro de esa misma instancia tampoco creará una transacción, no importa cómo están marcados?

Estoy usando la aplicación JBoss Servidor 4.2.1.GA, pero las respuestas no específicas son bienvenidas / preferidas.

Author: Tomasz Nurkiewicz, 2008-09-20

8 answers

Otra forma de hacerlo es en realidad tener ambos métodos en el mismo frijol-y tener una @EJB referencia a sí mismo! Algo así:

// supposing processObjects defined on MyStatelessRemote1 and process defined on MyStatelessLocal1
@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {
    @EJB
    private MyStatelessLocal1 myBean2;

    public void processObjects(List<Object> objs) {
        // this method just processes the data; no need for a transaction
        for(Object obj : objs) {
            this.myBean2.process(obj);
        }
    }


    @TransationAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void process(Object obj) {
        // do some work with obj that must be in the scope of a transaction

        this.mgr.merge(obj);
        // ...
        this.mgr.merge(obj);
        // ...
        this.mgr.flush();
    }
}

De esta manera realmente 'fuerza' el método process() para ser accedido a través de la pila ejb de proxies, por lo tanto tomando el @TransactionAttribute en efecto - y manteniendo solo una clase. Uf!

 24
Author: Magnilex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-31 11:45:58

Matt, la pregunta que haces es bastante clásica, creo que la solución de autorreferencia de Herval/Pascal es genial. Hay una solución más general que no se menciona aquí.

Este es un caso para transacciones de "usuario" EJB. Dado que se encuentra en un bean de sesión, puede obtener la transacción de usuario desde el contexto de la sesión. Así es como se verá su código con las transacciones de usuario:

// supposing processObjects defined on MyStatelessRemote1 and process defined on MyStatelessLocal1
@Stateless
@TransationAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class MyStatelessBean1 implements MyStatelessLocal1, MyStatelessRemote1 {

    @Resource
    private SessionContext ctx;

    @EJB
    private MyStatelessLocal1 myBean2;

    public void processObjects(List<Object> objs) {
        // this method just processes the data; no need for a transaction
        for(Object obj : objs) {
            this.myBean2.process(obj);
        }
    }


    public void process(Object obj) {

        UserTransaction tx = ctx.getUserTransaction();

        tx.begin();

        // do some work with obj that must be in the scope of a transaction

        this.mgr.merge(obj);
        // ...
        this.mgr.merge(obj);
        // ...
        this.mgr.flush();

        tx.commit();
    }
}
 4
Author: bluecarbon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-26 14:32:08

Creo que la cosa es que cada frijol está envuelto en un proxy que controla el comportamiento transaccional. Cuando llamas de un bean a otro, vas a través del proxy de ese bean y el comportamiento de la transacción puede ser cambiado por el proxy.

Pero cuando un bean llama a un método en sí mismo con un atributo de transacción diferente, la llamada no va a través del proxy, por lo que el comportamiento no cambia.

 2
Author: fiddlesticks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-04 16:32:11

Matt, por si sirve de algo he llegado exactamente a la misma conclusión que tú.

Los tipos TransactionAttributeTypes solo se tienen en cuenta al cruzar los límites de Bean. Al llamar a los métodos dentro del mismo bean TransactionAttributeTypes no tienen ningún efecto, no importa qué Tipos se pongan en los métodos.

Por lo que puedo ver, no hay nada en la especificación de Persistencia EJB que especifique cuál debe ser el comportamiento bajo estas circunstancias.

También he experimentado esto en Jboss. También le daré una oportunidad en Glassfish y le haré saber los resultados.

 1
Author: Brett Hannah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-12-04 16:15:50

En caso de que alguien se encuentre con esto un día:

Para evitar dependencias circulares (permitiendo auto referencia por ejemplo) en JBoss use la anotación 'IgnoreDependency' por ejemplo:

@IgnoreDependency @EJB Yo mismo myselfRef;

 1
Author: Kevin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-05-07 19:09:07

Todavía no lo he probado (estoy a punto de hacerlo), pero una alternativa a inyectar una auto-referencia a través de la anotación @EJB es el método SessionContext.getBusinessObject(). Esta sería otra manera de evitar la posibilidad de que una referencia circular explote las cosas sobre usted, aunque al menos para la inyección de frijoles sin estado parece funcionar.

Estoy trabajando en un sistema grande en el que se emplean ambas técnicas (presumiblemente por diferentes desarrolladores), pero no estoy seguro de cuál es la forma "correcta" de hacerlo.

 1
Author: tizzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-26 07:37:26

Creo que tiene que ver con el @TransationAttribute(TransactionAttributeType.Never) on method processObjects.

TransactionAttributeType.Nunca

Http://docs.sun.com/app/docs/doc/819-3669/6n5sg7cm3?a=view

Si el cliente se está ejecutando dentro de un transacción e invoca a la empresa método de bean, el contenedor lanza un Excepción remota. Si el cliente no está asociado con una transacción, el contenedor no iniciar un nuevo transacción antes de ejecutar el método.

Asumo que eres cliente el método processObjects del código del cliente. Debido a que probablemente su cliente no está asociado con una transacción, el método llama con TransactionAttributeType.Nunca es feliz en primer lugar. Luego llama al método process desde processObjects que aunque tenga el TransactionAttributeType.La anotación requerida no fue una llamada al método bean y la política de transacciones no se aplica. Cuando llamas a merge obtienes la excepción porque aún no estás asociado con una transacción.

Intente usar TransactionAttributeType.Requerido para ambos métodos bean para ver si funciona.

 0
Author: Jorge Ferreira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-09-20 00:07:45

Tuve estos problemas circulares de dependencia que Kevin mencionó. Sin embargo, la anotación propuesta @IgnoreDependency es una anotación específica de jboss y no hay contraparte en, por ejemplo, Glassfish.

Dado que no funciona con la referencia EJB predeterminada, me sentí un poco incómodo con esta solución.

Por lo tanto, le di una oportunidad a la solución de bluecarbon, iniciando así la transacción interna "a mano".

Además de esto, no veo otra solución que implementar el proceso interno() en otro frijol que también es feo porque simplemente queremos perturbar nuestro modelo de clase para tales detalles técnicos.

 0
Author: rainer198,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-09-26 14:31:46