I've got a more likely theoretical and software designing question regarding EJB-s and self invocation and transactions.
Let's suppose I have got an EJB class where I call one of the EJB-s method with another method which is also in this EJB. But i want to use the transactional annotation feature in the second method in order. So to be more specific:
@Local(BorrowingService.class)
@Stateless
public class BorrowingServiceBean implements BorrowingService {
static final JcanLogger LOG = JcanLoggerFactory
.getLogger(BorrowingServiceBean.class);
@PersistenceContext(unitName = ApplicationConstants.PERSISTENCE_UNIT_NAME)
protected EntityManager em;
@Resource
private SessionContext sessionContext;
@EJB
private PersonService personService;
@EJB
private StatusBean statusBean;
@EJB
private BookService bookService;
private static final long CAUSELESS_RETURN_COST = 5;
private static final String CHECKED_ISBN = "0201104040";
public static final long PERSON_ACCOUNT_REDUCTION = 10;
@Override
public void returnBook(Long bookId, Long userId) {
if (bookId == null || userId == null) {
throw new IllegalArgumentException("bookId and userId must not be null");
}
List<BorrowingEntity> borrowingsByUserId = getBorrowingsByUserId(userId);
for (BorrowingEntity borrowingEntity : borrowingsByUserId) {
BookEntity tmpBook = borrowingEntity.getBook();
if (tmpBook.getBookId().equals(bookId)) {
em.remove(borrowingEntity);
break;
}
}
//recomended self invocation could be here
onBookReturn(userId, bookId);
}
/**
* Aims to run a post-processing method in a new transaction (shouldn't be removed from here, and should remain as it is).
* Intention: The db change followed by the exception here represents some operations that should be rolled back for some reason.
*/
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
@Override
public void onBookReturn(Long userId, Long bookId) {
BookEntity book = bookService.getBookById(bookId);
if (CHECKED_ISBN.equals(book.getIsbnNumber())) {
personService.decreasePersonAccount(userId, CAUSELESS_RETURN_COST);
throw new InvalidOperationException("An operation has been attempted, that shouldn't be executed. This change should be rolled back.");
}
}
}
I know that there are some solution for such situation like this.
1. I could use self injection with @EJB
annotation I inject the bean into itself and call the annotated method.
2. I could use JNDI which is a bit old but fine.
3. I could use the SessionContext
's getBusinessObject
method which is in my current example.
4. I could create another EJB and put this annotated method there.
My question is in terms of software design and clean code which is better? Because I know that self-injection is not a good way, JNDI is old, but SessionContext solution is also based on JNDI, and if I separate the method into another EJB i just sacrifice the application's OOP design for using the EJB technology clean... So what is a recommended solution in a situation liket this? Thanks for advance! If this question is to theoretical for Stackoverflow and it would be more preferable to ask it on Software Engineering Stack Exchange, feel free to comment and I'll remove it from here.
Thanks for in advance!
I always use SessionContext. In the @PostConstruct, I call sessionContext.getBusinessObject and set a member variable named 'self'. Then, when I need to call a local method, I use 'self.someMethod()'.
Note I only do this if I need a separate transaction or want to call that method asynchronously.