Commit transaction only if all EJB’s are successful

Asked

Viewed 1,078 times

-1

I have a complex problem, but it can be exemplified by the following metaphor::

@Stateless
public interface ChildEJB01 {
    salvarGato(Gato g);
}

@Stateless
public interface ChildEJB01 {
    salvarCachorro(Cachorro c);
}

@Stateless
public interface ChildEJB01 {
    salvarPeriquito(Periquito p);
}

These three Ejbs are used in isolation at various points of my application and work well in isolation. But there’s a certain moment I need to use them together, see:

@Statefull
public class MasterEJB {

    @EJB
    private ChildEJB01 a;

    @EJB
    private ChildEJB02 b;

    @EJB
    private ChildEJB01 c;

    public void salvarTudo(){
        a.salvarGato();
        b.salvarCachorro();
        c.salvarPeriquito();
    }

}

Now I need to save all at once and I need everything to be saved in the database only if all are saved correctly [transaction].

Only that I have realized that even if one of them gives error what was done previously is saved anyway... How do I create a transaction for this method? (I’m using JTA (managed by Containner)).

My setup: Jboss AS7


editing for a new example using Ricardo’s tips

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class ChildEJB01 implements C01 {

@PersistenceContext
private EntityManager manager;

@WebMethod
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void persist(String a){
    Model m = new Model();
    m.setDesc(getClass().getCanonicalName());

    manager.persist(m);
}

}


@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER) 
public class ChildEJB02 implements C02 {

@PersistenceContext
private EntityManager manager;

@WebMethod
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void persist(String a){
    Model m = new Model();
    m.setDesc(getClass().getCanonicalName());

    manager.persist(m);
}

}


@WebService
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class MasterEJB {

@EJB
private C01 c01;

@EJB
private C02 c02;

@Resource
private SessionContext contexto;

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void persist() {
    for (int i = 0; i < 3; i++) {
        c01.persist("");
        c02.persist("");

        if (i == 2) // forcando uma exceção apos 4 inserts
            throw new RuntimeException();
    }
}

}

SELECT IN THE BANK BEFORE RUNNING THE CODE:

mysql> SELECT * FROM test; Empty set (0.00 sec) mysql>

CONSOLE OUTPUT DURING CODE EXECUTION:

10:26:00,905 INFO [stdout] (http-0.0.0.0-8080-1) Hibernate: Insert into test (description) values (?)

10:26:01,006 INFO [stdout] (http-0.0.0.0-8080-1) Hibernate: Insert into test (description) values (?)

10:26:01,057 INFO [stdout] (http-0.0.0.0-8080-1) Hibernate: Insert into test (description) values (?)

10:26:01,101 INFO [stdout] (http-0.0.0-8080-1) Hibernate: Insert into test (description) values (?)

10:26:01,135 INFO [stdout] (http-0.0.0-8080-1) Hibernate: Insert into test (description) values (?)

10:26:01,168 INFO [stdout] (http-0.0.0.0-8080-1) Hibernate: Insert into test (description) values (?)

10:26:01,209 ERROR [org.jboss.ejb3.Invocation] (http-0.0.0.0-8080-1) JBAS014134: EJB Invocation failed on Component Masterejb for method public void implementations.MasterEJB.persist(): javax.ejb.Ejbexception: java.lang.Runtimeexception

SELECT AT THE BANK:

mysql> SELECT * FROM test;

+----+---------------------------+

| id | Description |

+----+---------------------------+

| 1 | implementations.Childejb01 |

| 2 | implementations.Childejb02 |

| 3 | implementations.Childejb01 |

| 4 | implementations.Childejb02 |

| 5 | implementations.Childejb01 |

| 6 | implementations.Childejb02 |

+----+---------------------------+

6 Rows in set (0.00 sec)

===

