Performance Trap using JPA: Transfer Object pattern and JPQL to the Rescue!


DISCLAIMER:  I am a Regular Average Java Programmer; an anti-expert.  I like to do things right, but I’m generally satisfied with doing things that WORK.  I’m sure there’s some JE (Java Expert) out there who knows how to do it right and you can buy his book and worship his rightness at the Java Rock Star Hall of Fame.

Can I say that I love JAX-WS with JPA with Netbeans?  This is a golden combination folks.  Netbeans supports the hell out of these things.  But there are some traps.  One of which is the potential for very poor performance.  But I will show you how to avoid this with a simple design pattern:   Transfer Object.

Transfer Object is so crucial when using JPA.  I realize that we should all know this, but Netbeans does not generate Transfer Objects for you like they do Controllers and Entities.  You have to do it yourself.  And you probably should have to.  After all, how can Netbeans anticipate the specific data transfer needs of your crappy application?

Let’s break it down!

What is a Transfer Object?

Here’s a pretty comprehensive technical breakdown from java.sun.com  (he he…sun)

http://java.sun.com/blueprints/patterns/TransferObject.html

Now, let’s just put it into RAJP terms.  A Transfer Object (or Value Object) is just the attributes needed for a particular use case all wrapped up together in one object.

What Problems does it solve with JPA?

  1. Separation of the concerns of the Model with the View
  2. Poor performance due to Network Traffic
  3. Poor performance due to High Latency

In Java Persistence API (JPA), your basic representation of a row in the database is called an Entity.  An Entity is a special kind of Java Bean that knows a lot about how to map a database table to an object.  So it’s not just instance variables, getters, and setters.  It’s a powerful little guy that you don’t want just any tier of your enterprise to have access to.    It’s also an expensive little guy.  In fact, depending on how it relates to other entities it might not be so little at all.  One entity that is joined with other entities might pull in a whole universe of data. And worse, it might even be like a House of Mirrors; creating an infinite graph of data.  AHHHHHHH!!!  Let me out of here!!!!

For example, a Car might have a list of Dealers and a Dealer might have a list of Cars each of which has a list of Dealers, and so on!  Fortunately, JPA implementations do not infinitely load this stuff.  The problem only arises when you need to expose Entities to another tier or another application (say, with web services).

[Who am I kidding?  I don’t REALLY have tiers!!  Everything I do is in the web tier!  But I do have services!]

How do I do it?  “Is it magic?” you ask.  NO!!! It’s JAVA!  Nothing magical about it!  What do you think this is?  Ruby?

So, the idea is that you create these Transfer Objects with only the attributes that are required for the particular use case.    This separates the concerns of the Model from the View and minimizes network traffic, but it doesn’t necessarily solve the latency issue.  If your JPA calls are still retrieving and mapping gobs and gobs of data before you perform your transfer, then you may want to consider coding a more efficient JPQL query (or a stored procedure) and map the results directly into your Transfer Object.

Let’s look at how Netbeans builds entities and the inherent problems when it come to the way they handle web services.

Here’s an example of an Entity generated from a table in a relational database:


