I am trying to refactor an old application to use EJB3 with JPA.
We have two client layers (one servlet-based, one not) which both call into a delegate layer, which calls an EJB layer, which in turn calls a DAO. The EJB is EJB2 (bean-managed persistence), and the DAO uses hand-rolled SQL queries, committing transactions and closing connections manually.
I want to replace the EJB2 with EJB3, and change all the DAO to use JPA.
I started off by replacing the EJB2 code with an EJB3 using container-managed transactions. Since hibernate Criteria are so simple, and the EntityManager can be injected, I can do something like this:
@Stateless
public class NewSelfcareBean implements SelfcareTcApi {
@PersistenceContext(unitName="core")
EntityManager em;
public BasicAccount getAccount(String id) {
Criteria crit = getCriteria(BasicAccount.class);
crit.add(Restrictions.eq("id", id));
BasicAccount acc = (BasicAccount) crit.uniqueResult();
}
}
No need for a separate DAO layer. The account object looks a bit like this:
@Entity
@Table(name="er_accounts")
public class BasicAccount {
@OneToMany( mappedBy="account", fetch=FetchType.LAZY)
protected List<Subscription> subscriptions;
}
But in the servlet layer, where I call the EJB to get the account object, I want to build a response which might (or might not) include child subscriptions from the BasicAccount:
The servlet layer looks like this:
ResponseBuilder rb;
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
...
Account acc = getDelegateLayer().getAccount();
rb.buildSubscriptionResponse(acc.getSubscriptions());
...
}
Obviously this doesn't work since the transaction and entitymanager have been closed by the time we get back to the servlet layer - I get a LazyInitializationException.
So I can see a few options:
- ServletFilter to manage transactions manually. This means I lose the benefits of EJB container-managed transactions, doesn't it? Plus I would have to implement another filter mechanism on the other client (not the end of the world).
- Use a Stateful session bean instead, then I can use extended persistence context. But I don't really need a stateful bean, since no data is retained between transactions. So it would place unnecessary load on the server and I'd be using Stateful for something it wasn't really designed for.
- Call
Hibernate.init(acc.getSubscriptions())
- this will work but needs to be done in the EJB. Supposing I re-use the bean for another client method which doesn't need the subscriptions? An unnecessary DB call. - Use EAGER FetchType on my Account object. Bad for performance and creates unnecessary DB load.
None of these options seems any good.
Am I missing something? How should this be done? I can't be the first person to have this problem...
Two fetching policies means two use cases, so you are better of writing two methods in this case:
Eager fetching is most often a code smell and it's the service layer (EJB) responsibility to fetch the data. Finding yourself hacking the web layer to add transactional responsibility is a sign of breaking application layer boundaries.
A better approach is to use DTOs. JPA entities are persistence related and they leak the database and the ORM specific fetching retrieval mechanisms. A DTO is a much better fit since it can minimize the amount of data being fetched and sent to the web layer, therefore being ideal for rendering a view. Although you can practically use entities both in the service layer and in the web layer, there are use cases when a data projection is a better alternative.