EntityManager.flush() commits the transaction in a Java web service
Asked Answered
G

3

8

EDIT: Thanks everybody for your answers, but the problem was with my data source configuration, which was actually in auto-commit mode. See my answer below for details.

Both the Javadoc of the EntityManager.flush() method and searching for it in Google seem to suggest that the flush method only sends the pending statements to the database and does not commit the transaction. But a simple test web service I created (in Java 7, Oracle 11gR2, JBoss 7.1 and the Web service is packaged as a jar file) seem to indicate otherwise:

This is the table creation script:

CREATE TABLE test(
    id INTEGER NOT NULL,
    name VARCHAR2(20), 
    CONSTRAINT test_pk PRIMARY KEY ("ID")
);
CREATE SEQUENCE test_seq;

This is the corresponding entity:

@Entity @Table(name = "TEST")
public class Test implements Serializable {

    private static final long serialVersionUID = 9192814682033048425L;

    @Id @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TEST_SEQ")
    @SequenceGenerator(name="TEST_SEQ",sequenceName="TEST_SEQ", allocationSize = 1)
    private Integer id;

    @Column(name = "NAME")
    private String name;

    // Getters and setters...
}

And the test web service:

@Stateless @WebService(serviceName = "TestService")
@TransactionManagement(TransactionManagementType.CONTAINER)
public class TestServiceBean implements TestService {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public void createTest(String name) {
        Test test = new Test();
        test.setName(name);
        entityManager.persist(test);
        entityManager.flush();

        throw new RuntimeException();
    }
}

My understanding is that:

  • When the createTest method is called, the application starts a new transaction
  • The persist() method generates an INSERT statement to be sent to the database
  • The flush() method sends the INSERT statement to the database but does not commit the transaction!
  • The RuntimeException causes the transaction to rollback.

But obviously my understanding is wrong: every time I run the web service method I get one new row in the table. Moreover, stepping into this method with a debugger reveals that the row is inserted when the flush() method is called (I can "see" the row from another db session using SQL Developer).

Can someone please explain this behavior?

Gannon answered 18/11, 2012 at 14:27 Comment(0)
G
8

It seems that there is nothing wrong with flush() after all. The problem was that I didn't set up the data source correctly in JBoss. The lesson here is that if you want to use container managed transactions in EBJs then you need to:

  • In JBoss, check the Use JTA? check box in the data source configuration.
  • In Weblogic, check the Supports Global Transactions check box in the Transactions tab of the data source configuration.

Additionally, and in order to clear any confusion, the transaction management in my code is correct. Throwing a RuntimeException does rollback the Exception. Why is that? Well, from the Java EE 6 tutorial we have:

if a system exception is thrown, the container will automatically roll back the transaction.

But what is a system exception? The tutorial does not seem to touch upon the subject any further, so lets search the EJB spec. In page 382 we have:

A system exception is an exception that is a java.rmi.RemoteException (or one of its sub- classes) or a RuntimeException that is not an application exception.

OK, so maybe the RuntimeException is an application exception then? No it is not, because in page 380 we have this:

Application exceptions that are checked exceptions may be defined as such by being listed in the throws clauses of the methods of the bean’s business interface, no-interface view, home interface, component interface, and web service endpoint. An application exception that is an unchecked exception is defined as an application exception by annotating it with the ApplicationException metadata annotation, or denoting it in the deployment descriptor with the application-exception element.

So, because I didn't do any of the things listed above, the exception I throw in my code is indeed a system exception and indeed rolls back the transaction if you have set up your data source to use JTA.

Gannon answered 18/11, 2012 at 17:22 Comment(0)
L
0

To rollback a transaction in an EJB method you should call the setRollbackOnly() method, otherwise exiting from a method even throwing an exception causes the transaction to be commited. For a more detailed explanation you can refer to The Java EE 6 Tutorial.

Liegnitz answered 18/11, 2012 at 15:12 Comment(5)
Not really. If I remove the throw statement, inject an EJBContext to my bean and call context.setRollbackOnly(); I still get the row in the table.Gannon
This sounds really weird. There must be something in your configuration that marks the db as auto-commit or something like that. The flush method synchronize the entities with the database but inside the transaction boundaries, hence inserted rows should be visible inside the transaction and once commited, otherwise they should disappear. Check carefully your configuration (JDBC driver, autocommit property in persistence unit, and so on).Liegnitz
Thanks, you comment lead me to find the actual problem, which I wrote in another answer.Gannon
Then please improve your answer, because it is wrong. The code that I wrote in my question correctly rolls back the transaction (If the setup is correct!) and there is not need to call setRollbackOnly()Gannon
That's ok, it doesn't matter, anyway that's what the Java EE 6 specification states, maybe the JBoss implementation is not perfectly compliant with it.Liegnitz
M
0

Quoting JSR-317 of the JPA specification page 23:

Runtime exceptions thrown by property accessor methods cause the current transaction to be marked for rollback. Exceptions thrown by such methods when used by the persistence runtime to load or store persistent state cause the persistence runtime to mark the current transaction for rollback and to throw a PersistenceException that wraps the application exception.

Thus the RuntimeException you throw should be thrown from the entity bean setter NOT from the EJB.

I am sure you did not get the wrapping PersistenceException - which indicates the mark for roll back - instead you got the RuntimeException which you threw.

Try throwing RollbackException instead of RuntimeException from the EJB!!!

Or throw the RuntimeException from within the entity bean setters as the specification says.

Meatiness answered 19/11, 2012 at 5:48 Comment(1)
I've verified the code written by @Gannon on the Glassfish 3.1.2.2 container, it successfully rollbacks the transaction. The Java EE 6 specification says: If the bean throws an application exception, the rollback is not automatic but can be initiated by a call to setRollbackOnly. Anyway, that's it, you throw a RuntimeException from the EJB and the transaction rollbacks.Liegnitz

© 2022 - 2024 — McMap. All rights reserved.