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?
- Separation of the concerns of the Model with the View
- Poor performance due to Network Traffic
- 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…
Thanks for reading! If you would like to see what else I’m up to, check out Whiff