1
/*

 2  * To change this template, choose Tools | Templates
 3  * and open the template in the editor.
 4  */
 5
 6 package sample.persistence;
 7
 8 import java.io.Serializable;
 9 import java.math.BigDecimal;
 10 import java.util.List;
 11 import javax.persistence.Basic;
 12 import javax.persistence.CascadeType;
 13 import javax.persistence.Column;
 14 import javax.persistence.Entity;
 15 import javax.persistence.Id;
 16 import javax.persistence.JoinColumn;
 17 import javax.persistence.ManyToOne;
 18 import javax.persistence.NamedQueries;
 19 import javax.persistence.NamedQuery;
 20 import javax.persistence.OneToMany;
 21 import javax.persistence.Table;
 22
 23 /**
 24  *
 25  * @author david ctr wilson-bur
 26  */
 27 @Entity
 28 @Table(name = "PRODUCT", catalog = "", schema = "APP")
 29 @NamedQueries({
 30     @NamedQuery(name = "Product.findAll", query = "SELECT p FROM Product p"),
 31     @NamedQuery(name = "Product.findByProductId", query = "SELECT p FROM Product p WHERE p.productId = :productId"),
 32     @NamedQuery(name = "Product.findByPurchaseCost", query = "SELECT p FROM Product p WHERE p.purchaseCost = :purchaseCost"),
 33     @NamedQuery(name = "Product.findByQuantityOnHand", query = "SELECT p FROM Product p WHERE p.quantityOnHand = :quantityOnHand"),
 34     @NamedQuery(name = "Product.findByMarkup", query = "SELECT p FROM Product p WHERE p.markup = :markup"),
 35     @NamedQuery(name = "Product.findByAvailable", query = "SELECT p FROM Product p WHERE p.available = :available"),
 36     @NamedQuery(name = "Product.findByDescription", query = "SELECT p FROM Product p WHERE p.description = :description")})
 37 public class Product implements Serializable {
 38     private static final long serialVersionUID = 1L;
 39     @Id
 40     @Basic(optional = false)
 41     @Column(name = "PRODUCT_ID")
 42     private Integer productId;
 43     @Column(name = "PURCHASE_COST")
 44     private BigDecimal purchaseCost;
 45     @Column(name = "QUANTITY_ON_HAND")
 46     private Integer quantityOnHand;
 47     @Column(name = "MARKUP")
 48     private BigDecimal markup;
 49     @Column(name = "AVAILABLE")
 50     private String available;
 51     @Column(name = "DESCRIPTION")
 52     private String description;
 53     @JoinColumn(name = "PRODUCT_CODE", referencedColumnName = "PROD_CODE")
 54     @ManyToOne(optional = false)
 55     private ProductCode productCode;
 56     @JoinColumn(name = "MANUFACTURER_ID", referencedColumnName = "MANUFACTURER_ID")
 57     @ManyToOne(optional = false)
 58     private Manufacturer manufacturer;
 59     @OneToMany(cascade = CascadeType.ALL, mappedBy = "product")
 60     private List<PurchaseOrder> purchaseOrderList;
 61
 62     public Product() {
 63     }
 64
 65     public Product(Integer productId) {
 66         this.productId = productId;
 67     }
 68
 69     public Integer getProductId() {
 70         return productId;
 71     }
 72
 73     public void setProductId(Integer productId) {
 74         this.productId = productId;
 75     }
 76
 77     public BigDecimal getPurchaseCost() {
 78         return purchaseCost;
 79     }
 80
 81     public void setPurchaseCost(BigDecimal purchaseCost) {
 82         this.purchaseCost = purchaseCost;
 83     }
 84
 85     public Integer getQuantityOnHand() {
 86         return quantityOnHand;
 87     }
 88
 89     public void setQuantityOnHand(Integer quantityOnHand) {
 90         this.quantityOnHand = quantityOnHand;
 91     }
 92
 93     public BigDecimal getMarkup() {
 94         return markup;
 95     }
 96
 97     public void setMarkup(BigDecimal markup) {
 98         this.markup = markup;
 99     }
100
101     public String getAvailable() {
102         return available;
103     }
104
105     public void setAvailable(String available) {
106         this.available = available;
107     }
108
109     public String getDescription() {
110         return description;
111     }
112
113     public void setDescription(String description) {
114         this.description = description;
115     }
116
117     public ProductCode getProductCode() {
118         return productCode;
119     }
120
121     public void setProductCode(ProductCode productCode) {
122         this.productCode = productCode;
123     }
124
125     public Manufacturer getManufacturer() {
126         return manufacturer;
127     }
128
129     public void setManufacturer(Manufacturer manufacturer) {
130         this.manufacturer = manufacturer;
131     }
132
133     public List<PurchaseOrder> getPurchaseOrderList() {
134         return purchaseOrderList;
135     }
136
137     public void setPurchaseOrderList(List<PurchaseOrder> purchaseOrderList) {
138         this.purchaseOrderList = purchaseOrderList;
139     }
140
141     @Override
142     public int hashCode() {
143         int hash = 0;
144         hash += (productId != null ? productId.hashCode() : 0);
145         return hash;
146     }
147
148     @Override
149     public boolean equals(Object object) {
150         // TODO: Warning - this method won't work in the case the id fields are not set
151         if (!(object instanceof Product)) {
152             return false;
153         }
154         Product other = (Product) object;
155         if ((this.productId == null && other.productId != null) || (this.productId != null && !this.productId.equals(other.productId))) {
156             return false;
157         }
158         return true;
159     }
160
161     @Override
162     public String toString() {
163         return "sample.persistence.Product[productId=" + productId + "]";
164     }
165
166 }
167
168

