8

I am call two methods, the first one update a table and the next one insert a record in another table. When the second transaction fails the EJB is not doing the rollback of the first transaction.

This is my backing bean:

@ManagedBean
@ViewScoped
public class TransactionTestBean implements Serializable {

    @EJB
    private TransactionTestService service;

    public String loadView() {
        return "/test/transactionTest";
    }

    public void test() {
        try {
            service.updateTest();
        } catch (Exception e) {
        }
    }
}

The EJB interface:

@Local
public interface TransactionTestService {

    void updateTest() throws CustomException;
}

The EJB class:

@Stateless
@TransactionManagement
public class TransactionTestServiceImpl implements TransactionTestService {

    @Resource(mappedName = "java:jboss/datasources/xxxxxDS", shareable = true)
    public DataSource dataSource;

    private TransactionTestDAO dao;

    @PostConstruct
    public void init() {
        dao = new TransactionTestDAOImpl();
    }

    @PreDestroy
    public void destroy() {
        dao = null;
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void updateTest() throws CustomException {

        try (Connection connection = dataSource.getConnection()) {
            dao.updateRecord(connection);
            // dao.saveRecord(connection);
        } catch (SQLException exception) {
            throw new CustomException(exception, exception.getMessage());
        }
    }
}

And my custom exception:

@ApplicationException(rollback = true)
public class CustomException extends Exception {

    public CustomException(Throwable cause, String message) {
        super(message, cause);
    }
}

EDITED:

Added the DAO Class:

public class TransactionTestDAOImpl implements TransactionTestDAO {

    @Override
    public void updateRecord(Connection connection) throws CustomException {

        PreparedStatement preparedStatement = null;

        try {
            preparedStatement = connection.prepareStatement("UPDATE table_x SET field_x = ? WHERE field_y = 1");
            preparedStatement.setInt(1, 1);
            preparedStatement.executeUpdate();
        } catch (Exception exception) {
            throw new CustomException(exception, exception.getMessage());
        } finally {
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException sqlException) {
                }
            }
        }
    }
}

And the DAO Interface:

public interface TransactionTestDAO {

    void updateRecord(Connection connection) throws CustomException;
}
Gas
  • 17,601
  • 4
  • 46
  • 93
John Alexander Betts
  • 4,718
  • 8
  • 47
  • 72
  • 1
    please provide the jboss configuration for your datasource – Camilo Oct 02 '14 at 01:03
  • What is your DAO implementation? – Gas Oct 02 '14 at 09:19
  • If you just use plain JDBC in your dao, then there is no second transaction. Both methods `updateRecord()` and `saveRecord()` are called in the `single` transaction and that transaction should be rolledback on your exception. – Gas Oct 02 '14 at 09:28
  • I have added the DAO – John Alexander Betts Oct 02 '14 at 14:25
  • John, can you provide stack trace, and how you know that there 2 transactions? As I wrote - you should only have 1 transaction. Both methods are invoked in single transaction. – Gas Oct 06 '14 at 20:36
  • Exactly, you are right, there are two methods invoked in a single transaction but when the second one fails the rollback of the first method doesn't work – John Alexander Betts Oct 06 '14 at 20:41
  • @Gas I have no exceptions thrown I am forcing one to test if the first method do its rollback – John Alexander Betts Oct 06 '14 at 21:21
  • What do you mean - 'the rollback doesn't work'? Changes done by first method should not be committed to the database. Your table_x should not be changed. How are you generating failed transaction? Add proper e.printStackTrace() to your `public void test()` method in `TransactionTestBean`. – Gas Oct 06 '14 at 21:49
  • the first method change the data in my table and it should'nt because in the second method I am sending an integer parameter where it must be a string parameter. The second method fails but the first method save the data successfully (It doesn't make the rollback) – John Alexander Betts Oct 06 '14 at 21:55
  • Are you sure that the second method fails? Maybe it all works, thats why there is no rollback (add that printStack so you know that there was exception, as currently you just dont). Passing integer as string will work, it wouldn't the other way. So based on what you are saying, I'd say that there is no rollback as your transaction successfully commits. – Gas Oct 06 '14 at 22:11
  • Yes, I'm sure. I am forcing an exception in the second method – John Alexander Betts Oct 06 '14 at 22:33

4 Answers4

8

The key issue in this case was bad default in datasources in some JBoss versions. Original code was fine and was working correctly in other application servers (WebSphere App Server and lightweight WebSphere Liberty).

