In the previous installment I described the issues that I encountered persisting an existing POJO domain model with EJB 3. EJB 3.0 is definitely an improvement over EJB2 but the current O/R mapping is extremely limited when compared to Hibernate or JDO. As a result I was less happy than someone migrating to EJB 3 from EJB 2.
The next step in the EJB 3.0 migration process is to port the domain model classes that find, create and delete persistent objects. In this domain model the classes that manipulate persistence objects are the repositories, which are DAO-like classes that represent collections of persistent objects. There are three repositories:
These classes are mostly simple wrappers around the persistence framework APIs. The JDO versions use the Spring JdoTemplate class and the Hibernate versions use the HibernateTemplate class. In this blog I am going to describe the EJB 3 implementations of the PendingOrderRepository and the RestaurantRepository.
Implementing the PendingOrderRepository
The PendingOrderRepository defines two methods: createPendingOrder(), which creates a PendingOrder, and findPendingOrder(), which finds a PendingOrder:
public interface PendingOrderRepository {
PendingOrder createPendingOrder();
PendingOrder findPendingOrder(String pendingOrderId);
}
Spring does not yet support EJB 3 EntityManagerTemplate so the repositories must call the EntityManager directly. Fortunately, the PendingOrderRepository is pretty simple so its EJB 3 implementation is straightforward. The createPendingOrder() method calls EntityManager.persist() and the findPendingOrder() method calls EntityManager.find():
public class EJB3PendingOrderRepository implements
PendingOrderRepository {
private EntityManager entityManager;
public EJB3PendingOrderRepository(EntityManager entityManager) {
this.entityManager = entityManager;
}
public PendingOrder createPendingOrder() {
PendingOrder po = new PendingOrder();
entityManager.persist(po);
return po;
}
public PendingOrder findPendingOrder(String pendingOrderId) {
return (PendingOrder) entityManager.find(
PendingOrder.class, new Integer(pendingOrderId));
}
}
Since its so simple there is not much else to say. Let's look at the RestaurantRepository, which is a little more complicated.
Implementing the RestaurantRepository
The RestaurantRepository defines two methods: findRestaurant(), which simply finds the restaurant with specified id, and findAvailableRestaurants(), which finds the restaurants that serve the specified delivery and delivery time:
public interface RestaurantRepository {
public List findAvailableRestaurants(
Address deliveryAddress,
Date deliveryTime);
public Restaurant findRestaurant(String restaurantId);
}
The findRestaurant() method calls Entity.find() to load the specified restaurant and the findAvailableRestaurants() method executes a named query passing as parameters the delivery address's zip code, and the delivery time's day of week, hour and minute:
public class EJB3RestaurantRepository implements
RestaurantRepository {
private EntityManager entityManager;
public EJB3RestaurantRepository() {
}
public EJB3RestaurantRepository(
EntityManager entityManager) {
this.entityManager = entityManager;
}
public Restaurant findRestaurant(
String restaurantId) {
return entityManager.find(Restaurant.class,
new Integer(restaurantId));
}
public List findAvailableRestaurants(
Address deliveryAddress, Date deliveryTime) {
Query query = entityManager
.createNamedQuery("Restaurant.findAvailableRestaurants");
Calendar c = Calendar.getInstance();
c.setTime(deliveryTime);
int dayOfWeek = c.get(Calendar.DAY_OF_WEEK);
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);
String zipCode = deliveryAddress.getZip();
query.setParameter("dayOfWeek", new Integer(
dayOfWeek));
query.setParameter("hour", new Integer(hour));
query.setParameter("minute", new Integer(
minute));
query.setParameter("zipCode", new Integer(
zipCode));
return query.getResultList();
}
The findRestaurants() method uses a Calendar to extract the components of the delivery time and passes them as named query parameters. It executes the following named query, which is defined as annotation of the Restaurant class:
@Entity (access=AccessType.FIELD)
@NamedQuery(
name="Restaurant.findAvailableRestaurants",
queryString="SELECT OBJECT(restaurant) FROM Restaurant as restaurant, IN(restaurant.serviceArea) as zip, IN(restaurant.timeRanges) as tr WHERE zip.zipCode = :zipCode AND tr.dayOfWeek = :dayOfWeek AND ( (tr.openHour < :hour OR (tr.openHour = :hour AND tr.openMinute <= :minute)) AND (tr.closeHour > :hour OR (tr.closeHour = :hour AND tr.closeMinute > :minute) ))"
)
@Table(name="FTGO_RESTAURANT")
public class Restaurant implements Serializable {
...
This query finds restaurants whose serviceArea contains the specified zip and that have a timeRange for the specified delivery time.
Summary
As you can see, implementing the repositories was extremely straightforward. It is quite nice when things are this easy. In the next installment I will describe the design of the session facade that encapsulates the business logic. I will also explain how to use EJB dependency injection to supply its dependencies including the repositories described above.