Note that there are several joins with other entities.  Well each of those entities join back to Product!  This is helpful stuff, but there is the trap!  Do not try to return this entity from a web service and do not try to replicate these relationships in your Transfer Object!  And DON’T CROSS THE STREAMS!!!!!!

Here’s what will happen:

com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML

OH SNAP!!!  Whoa!!!  A bottomless pit of XML.  That sounds scary.  And I bet if you DID reach the bottom it would not be a soft landing.  XML is hard and scratchy!!!

Imagine with me a simple use case where a web application needs to show a Product catalog.  All you really need is Product Id (for retrieval purposes), Description, Cost,  and the name of the Manufacturer.  You might not want your web app to expose the Markup, the Quantity on Hand or other details.  So you design ProductTO (TO for TransferObject)  with only the fields needed for the catalog listing.


 1 /*
 2  * To change this template, choose Tools | Templates
 3  * and open the template in the editor.
 4  */
 5
 6 package sample.persistence;
 7
 8 import java.math.BigDecimal;
 9
10 /**
11  *
12  * @author david ctr wilson-bur
13  */
14 public class ProductTO {
15     private Integer productId;
16     private String description;
17     private BigDecimal purchaseCost;
18
19     public String getDescription() {
20         return description;
21     }
22
23     public void setDescription(String description) {
24         this.description = description;
25     }
26
27     public String getManufacturerName() {
28         return manufacturerName;
29     }
30
31     public void setManufacturerName(String manufacturerName) {
32         this.manufacturerName = manufacturerName;
33     }
34
35     public Integer getProductId() {
36         return productId;
37     }
38
39     public void setProductId(Integer productId) {
40         this.productId = productId;
41     }
42
43     public BigDecimal getPurchaseCost() {
44         return purchaseCost;
45     }
46
47     public void setPurchaseCost(BigDecimal purchaseCost) {
48         this.purchaseCost = purchaseCost;
49     }
50     private String manufacturerName;
51
52
53 }
54
55

One last convenience is to make a transfer method in your Entity class.

166     public ProductTO transfer(){
167         ProductTO to = new ProductTO();
168         to.setProductId(productId);
169         to.setDescription(description);
170         to.setPurchaseCost(purchaseCost);
171         to.setManufacturerName(this.manufacturer.getName());
172         return to;
173     }

The TransferObject solves the first two problems, but leaves the issue of latency.  When you retrieve a Product list, you are also retrieving all the entities joined to it.  Even if you configured the entity with lazy loading, you would still load Manufacturer entity when you try to access the manufacturer name in your transfer method.  Imagine THAT scenario compounded with multiple joins and large datasets.

Get the picture?

This is where some Java Persistence Query Language (JPQL) comes in handy.

SELECT p.productId, p.description, p.purchaseCost, p.manufacturer.name FROM Product p

Map this result set to your ProductTO and you solve your problems.


258
public List<ProductTO> findProductCatalog() {

259         EntityManager em = getEntityManager();
260         List<ProductTO> productTOs = new ArrayList<ProductTO>();
261         try {
262             Query q = em.createQuery("SELECT p.productId, p.description, p.purchaseCost, p.manufacturer.name FROM Product p");
263             List<Object[]> products = (List<Object[]>)q.getResultList();
264             for (Object[] objects : products) {
265                 ProductTO to = new ProductTO();
266                 to.setProductId((Integer) objects[0]);
267                 to.setDescription((String) objects[1]);
268                 to.setPurchaseCost((BigDecimal) objects[2]);
269                 to.setManufacturerName((String) objects[3]);
270                 productTOs.add(to);
271             }
272             return productTOs;
273         }
274         finally {
275             em.close();
276         }
277     }

Yes, I know this kind of defeats the purpose of automated ORM (Object Relational Mapping), but…

RAJP – [raj-pee] Just one of the thousands of regular, average java programmers trying to get their job done…

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s