Datasources created in JBoss are not JTA - in admin console the Use JTA setting is unchecked and in xml related setting is <datasource jta="false" .... Changing this setting to true fixed the problem. (JohnB, you wrote that defining xa-datasource fixed that, but since I didn't see your original xml with datasource definition, I believe that during changing datasource you've also change this flawed jta="false" setting). It will work for non xa-datasources also, as Grzesiek tested.

This is a very bad default, since it causes transactions not to be managed by container and causes flawed transaction behavior in connections got in EJB components.

Big thanks to Grzesiek D. who helped me in diagnosing this issue.

Community
  • 1
  • 1
Gas
  • 17,601
  • 4
  • 46
  • 93
5

Please try this

@Override
public void updateTest() throws CustomException {

    Connection connection = dataSource.getConnection();
    try {
        connection.setAutoCommit(false);  // this should be the key

        dao.updateRecord(connection);
        dao.saveRecord(connection);

        connection.commit();

    } catch(Exception ex) {
        connection.rollback();
        throw new CustomException(ex, ex.getMessage());

    } finally {
        if(connection != null) {
            connection.close();
        }
    }
}

Update

My answer above has a mistake, because above code assumes that BMT (Bean-Managed Transactions) is used. But as we can see, you are using CMT (Container-Managed Transactions). Because @TransactionManagement is equivalent of @TransactionManagement(TransactionManagementType.CONTAINER)).

Above code snippet will only work with BMT. With CMT you should get error like below:

Caused by: java.sql.SQLException: You cannot set autocommit during a managed transaction!

But, my mistake turned to someething good in the end, because when you wrote

This works very well (...)

then we found an answer: you think that your EJB bean use CMT with JTA, but due to some error, it does not.


In comments below, I've also advised you to use JPA, but in this simple case JDBC is good enough. CMT transactions can be freely used also with JDBC.

Also type of the data source doesn't matter here. CMT can be freely used with a non-XA datasource (also called local datasource) and XA datasource as well.

Update 2

User @Gas solved the problem in the following comment. Kudos for him.

Basically: there were nothing wrong with the original code. Problem lies in the configuration of the datasource (has to be JTA enabled). So edit Datasource configuration via JBoss Administration console and set a checkbox "Use JTA".

halfer
  • 19,824
  • 17
  • 99
  • 186
G. Demecki
  • 10,145
  • 3
  • 58
  • 58
  • 1
    This works very well but I need to manage this in the standalone.xml not in java – John Alexander Betts Oct 07 '14 at 14:25
  • You mean "manage `autocommit`"? If so, then try to set it to `false` in your `standalone.xml` file, ok? – G. Demecki Oct 07 '14 at 14:32
  • Yes, I dit it in the standalone.xml but don'i like to manage the commit and rollback by code I would like EJB do that automatically – John Alexander Betts Oct 07 '14 at 14:35
  • If so, then **remove most of your code** to the trash and just use `EntityManager` instead. Remove your daos with `PreparedStatement`, and use entity manager in its place. Also discard opening manually connection to the `DataSource`. And let your EJB container manage all your transactions. – G. Demecki Oct 07 '14 at 14:50
  • Could you give me some link where I can see how to do it? – John Alexander Betts Oct 07 '14 at 14:55
  • Yes of course, here you have [Sample Petstore with Java EE 6](https://github.com/agoncal/agoncal-application-petstore-ee6). Long in short: use `EntityManager` instead of your DAO class, and inject it in your `@Stateless` bean and you are home. All transactions will be mangaged by the EJB container, you don't have to commit/ rollback anything. – G. Demecki Oct 08 '14 at 08:11
  • 1
    @JohnB Why you are playing with autocommit? You shouldn't need to set any transaction logic like this `connection.setAutoCommit()` or `connection.commit()` in CMT EJB. You probably defined your datasource in JBoss incorrectly, please add relevant part to your question. You really dont need JPA for such simple cases like this. I've run your original example on WebSphere Liberty and it works correctly as you expected (without changing autocommit, manual transaction handling and JPA). – Gas Oct 09 '14 at 10:19
  • Grzesiek this is POJO development what you are presenting here, not EJB. In EJB you should **never** handle transaction via connection object. In CMT bean it is handled by container, in BMT it is handled via UserTransaction interface. Container that follows the spec will throw exception: `Caused by: java.sql.SQLException: DSRA9350E: Operation Connection.commit is not allowed during a global transaction` when you do that. – Gas Oct 09 '14 at 10:54
  • Indeed, agree with you. – G. Demecki Oct 09 '14 at 11:02
  • @Gas no, type of datasource doesn't matter (I've checked a sec ago to be sure). To be honest... your amount of comments is annoying. Why do not you write a normal answer? If it would be correct, I would vote for it, really. – G. Demecki Oct 10 '14 at 06:26
  • GrzesiekD you are correct, my comment about XA datasource was incorrect, I removed it. I cant give an answer, since I don't have JBoss env installed and cant test it. I've tested John's code on 2 servers - WebSphere App Server and Liberty Profile and it works fine, transaction is correctly rolled back. So it is nothing wrong with original code. Something makes JBoss incorrectly think that this is not managed transaction. I don't know what, and currently dont have time to setup JBoss env just to test it. Trying to guide you to find correct solution using comments. Sorry, if you find it annoying – Gas Oct 10 '14 at 08:36
  • 3
    Grzesiek My probably last comment, wont bother you any more. Since looks like you have env ready, could you please test this http://stackoverflow.com/a/16910818/3701228 - edit Datasource configuration via JBoss Administration console and set a checkbox "Use JTA" - in xml it should be ` – Gas Oct 10 '14 at 09:00
  • @Gas, your link explains everything. Unchecked **Use JTA** is causing this problem. Bravo. +1 for you. Post it as an answer, so OP can accept it. – G. Demecki Oct 10 '14 at 09:44
  • Lets clean this a bit.:) Please, update your answer, so it will be correct, as you did most of the work here. You deserve it. I'll upvote. Your attached exception gave me some hints, and you tested it so its more like a team work here. Dzięki za pomoc, pozdrawiam. – Gas Oct 10 '14 at 10:10
  • @Gas I change my datasource to xa-datasource then I delete the autocommit and rollback sentences and all works right. Could you please answer my question to give you your well won 100 points + the correct answer points – John Alexander Betts Oct 10 '14 at 16:07
