1

I developed a typical enterprise application that is responsible for provisioning customer to a 3rd party system. This system has a limitation, that only one thread can work on a certain customer. So we added a simple locking mechanism that consists of @Singleton which contains a Set of customerIds currently in progress. Whenever a new request comes for provisioning, it first checks this Set. If cusotomerId is present, it waits otherwise it adds it to the Set and goes into processing.

Recently it was decided, that this application will be deployed in cluster which means that this locking approach is no longer valid. We came up with a solution to use DB for locking. We created a table with single column that will contain customerIds (it also has a unique constraint). When a new provisioning request comes we start a transaction and try and lock the row with customerId with SELECT FOR UPDATE (if customerId does not exist yet, we insert it). After that we start provisioning customer and when finished, we commit transaction. Concept works but I have problems with transactions. Currently we have a class CustomerLock with add() and remove() methods that take care of adding and removing customerIds from Set. I wanted to convert this class to a stateless EJB that has bean-managed transactions. add() method would start a transaction and lock the row while remove() method would commit transaction and thus unlocked the row. But it seems that start and end of transaction has to happen in the same method. Is there a way to use the approach I described or do I have to modify the logic so the transaction starts and ends in the same method?

CustomerLock class:

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class CustomerLock {

    @Resource
    private UserTransaction tx;

    public void add(String customerId) throws Exception {
        try {
            tx.begin();
            dsApi.lock()
        } catch (Exception e) {
            throw e;
        }
    }

    public void remove(String customerId) throws Exception {
        try {
            tx.commit();
        } catch (Exception e) {
            throw e
        }
    }
}

CustomerProvisioner class excerpt:

public abstract class CustomerProvisioner {

    ...

    public void execute(String customerId) {
        try {
            customerLock.add(customerId);

            processing....

            customerLock.remove(customerId);
        } catch (Exception e) {
            logger.error("Error", e);
        }
    }

    ...

}

StandardCustomerProvisioner class:

@Stateless
public class StandardCustomerProvisioner extends CustomerProvisioner {

    ...

    public void provision(String customerId) {
        // do some business logic
        super.execute(customerId);
    }
}
goggy
  • 25
  • 6
  • Why do you think that "start and end of transaction has to happen in the same method" ? There is quite strange commit call in finally block BTW. When everything goes fine (no exception thrown), it will commit transaction and DB lock will disappear so far (it's transaction-wide). – user3714601 Jan 05 '17 at 17:11
  • sorry... that 'finally' was not ment to be there :) I corrected the code. – goggy Jan 06 '17 at 08:11
  • Excerpt = snippet, those are usually not detailed enough. What is "CustomerProvisioning" ? Another EJB? – Gimby Jan 06 '17 at 08:50
  • It's a little more "twisted" but technically yes. Elaborated a little more... We have three implementations of CustomerProvisioning abstract class. – goggy Jan 06 '17 at 09:13
  • 1
    The only thing I can see now that there is a mix of container managed transactions and bean managed transactions going on, but I don't see how things are wired up together at all. – Gimby Jan 06 '17 at 09:29
  • I realized my mistake now :) Thank you all for comments... – goggy Jan 19 '17 at 13:35

1 Answers1

1

As @Gimby noted, you should not mix container-managed and bean-managed transactions. Since your StandardCustomerProvisioner has no annotation like "@TransactionManagement(TransactionManagementType.BEAN)" - it uses container-managed transactions, and REQUIRED by default.

You have 2 options to make it work:

1) To remove "@TransactionManagement(TransactionManagementType.BEAN)" with UserTransaction calls and run CMT

2) Add this annotation ("@TransactionManagement(TransactionManagementType.BEAN)") to StandardCustomerProvisioner and use transaction markup calls from this method, so all the invoked methods use the same transactional context. Markup calls from CustomerLock should be removed anyway.

user3714601
  • 1,156
  • 9
  • 27
  • Sorry for the late response... Yes I totally misunderstood how this transactions work in container. It got me thinking only after a couple of comments to realize the above solution. I guess I just needed a little 'brainstorming' :) Thank you all for replies! Cheers – goggy Jan 19 '17 at 13:33