6
\$\begingroup\$

I thinking of a way to start a new transaction inside a single EJB bean. Typical use case would be to process every item from a list in a separate transaction.

One way to do it would be to use UserTransaction:

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class ManagerBean implements Manager {
    @Resource
    private UserTransaction tx;

    @Override
    public void processAll(List<Object> list) throws Exception {
        for (Object obj : list) {
            tx.begin();
            try {
                processOne(obj);
                tx.commit();
            } catch (Exception ex) {
                tx.rollback();
            }
        }
    }

    private void processOne(Object obj) {
        /* ... */
    }
}

But I find it error prone to directly use commit() and rollback(). I'd prefer to leave transaction handing to the container. So I've come up with this pattern:

ManagerBean.java

@Stateless
public class ManagerBean implements Manager, ManagerInternal {
    @Resource
    private SessionContext context;

    @Override
    public void processAll(List<Object> list) {
        ManagerInternal txThis = context.getBusinessObject(ManagerInternal.class);
        for (Object obj : list) {
            txThis.processOne(obj);
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void processOne(Object obj) {
        /* ... */
    }
}

@Local
interface ManagerInternal {
    void processOne(Object obj);
}

Manager.java:

@Local
public interface Manager {
    void processAll(List<Object> list);
}

Though I'm unsure if it's ok to have package-private local interface. It works on JBoss 4.2, but I don't know about other app servers.
What do you think about this pattern?

Alternative using self-injection:

Instead of using SessionContext.getBusinessObject(interface) I suppose you can inject EJB into itself:

ManagerBean.java

@Stateless
public class ManagerBean implements Manager, ManagerInternal {
    @EJB
    private ManagerInternal txThis;

    @Override
    public void processAll(List<Object> list) {
        for (Object obj : list) {
            txThis.processOne(obj);
        }
    }

    @Override
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void processOne(Object obj) {
        /* ... */
    }
}

@Local
interface ManagerInternal {
    void processOne(Object obj);
}

Manager.java stays the same.

EJB interfaces with package access seem to be not allowed by Wildfly.

\$\endgroup\$
5
\$\begingroup\$

Variable Names:

Your variable names are very generic. The List<Object> you pass in is just called list. This might be fine at coding-time, but as soon as you have to analyze the code weeks or maybe even months later, you will probably wonder, what purpose that list serves.

I'd thus propose you change the variable name a bit. Something like listForPersisting or something should be fine ;) The same may apply to your UserTransaction and the SessionContext, but as they are defined enough by their typename, that shouldn't be a big problem.

Spacing / Comments

I realy like your spacing. Your code is speaking enough, that you can leave out comments and still you understand it on first reading.

That's very good, keep it up!

Approach

I don't think you can solve your specific problem in many other ways as elegantly as you did.

What I do not like about your code is that you needlessly create the interface ManagerInternal as if you needed a function to override.
You name that interface "Internal" and go through the hassle of making it @Local. But then you do something totally incomprehensive. You expose the overriden method.

If you don't need the method anywhere else, the principle of information hiding applies.

In computer science, information hiding is the principle of segregation of the design decisions in a computer program that are most likely to change, thus protecting other parts of the program from extensive modification if the design decision is changed. The protection involves providing a stable interface which protects the remainder of the program from the implementation (the details that are most likely to change).

Written another way, information hiding is the ability to prevent certain aspects of a class or software component from being accessible to its clients, using either programming language features (like private variables) or an explicit exporting policy.

\$\endgroup\$
  • \$\begingroup\$ That does not solve my case. In the code you posted either all list elements are saved or none (on rollback). That's not what I'm trying to achieve. I'll update the question to make it clear. \$\endgroup\$ – rzymek Mar 2 '14 at 21:04
  • \$\begingroup\$ @rzymek Sorry for the long pipeline.. The code posted was meant as a possible alternative. It's not tested with your specific problem. As you posted your code for review, I thought you had the functionality finished. I will edit my answer to more focus on the review character. \$\endgroup\$ – Vogel612 Mar 3 '14 at 7:45
  • \$\begingroup\$ The thing with ManagerInternal interface is that the JavaEE container offers transaction management only when methods are called via @Local/@Remote interface. At least for < JavaEE6. \$\endgroup\$ – rzymek Mar 4 '14 at 9:10
  • \$\begingroup\$ @rzymek my problem was more, the modifier public... you needlessly expose a method, you only need for internal purposes, which is a possibly exploitable weakness \$\endgroup\$ – Vogel612 Mar 4 '14 at 9:14
  • \$\begingroup\$ Unfortunately it has to be public because processOne has to be called via interface. And I can't make an interface method not public. \$\endgroup\$ – rzymek Mar 4 '14 at 9:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.