4

I'm almost sure that your problem is caused because you are creating your DAO by hand with new keyword:

@PostConstruct
public void init() {
    dao = new XxxxDAOImpl();
}

When you are doing such things, your ejb container cannot manage that object lifecycle and transaction boundaries. You should let the container to create and manage the dao (and inject it for you). In that way, you will gain a proper transaction propagation across all your EJB methods - and thus your problem will be solved.

To achieve that, you can simply annotate your DAO class with @Stateless and inject it in your TransactionTestServiceImpl class with:

@EJB
private XxxxDAO dao;

And then, of course, remove init and destroy methods.

Personal advice

Why to use a separate, additional dao layer at all? In Java EE world the most convenient option is to just use an EntityManager. Entity Manager plays role of dao very well in most use cases. Although JDBC in your example is good enough, JPA is (IMHO) simpler.

Update

This was a bad guess, see my other answer on this page with updates.

halfer
  • 19,824
  • 17
  • 99
  • 186
G. Demecki
  • 10,145
  • 3
  • 58
  • 58
  • That's not necessarily true. His connection is taken from the datasource, so it is managed by the container. You dont know the dao implementation, so you don't know if creating using `new` has any impact. – Gas Oct 02 '14 at 09:27
  • @Gas sure, I may be wrong, I don't know the dao impl (and also transaction type). But your assumption: _so it is managed by the container_ is **simply wrong**. Author may be using `transaction-type="RESOURCE_LOCAL"`. And then inside each DAO method probably he is starting "by hand" a new transaction. It would perfectly explain his problem. – G. Demecki Oct 02 '14 at 10:17
  • Again, you are too fast :-) - `transaction-type="RESOURCE_LOCAL"` assumes using JPA and `persistence.xml`. Author is rather not using that, since he is passing `connection` object, which is not used by JPA. And connection got from DataSource in CMT managed EJB is always using JTA. And in CMT bean **you cant** start by hand new transaction on connection. So lets stop arguing about something we cannot see. – Gas Oct 02 '14 at 11:06
  • @GrzesiekD. this change plus the xa-datasource solve my problem – John Alexander Betts Oct 10 '14 at 16:27
1

I think the problem is that the Connection/DataSource is simply not part of your current transaction. Instead of injecting an JDBC connection I suggest to:

  1. Create a PersistenceUnit for the defined DataSoruce in your persistence.xml of the JTA type.
  2. Inject the corresponding EntityManager in your EJB.
  3. Unwrap the Connection from the EntityManager injected in step 2. There is no standard way to it, check this answer.
Community
  • 1
  • 1
V G
  • 18,822
  • 6
  • 51
  • 89
  • I'm reading your answer, and first - you says that there is no standard way to solve that problem - which is not true, you just didn't bother to find a root cause of the problem. Second - question is about plain JDBC and you suggest to use JPA - totally irrelevant for this question. And third you are then suggesting nonstandard, provider dependent way to get to connection, instead of plain JDBC API. So I'm not surprised that it was downvoted, because your answer is just not useful in this case. – Gas Oct 12 '14 at 18:23