In short.. the rollback still does not work :(

== +1 Edit:

now using a UNICO EJB (without using other injected EJB’s);

@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void persist() {

    for (int i = 0; i < 5; i++) {
        Model m = new Model();
        m.setDesc("KEKEKEKEKKE: " + i);
        manager.persist(m);
    }

    contexto.setRollbackOnly();
}

rollback was not done. ta VERY STRANGE :o


RESOLVED:

I had to activate the transactions for my DATASOURCE from JBOSS.. it was just mark the box: [x] USE JTA

  • The methods salvarGato(), salvarCahorro() and salvarPeriquito(), are already in transactional context? If so, what is the context of each one (REQUIRED, REQUIRES_NEW...)?

  • what would be in transactional context? the methods are all marked with REQUIRED

  • Transactional context (or transaction propagation) is the way the container should handle transactions. For example, if you mark a method as transactional and specify that the spread of that transaction is REQUIRED for example, indicates that if this method is called another method without being transactional it will create a new transaction. If the method you call is already in transactional context, then the method called will use the transaction opened in the previous method.

  • Yes yes.. was what I expected from this code.. But when the exception is released.. no rollback :( | look at this test code: http://pastebin.com/ySMMSUwV

  • 1

    See my answer and see if it matches your code. If hit and there is still the exception and there is no rollback, have to edit your question and post the exception released?

  • ready.. edited question with a new example.. but with the same error unfortunately.

  • It is not the best solution, but try to do this: place the following call before launching Runtimeexception(): sessionContext.setRollbackOnly(); And take the test. I’m researching what might be.

  • One more tip: write down the classes of your EJB’s with: @ApplicationException(rollback=true) and try again.

  • Another thing I’m using my persistence unit like this: http://pastebin.com/hwn0vNrg | I’m doing the tests you asked for

  • I added another Edit.. and this is even stranger.. at a glance.. It’s as if the transactions didn’t work..

  • Some things to check: it looks like Dialect is Innodb, but confirm that the engine is really Innodb. If it is Myisam, the transactions will not really work. Second, try doing it with a Servlet, not a Webservice, to reduce the number of variables. If nothing goes right, try injecting a @Usertransaction and manually demarcate the transaction. If it doesn’t work that way, then I would recommend taking a look at the quickstarts available in jboss.org/jdf. Maybe one or the other Quickstart will give you some light on what is happening (or rather not happening).

  • answers found the/ | put in the main post the resolution. :)

  • Nice that you managed to solve your problem! Congratulations! I edited my answer to encompass your solution as well. Abs.

Show 8 more comments

1 answer

2


Here we go! This will require a little knowledge on transactions and their propagation. In addition to enabling the use of JTA in persistence.xml.

Changing the persistence.xml

To enable transactions via JTA, should be specified in which datasource the transaction will occur as follows:

<jta-data-source>java:/meuDatasource</jta-data-source>

Writing down the EJB’s

Now, we need to note down the methods of the EJB’s children to support the spread TransactionAttributeType.REQUIRED. Does that mean that:

Transactionattributetype.REQUIRED: If there is already a transaction in progress, then that same transaction will be used. If there is no transaction in progress, then a new one will be created.

Let’s go to the codes:

Childejb1

@Stateless
public class ChildEJB1 {

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void salvarPiriquito() {
        //codigo
    }
}

Childejb2

@Stateless
public class ChildEJB2 {

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void salvarGato() {
        // codigo
    }
}

Childejb3:

@Stateless
public class ChildEJB3 {

    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void salvarCachorro() {
        //codigo
    }
}

And finally, the Masterejb:

@Stateful
public class MasterEJB {

    private ChildEJB1 ejb1;
    private ChildEJB2 ejb2;
    private ChildEJB3 ejb3;

    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void salvarTudo() {
        this.ejb1.salvarPiriquito();
        this.ejb2.salvarGato();
        this.ejb3.salvarCachorro();
    }
}

See that in Masterejb I used another type of transaction propagation (TransactionAttributeType.REQUIRES_NEW). What this kind of propagation tells us:

TransactionAttributeType.REQUIRES_NEW: This propagation must create a new transaction from a context without any transaction. If there is already a transaction in progress, then an error will be generated.

And what all this code posted means?

It means that when you call the EJB’s "children" methods in isolation, a new transaction will be created and the objects will be saved. I’m assuming that when these methods are called "alone", has no previously opened transaction.

When the methods of the EJB’s children are called by the method salvarTudo(), see that in the method itself salvarTudo() the transaction is already opened and that transaction propagates to the methods of the EJB’s son. If the transaction fails in any method of the EJB’s children, then the rollback will be done and no object will remain persisted from any EJB son.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.