Enterprise Java Development@TOPIC@
In this chapter we are going to focus on identifying issues that occur when interfacing with a JPA-based EJB and especially through a remote interface. To be fair -- not all of the issues covered in this chapter are unique to JPA but the integration between the Java business objects and database persistence technology is so seamless that it will definitely come up within this context.
Activate the eventSerializable() @Test within EventMgmtIT.java
Build, Deploy, and Test the application from the parent module
This problem can be hard to solve because there is no pointer to the problem. We have a DTO in one of our interface methods that has not implemented Serializable. I can usually tell that when one or more other methods work and a specific method does not work and the client simply reports "No EJB receiver...".
Update Event to implement Serializable within Event.java in the impl module.
We have a choice in how to fix this. We can either add the JAR that contains the missing class to the client's classpath or we can attempt to remove the provider class entirely and pass a pure POJO back to the client. In this section -- we will use the classpath solution. This solution might be appropriate for internal system interfaces and not when you have clients from external organizations. In that later case -- you likely would not use RMI anyway over other more web-friendly technologies like JAX-RS.
Add the JAR that contains the missing class as a dependency of the RMI IT Test module. This is hibernate-core.
Figure 62.6. (option) Add hibernate-core Dependency to Resolve ClassNotFoundException
<!-- necessary to supply hibernate classes when marshaling managed entities as DTOs -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
Note that the above dependency -- without version# -- depends upon the root pom defining a dependencyManagement with the version for implementation modules to use.
Figure 62.7. Add Provider Artifact to Client Classpath/Dependencies
<properties>
<hibernate-entitymanager.version>4.3.4.Final</hibernate-entitymanager.version>
</properties>
<dependencyManagement>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate-entitymanager.version}</version>
</dependency>
</dependencyManagement>
Note the PersistentBag class is still there. Our client just knows what to do with it now.
Update eventTouchedSome() to call getEventTouchedSome() instead of getEvent().
The difference between the two methods is that getEventTouchedSome() specifically accesses some of the properties of the event in order to trigger the lazy-load to occur, in time on the server-side.
Figure 62.12. Pre-Touching EJB Implementation
jpatickets-labex-ejb/src/ |-- main | |-- java | | `-- org | | `-- myorg | | `-- jpatickets | | `-- ejb | | |-- EventMgmtEJB.java
@Override
public Event getEventTouchedSome(int id) {
logger.debug("getEventTouchedSome({})", id);
Event event = getEvent(id);
//touch the ticket collection to load tickets prior to marshaling back
event.getTickets().size();
return event;
}
This solution works because the getEventTouchedMore() method on the server-side does a more complete walk of the object graph before returning it to the client.
Figure 62.15. Deeper Pre-Touch Solution Allowing More Data Returned
jpatickets-labex-ejb/src/ |-- main | |-- java | | `-- org | | `-- myorg | | `-- jpatickets | | |-- dto | | | `-- EventDTO.java | | `-- ejb | | |-- EventMgmtEJB.java
@Override
public Event getEventTouchedMore(int id) {
logger.debug("getEventTouchedMore({})", id);
Event event = getEvent(id);
//touch ticket collection and all seats to load both prior to marshaling back
event.getTickets().size();
event.getVenue().getName();
for (Ticket t: event.getTickets()) {
t.getSeat().getPosition();
}
return event;
}
The new call will call a wrapper method within the EJB that will create a new Event POJO and copy over state information from the managed Event entity. The chain usually continues (i.e., to Tickets) until we have created clean POJO clones free of all provider classes.
Figure 62.21. EventMgmtEJB.getCleansedEvent()
jpatickets-labex-ejb/src/ |-- main | |-- java | | `-- org | | `-- myorg | | `-- jpatickets | | `-- ejb | | |-- EventMgmtEJB.java
@Override
public Event getEventCleansed(int id) {
logger.debug("getCleansedEvent({})", id);
Event event = getEvent(id);
return toCleansed(event);
}
private Event toCleansed(Event bo) {
Event pojo = new Event(bo.getId());
pojo.setName(bo.getName());
pojo.setStartTime(bo.getStartTime());
List<Ticket> tickets = new ArrayList<>(bo.getTickets().size());
for (Ticket t: bo.getTickets()) {
toCleansed(t, pojo);
}
pojo.setTickets(tickets);
return pojo;
}
private Ticket toCleansed(Ticket bo, Event event) {
//example cleansing is stopping here for the example
Ticket pojo = new Ticket(event, bo.getSeat());
pojo.setPrice(bo.getPrice());
pojo.setSold(bo.isSold());
return pojo;
}
The above client call will now be calling a JPA-QL query instead of a simple em.find(). In the figure below I have removed the business logic and DAO layers from the call and exposed the raw NamedQuery -- which is defined in the Event @Entity class. The JPA-QL performs a "join fetch" on the tickets to pre-load that collection. That allows the client to call event.getTickets().size() without suffering a lazy-load error and it allows the information to be more efficiently accessed from the database.
Figure 62.25. Partial Pre-Fetching Solution
jpatickets-labex-ejb/src/ |-- main | |-- java | | `-- org | | `-- myorg | | `-- jpatickets | | `-- ejb | | |-- EventMgmtEJB.java
@Override
public Event getEventFetchedSome(int id) {
logger.debug("getEventFetchedSome({})", id);
List<Event> events = em.createNamedQuery("JPATicketEvent.fetchEventTickets",
Event.class)
.setParameter("eventId", id)
.getResultList();
return events.isEmpty() ? null : events.get(0);
}
jpatickets-labex-impl/src/ |-- main | `-- java | `-- org | `-- myorg | `-- jpatickets | | |-- Event.java
@Entity
@NamedQueries({
@NamedQuery(name="JPATicketEvent.fetchEventTickets",
query="select e from Event e "
+ "join fetch e.tickets "
+ "where e.id=:eventId")
})
public class Event implements Serializable /* */ {
The above call uncovers an impact because the EJB and supporting DAO call execute a deeper "join fetch" to satisfy a more complex information need.
Figure 62.28. Deeper Pre-Fetching Solution
jpatickets-labex-ejb/src/ |-- main | |-- java | | `-- org | | `-- myorg | | `-- jpatickets | | |-- dto | | | `-- EventDTO.java | | `-- ejb | | |-- EventMgmtEJB.java
@Override
public Event getEventFetchedMore(int id) {
logger.debug("getEventFetchedMore({})", id);
List<Event> events = em.createNamedQuery("JPATicketEvent.fetchEventTicketsSeats",
Event.class)
.setParameter("eventId", id)
.getResultList();
return events.isEmpty() ? null : events.get(0);
}
jpatickets-labex-impl/src/ |-- main | `-- java | `-- org | `-- myorg | `-- jpatickets | | |-- Event.java
@Entity
@NamedQueries({
@NamedQuery(name="JPATicketEvent.fetchEventTicketsSeats",
query="select e from Event e "
+ "join fetch e.venue "
+ "join fetch e.tickets t "
+ "join fetch t.seat "
+ "where e.id=:eventId")
})
public class Event implements Serializable /* */ {
The EventDTO removes the need for the Venue entity and just carries the venueName. It removes the need to carry all the tickets and replaces that with just the number of tickets.
Figure 62.31. EventDTO Class
jpatickets-labex-ejb/src/ |-- main | |-- java | | `-- org | | `-- myorg | | `-- jpatickets | | |-- dto | | | `-- EventDTO.java
public class EventDTO implements Serializable {
private int id;
private String eventName;
private String venueName;
private Date startTime;
private int numTickets;
I called this first implementation "brute-force" earlier in the introduction. That is because the EJB method simply walks the event managed entity and grabs what it needs -- loaded or not. As you should expect, this will cause a significant number of lazy-loads. We will improve shortly.
Figure 62.32. Brute-Force getEventLazyDTO()
jpatickets-labex-ejb/src/ |-- main | |-- java | | `-- org | | `-- myorg | | `-- jpatickets | | |-- dto | | | `-- EventDTO.java
@Override
public EventDTO getEventLazyDTO(int id) {
logger.debug("getEventDTO({})", id);
Event event = eventMgmt.getEvent(id);
return toEventDTO(event);
}
private EventDTO toEventDTO(Event event) {
EventDTO dto = new EventDTO();
dto.setId(event.getId());
dto.setEventName(event.getName());
dto.setVenueName(event.getVenue().getName());
dto.setStartTime(event.getStartTime());
dto.setNumTickets(event.getTickets().size());
return dto;
}
The EJB invokes a NamedQuery on the DAO (layers have been removed for clarity) that is tuned to provide the information required for the EventDTO. The DAO returns the information (event, venueName, and numTickets) in a Map since the DTO is located in the EJB module and not accessible to the DAO code in the Impl module. The EJB uses the information from the map to populate the DTO prior to returning it to the client.
Figure 62.35. Fetched DTO EJB/DAO Calls
jpatickets-labex-ejb/src/ |-- main | |-- java | | `-- org | | `-- myorg | | `-- jpatickets | | |-- dto | | | `-- EventDTO.java | | `-- ejb | | |-- EventMgmtEJB.java
@Override
public EventDTO getEventFetchedDTO(int eventId) {
@SuppressWarnings("unchecked")
List<Object[]> rows = em.createNamedQuery("JPATicketEvent.fetchEventDTO")
.setParameter("eventId", eventId)
.getResultList();
Map<String, Object> dtoData = new HashMap<String, Object>();
if (!rows.isEmpty()) {
Object[] row = rows.get(0);
Event event = (Event) row[0];
String venueName = (String) row[1];
Number numTickets = (Number) row[2];
dtoData.put(EVENT, event);
dtoData.put(VENUE_NAME, venueName);
dtoData.put(NUM_TICKETS, numTickets.intValue());
}
return toEventDTO(dtoData);
}
private EventDTO toEventDTO(Map<String, Object> dtoData) {
EventDTO dto = new EventDTO();
Event event = (Event) dtoData.get(EventMgmtDAO.EVENT);
String venueName = (String) dtoData.get(EventMgmtDAO.VENUE_NAME);
int numTickets = (Integer) dtoData.get(EventMgmtDAO.NUM_TICKETS);
dto.setId(event.getId());
dto.setEventName(event.getName());
dto.setStartTime(event.getStartTime());
dto.setVenueName(venueName);
dto.setNumTickets(numTickets);
return dto;
}
To complete our exercise, I will also explain the DAO query. The DAO uses a @NamedNativeQuery with custom native SQL and a @SqlResultSetMapping. The native SQL is used to obtain an Event entity, the name of the Venue, and perform an aggregate count() of the tickets within the DB. The @SqlResultSetMapping is used to realize a managed Event instance, a venueName String, and numTickets Number from the returned columns. If you look back at the EJB/DAO processing above you will see the information coming back in three (3) elements of an Object array.
Figure 62.36. Fetched DTO JPA Support
jpatickets-labex-impl/src/ |-- main | `-- java | `-- org | `-- myorg | `-- jpatickets | | |-- Event.java
@Entity
@Table(name="JPATICKETS_EVENT")
@NamedNativeQueries({
@NamedNativeQuery(name="JPATicketEvent.fetchEventDTO",
query="select event.EVENT_ID, event.EVENT_NAME, event.START_TIME, event.VENUE_ID, "
+ "venue.NAME venueName, count(ticket.*) numTickets "
+ "from JPATICKETS_EVENT event "
+ "join JPATICKETS_VENUE venue on venue.VENUE_ID = event.VENUE_ID "
+ "join JPATICKETS_TICKET ticket on ticket.EVENT_ID = event.EVENT_ID "
+ "where event.EVENT_ID = :eventId "
+ "group by event.EVENT_ID, event.EVENT_NAME, event.START_TIME, event.VENUE_ID, venue.NAME",
resultSetMapping="JPATicketEvent.EventDTOMapping")
})
@SqlResultSetMappings({
@SqlResultSetMapping(name="JPATicketEvent.EventDTOMapping",
entities={
@EntityResult(entityClass=Event.class)
},
columns={
@ColumnResult(name="venueName", type=String.class),
@ColumnResult(name="numTickets", type=Long.class)
}
)
})
public class Event implements Serializable /* */ {