EJB3 and Seam: non-atomic transactions with loop scenario
gonorrhea Mar 23, 2009 7:03 PMI finally figured out what I was doing wrong all last week. Apparently you cannot demarcate private methods with @TransactionAttribute(...) in SFSB/SLSB classes. Unfortunately, the container/JBoss/Eclipse, etc. does not complain about you doing this so I thought I was doing something else wrong.
Specifying the TransactionAttribute annotation on the bean class means that it applies to all
applicable business interface methods of the class.
JSR220: section 13.3.7.1
Here is the scenario...
functional requirement: user inputs multiple serial numbers in a HtmlInputTextarea control and submits to action method in SFSB. There is a loop in the action method that loops thru each serial number in the List and persists x methods to up to three tables in RDBMS. We want each iteration for each serial number to be wrapped in its own tx. This means we are breaking the atomicity rule of the
ACID properties of database transactions (on purpose as per the functional requirement). i.e., if the serial numbers persist to the tables prior to a RuntimeException and the others after the exception don't exec/commit, that's fine. So I modeled this as NOT_SUPPORTED for the public action method and then injected another SFSB with its own persist() methods that are both demarcated as REQUIRED (default). It works fine when you use @Begin(join=true) for the action method but if when I tried @Begin(join=true, flushMode=FlushModeType.MANUAL) and flushed manually at the end of the persistX() methods, I got the following exception for the persistB() method after flush() execs:
Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
Why does this happen? Can we not use MANUAL flushMode in this use case?
Here is the code:
action SFSB:
@Stateful @Name("testTransactionsAction") public class TestTransactionsAction implements TestTransactionsLocal { @Logger private Log log; @In StringUtils stringUtils; private String serialNumbers; @In private TestPersistLocal testPersistBean; /*--------------------------------------------------------------------------BEGIN METHODS-------------------------------------------------------------------*/ @Begin(join=true) @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) public void searchSerialNumbers() { //parse serial numbers from HtmlInputTextarea control.... List<String> serialNumberList = parseSerialNumber(); if (serialNumberList != null && serialNumberList.size() > 0) { for (String serialNumber : serialNumberList) //persist records one serialNumber at a time... { this.persistA(serialNumber); this.persistB(serialNumber); } } } //Specifying the TransactionAttribute annotation on the bean class means that it applies to all //applicable business interface methods of the class. // JSR220: section 13.3.7.1 //@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) private void persistA(String serialNumber) { testPersistBean.persistA(serialNumber); } //@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) private void persistB(String serialNumber) { testPersistBean.persistB(serialNumber); } private List<String> parseSerialNumber() { //parse serialNumber assuming that the regex to use is CR (carriage return), which will be scan gun post-fire append <ENTER> List<String> serialNumberList = stringUtils.splitAndTrimStringAsArray(serialNumbers, "\\r"); return serialNumberList; } public String getSerialNumbers() { return serialNumbers; } public void setSerialNumbers(String serialNumbers) { this.serialNumbers = serialNumbers; } @Remove @Destroy public void destroy() {} }
Here is the support SFSB:
@Name("testPersistBean") @Stateful @AutoCreate public class TestPersistBean implements TestPersistLocal { @Logger private Log log; @In FacesMessages facesMessages; @In private EntityManager entityManager; /*-------------------------------------------BEGIN METHODS---------------------------------------------*/ public void persistA(String serialNumber) { TestTransactions testTransactions1 = new TestTransactions(); testTransactions1.setSerialNumber(serialNumber); testTransactions1.setAddedDate(new Date()); entityManager.persist(testTransactions1); //entityManager.flush(); } public void persistB(String serialNumber) { TestTransactions testTransactions2 = new TestTransactions(); testTransactions2.setSerialNumber(serialNumber); testTransactions2.setAddedDate(new Date()); entityManager.persist(testTransactions2); //entityManager.flush(); //Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress } @Remove @Destroy public void destroy() {} }