How to align EJB3 and servlets correctly?

I am trying to reorganize an old application to use EJB3 with JPA.

We have two client layers (one based on a servlet, one not), which are both called on the delegate layer, which calls the EJB layer, which in turn calls the DAO. EJB is EJB2 (bean-set storage), and the DAO uses manual SQL queries that complete transactions and close connections manually.

I want to replace EJB2 with EJB3 and change all DAOs to use JPA.

I started by replacing EJB2 code with EJB3 using container-driven transactions. Since the criteria for hibernation is so simple, and the EntityManager can be entered, 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 separate DAO layer required. The account object looks something 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 EJB to get the account object, I want to create a response that may (or may not include) child subscriptions from BasicAccount:

The servlet level is as follows:

 ResponseBuilder rb; public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { ... Account acc = getDelegateLayer().getAccount(); rb.buildSubscriptionResponse(acc.getSubscriptions()); ... } 

Obviously, this did not work, since the transaction and the entity-manipulator were closed by the moment of returning to the servlet level. I get a LazyInitializationException.

So, I see several options:

  • ServletFilter for manual transaction management. This means that I am losing the benefits of EJB container managed transactions, right? In addition, I would have to implement another filtering mechanism on another client (and not at the end of the world).
  • Use a stateful bean session instead, then I can use the extended persistence context. But I really don't need a stateful bean, because the data is not saved between transactions. Thus, this would create an unnecessary load on the server, and I would use Stateful for something that it was not intended for.
  • Calling Hibernate.init(acc.getSubscriptions()) - this will work, but it needs to be done in EJB. Suppose I reuse a bean for another client method that does not need subscriptions? Optional DB call.
  • Use EAGER FetchType for the object of my account. Bad for performance and creates unnecessary load on the database.

None of these options seem good.

Am I missing something? How to do it? I cannot be the first person to have this problem ...

+5
source share
2 answers

Two sampling policies are two use cases, so in this case you better write two methods:

 public BasicAccount getAccount(String id) { Criteria crit = getCriteria(BasicAccount.class); crit.add(Restrictions.eq("id", id)); BasicAccount acc = (BasicAccount) crit.uniqueResult(); } } public BasicAccount getAccountWithSubscriptions(String id) { Criteria crit = getCriteria(BasicAccount.class); crit.add(Restrictions.eq("id", id)); crit.setFetchMode("subscriptions", FetchMode.JOIN); BasicAccount acc = (BasicAccount) crit.uniqueResult(); } } 

Unwanted sampling is most often the smell of code , and the responsibility for selecting data for it depends on the level of service (EJB). Searching for a web layer hack to add transactional responsibility is a sign of breaking the boundaries of the application layer.

The best approach is to use DTO. JPA entities are associated with persistence, and they leak through the database and specific ORM selection search engines. DTO is much better suited, as it can minimize the amount of data received and sent to the web layer, therefore it is ideal for visualizing a presentation. Although you can practically use objects both at the service level and at the web level, there are examples of use when data projection is the best alternative.

+6
source

This is a VERY typical EAGER / LAZY extraction problem.

Solution 1 (not the best): create two methods: one getBasicAccountWithSubscription() and one getBasicAccount() , as Vlad said. This is the smell code IMHO. Imagine that you have 10 more such relationships. Creating such a method for every possible combination will lead to the creation of 2 ^ 10 (exponential) new methods (for example, getBasicAccountWithSubsciptionWithoutLastLoginsWithTelephones...() ), which is not very good. Of course, you could generate a method with 10 logical parameters that determine what relation to the selection, but in this case the method will mix the database material (what relation to loading) with business logic parameters (BasicAccount identifier in your case).

Solution 2 (the best IMHO): create one more method in your EJB: List<Subscription> getSubscriptionsForAccount(Long accountId); (in this case, the property will be deliberately lazy). Call the new method in which you need an account subscription. To make sure no one calls your BasicAccount.getSubscriptions() method, you can make it private. If you had 10 relationships, you would create a new method for each relationship (so the number of new methods would be linear). For example, if you had a different relation, private List<Login> logins; in your BasicAccount organization, you would add another method to the EJB public List<Login> getLoginsForAccount(Long accountId) .

+1
source

All Articles