The first example is a facade for interacting with the database and processing the OrderEntity. It includes 3 files: The OrderEntityFacade, the OrderEntityFacadeInterface, and a helper class the AbstractFacade. The OrderEntityFacade imports the OrderEntity posted in Entities - JPA 2.0.
OrderEntityFacade.java
package com.sample
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
@Stateless
public class OrderEntityFacade extends AbstractFacade<OrderEntity> implements OrderEntityFacadeInterface, Serializable {
@PersistenceContext(unitName = "JSFDemoPU")
private EntityManager em;
@Override
protected EntityManager getEntityManager() {
return em;
}
@Override
public void setEm(EntityManager em) {
this.em = em;
}
public OrderEntityFacade() {
super(OrderEntity.class);
}
private List<OrderEntity> createSampleData() {
List<OrderEntity> list = null;
Long numberParts = (Long) getEntityManager().createQuery("select count(*) from PartEntity t").getSingleResult();
if (numberParts == 0) {
PartEntity tower = new PartEntity("Tower", null);
PartEntity monitor = new PartEntity("Monitor", tower);
PartEntity printer = new PartEntity("Printer", tower);
PartEntity paper = new PartEntity("Paper", printer);
PartEntity entertain = new PartEntity("Entertainment Subsystem", tower);
PartEntity speakers = new PartEntity("Speakers", entertain);
PartEntity glasses3d = new PartEntity("3D Glasses", entertain);
PartEntity pad = new PartEntity("Game Pad", entertain);
PartEntity building = new PartEntity("Building", null);
PartEntity security = new PartEntity("Security", building);
PartEntity guards = new PartEntity("Guards", security);
PartEntity cameras = new PartEntity("Cameras", security);
PartEntity alarm = new PartEntity("Alarm", security);
PartEntity carpet = new PartEntity("Carpet", building);
this.getEntityManager().persist(tower);
this.getEntityManager().persist(building);
UserEntity user = new UserEntity("TestUser", "TestPassword");
this.getEntityManager().persist(user);
OrderEntity sampleOrder = new OrderEntity();
sampleOrder.setName("Sample Order1");
OrderPartsEntity orderPartsEntity = new OrderPartsEntity(sampleOrder, tower, null);
list = new ArrayList<OrderEntity>();
list.add(sampleOrder);
this.getEntityManager().persist(sampleOrder);
}
return list;
}
@Override
public List<OrderEntity> findAll() {
List<OrderEntity> list = getEntityManager().createQuery("select t from OrderEntity t").getResultList();
if (list.isEmpty()) {
list = createSampleData();
}
return list;
}
private void loadParts(OrderPartsEntity part) {
for (OrderPartsEntity subPart : part.getSubOrderParts()) {
loadParts(subPart);
}
}
@Override
public OrderEntity getOrderEntity(OrderEntity orderEntity) {
OrderEntity fullyLoadedOrderEntity = em.merge(orderEntity);
for (OrderPartsEntity part : fullyLoadedOrderEntity.getOrderedParts()) {
loadParts(part);
}
return fullyLoadedOrderEntity;
}
/**
* retrieves an order entity with name = supplied name
* @param name of the entity to be retrieved
* @return null if no entity has name, or the first entity whose name is name, order being undefined
*/
@Override
public OrderEntity findByName(String name) {
Query query = em.createQuery("select q from OrderEntity q where name=?1");
query.setParameter(1, name);
List results = query.getResultList();
if (results.isEmpty()) {
return null;
}
return (OrderEntity) results.get(0);
}
@Override
public EntityManager getEm() {
return em;
}
@Override
public void remove(OrderEntity entity) {
super.remove(entity.getId());
}
}
package com.sample;
import java.io.Serializable;
import java.util.List;
import javax.ejb.Local;
import javax.persistence.EntityManager;
/**
* @author Thomas Dias
*/
@Local
public interface OrderEntityFacadeInterface extends Serializable {
void create(OrderEntity testEntity);
void edit(OrderEntity testEntity);
void remove(OrderEntity testEntity);
OrderEntity find(Object id);
List<OrderEntity> findAll();
int count();
public OrderEntity getOrderEntity(OrderEntity orderEntity);
public EntityManager getEm();
public void setEm(EntityManager em);
public OrderEntity findByName(String name);
}
AbstractFacade.java
package com.sample;
import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import javax.persistence.EntityManager;
/**
* @author Thomas Dias
*/
public abstract class AbstractFacade<T> implements Serializable {
private static final Logger logger = Logger.getLogger("AbstractFacade");
private Class<T> entityClass;
static {
try {
final FileHandler fh = new FileHandler("JSFDemoAPP.log");
fh.setFormatter(new SimpleFormatter());
logger.addHandler(fh);
} catch (IOException e) {
} catch (SecurityException e) {
}
}
protected abstract EntityManager getEntityManager();
public AbstractFacade(Class<T> entityClass) {
this.entityClass = entityClass;
}
public void create(T entity) {
getEntityManager().persist(entity);
}
public void edit(T entity) {
logger.log(Level.FINE, "Saving {0}, {1} ", new Object[]{entity.getClass(), entity});
getEntityManager().merge(entity);
}
public void remove(Long id) {
if (id != null) {
T toRemove = find(id);
if (toRemove != null) {
getEntityManager().remove(toRemove);
}
}
}
public T find(Object id) {
if (logger.isLoggable(Level.WARNING)) {
logger.log(Level.WARNING, "my warning : finding {0}", id.toString());
}
logger.log(Level.INFO, "my info : finding {0}", id);
logger.severe("my severe : finding ");
return getEntityManager().find(entityClass, id);
}
public List<T> findAll() {
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
cq.select(cq.from(entityClass));
return getEntityManager().createQuery(cq).getResultList();
}
public int count() {
javax.persistence.criteria.CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
javax.persistence.criteria.Root<T> rt = cq.from(entityClass);
cq.select(getEntityManager().getCriteriaBuilder().count(rt));
javax.persistence.Query q = getEntityManager().createQuery(cq);
return ((Long) q.getSingleResult()).intValue();
}
}
The first question that arises from this example is: "What is a facade?" It is a design pattern, similar to the DAO (or it can use a DOA), and according to Sun, "Use a session bean as a facade to encapsulate the complexity of interactions between the business objects participating in a workflow. The Session Facade manages the business objects, and provides a uniform coarse-grained service access layer to clients. " See the full definition at: Core J2EE Patterns - Session Facade. Although typically the facade would not do the work directly, but implement a service or DAO, I have simplified the code here to go direct. It would be a simple matter to abstract out that functionality and place it into a DAO and inject the DOA into our bean.
Next, we see the @Stateless followed by the class definition. Here we have defined the class to implement Serializable. There is no requirement to be Serializable, unless you are an @State or an @Remote bean. These are required to be Serializable in case the @State bean is passivated, or the @Remote bean is called from an RMI. Also, we have implemented an interface. This means this EJB is an interface bean and could be used as an @Remote bean if we desired. It also means when we inject the bean the default name is the interface name, not the class name. There was no particular reason to make this bean an Interface Bean other than demonstration purposes.
We then inject a persistence unit. Notice, we have a getter and setter for the persistence unit. This is not required, but is helpful when we test.
We have a default constructor which does something. In our other beans we have used the @PostConstruct to do our initialization, this is not to say the constructor can't be used, as we see, it can, but we may need the injections to have taken place before we do our initialization. The injections have not occurred while we are in the constructor, and therefor we use the @PostConstruct to initialize fields that are dependent on injection.
Because this is a tutorial, we needed a simple method to populate the database with values as we created the database so we could demonstrate the functionality. The method createSampleData() does just that. But, let's go into the method and understand what is happening:
- We are going to need a transaction, but we have no transaction annotation. That is because @TransactionAttribute( TransactionAttributeType.REQUIRED) is the default, and assumed.
- We create many sub parts, and put them in the list but we only have one persist statement. That is because we defined the sub entities to: cascade = {CascadeType.ALL} which means they will all persist when the parent is persisted.
The next two methods go together. The getOrderEntity method would suffer the same issue as the above findAll() method, but when we return the order we want to be able to access its sub parts. To do that we need a fully initialized order with all its subparts loaded, so when the entity becomes detached, we wont have a LazyInitialization error. We don't want to turn off lazy loading because we don't always want to fully load an order everytime we get one, so we must manually load the entire order. We do this with the loadParts method. It accesses all the sub parts so the ORM will load them and initialize the entire record including its subparts. Another item of note is that we have called getOrderEntity with an actual OrderEntity! But this entity is already detached! So, we have to reattach it to the current persistence context before we can get all the sub-parts. We do this this with the em.merge method. This put the entity into the cache (persistence context) to be managed. It will also make any changes to the database record that have been made. Now, there is not a transaction required with this method, but if there was, because the default transaction type is REQUIRED, the changes would automatically persist at the end of the method. At the end of the method, the entity and all the sub parts are once again, detached. Notice, the entity returned (that is fully loaded) is not the entity that was passed in. Although it is the same record, when we merged the object, we got a new object back relating to the newly managed object.
And then we wrap up the example with findByName and remove methods which allow us to search for a record and delete a record, thus completing our CRUD operations. "Au contraire," you say, we didn't implement an update? Sure we did, it is in the abstract facade as the method edit. The basic CRUD methods are in the abstract facade, and the more dependent CRUD operations are in the entity facade.
The second / third examples of the Stateless Bean are facades as well. Thus rounding out our database connectivity with Stateless Beans and separating the persistence layer.
PartEntityFacade.java
package com.sample;
import java.io.Serializable;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
/**
* @author Thomas Dias
*/
@Stateless
public class PartEntityFacade extends AbstractFacade<PartEntity> implements Serializable {
@PersistenceContext(unitName = "JSFDemoPU")
private EntityManager em;
@Override
public EntityManager getEntityManager() {
return em;
}
public void setEntityManager(EntityManager em) {
this.em = em;
}
public PartEntityFacade() {
super(PartEntity.class);
}
public List<PartEntity> getAvailableParts(Long id) {
Query query;
if (id == null) {
query = em.createQuery("select p from PartEntity p where parentId is null");
} else {
query = em.createQuery("select p from PartEntity p where parentId = ?1");
query.setParameter(1, id);
}
return query.getResultList();
}
private void eagerLoad(PartEntity part) {
if (part == null) {
return;
}
for (PartEntity subPart : part.getSubParts()) {
eagerLoad(subPart);
}
}
public PartEntity findByName(String name) {
PartEntity result;
try {
Query query = em.createQuery("select p from PartEntity p where name=?1");
query.setParameter(1, name);
result = (PartEntity) query.getSingleResult();
eagerLoad(result);
} catch (Exception e) {
result = null;
}
return result;
}
}
OrderPartsEntityFacade.java
package com.sample;
import java.io.Serializable;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
/**
*
* @author Thomas Dias
*/
@Stateless
public class OrderPartsEntityFacade extends AbstractFacade<OrderPartsEntity> implements Serializable {
@PersistenceContext(unitName = "JSFDemoPU")
private EntityManager em;
@Override
protected EntityManager getEntityManager() {
return em;
}
public void setEm(EntityManager em) {
this.em = em;
}
public OrderPartsEntityFacade() {
super(OrderPartsEntity.class);
}
}
With these examples we see we do not implement an interface, and thus they are Interfaceless beans. Other than that, these two examples don't really introduce any new concepts.
So, we have seen our entire persistence layer implemented with stateless beans. The only violations of this were seen through examples to demonstrate the other methods of implementing the data layer. The purpose of EJBs is not solely for implementing the data layer, but because of declarative transactions, it is the appropriate place to implement that layer. We will see more EJBs when we implement Web Services.
One thing to note in this post is the issue of N+1 selects. N+1 selects occur when you have a one-to-many or many-to-many relationship and you retrieve the data one select at a time. For instance, we have a part that has sub parts. We retrieve the part. Then we display the information for each of the sub parts. In a stateful environment, Hibernate would automatically do a select for each part assuming a lazy initialization strategy. Since we displayed a stateless example, we did the exact same thing by accessing each part while the entityManager was still active. This caused the sub parts to be fully initialized. Unfortunately, this also caused N+1 selects because we if part A had 4 subparts, 5 selects occured. In addition, if each subpart had subparts we executed those selects as well. This can lead to a huge hit to the database - imagine if you will looking up a piece of equipment that had 40,000 different parts, each part being individually selected. When planning your selects, take the N+1 issue into account. You wouldn't be able to turn off lazy loading, because you wouldn't want to retrieve all 40,000 parts just the get the assembled piece of equipments name, but you can modify your selects with JOIN. For example, you can use the statement:
Query query = em.createQuery("select p from PartEntity p JOIN FETCH p.subParts where p.name=?1");
This will retrieve the part and the subparts all in one select statement. You need to plan your select statements based on what selects are going to occur for the best performance.
No comments:
Post a Comment