But this is not without drawbacks. If you have to use an element of the relation, you need to make sure, that the relation gets initialized within the transaction that load the entity from the database. This can be done by using a specific query that reads the entity and the required relations from the database. But this will result in use case specific queries. Another option is to access the relation within your business code which will result in an additional query for each relation. Both approaches are far from perfect.
JPA 2.1 entity graphs are a better solution for it. The definition of an entity graph is independent of the query and defines which attributes to fetch from the database. An entity graph can be used as a fetch or a load graph. If a fetch graph is used, only the attributes specified by the entity graph will be treated as FetchType.EAGER. All other attributes will be lazy. If a load graph is used, all attributes that are not specified by the entity graph will keep their default fetch type.
Lets have a look how to define and use an entity graph.
The example entities
For this example we will use an order with a list of items and each item has a product. All relations are lazy.The Order entity:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Entity | |
@Table(name = "purchaseOrder") | |
@NamedEntityGraph(name = "graph.Order.items", | |
attributeNodes = @NamedAttributeNode(value = "items", subgraph = "items"), | |
subgraphs = @NamedSubgraph(name = "items", attributeNodes = @NamedAttributeNode("product"))) | |
public class Order implements Serializable { | |
@Id | |
@GeneratedValue(strategy = GenerationType.AUTO) | |
@Column(name = "id", updatable = false, nullable = false) | |
private Long id = null; | |
@Version | |
@Column(name = "version") | |
private int version = 0; | |
@Column | |
private String orderNumber; | |
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY) | |
private Set<OrderItem> items = new HashSet<OrderItem>(); | |
... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Entity | |
public class OrderItem implements Serializable | |
{ | |
@Id | |
@GeneratedValue(strategy = GenerationType.AUTO) | |
@Column(name = "id", updatable = false, nullable = false) | |
private Long id = null; | |
@Version | |
@Column(name = "version") | |
private int version = 0; | |
@Column | |
private int quantity; | |
@ManyToOne | |
private Order order; | |
@ManyToOne(fetch = FetchType.LAZY) | |
private Product product; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Entity | |
public class Product implements Serializable | |
{ | |
@Id | |
@GeneratedValue(strategy = GenerationType.AUTO) | |
@Column(name = "id", updatable = false, nullable = false) | |
private Long id = null; | |
@Version | |
@Column(name = "version") | |
private int version = 0; | |
@Column | |
private String name; |
Named entity graph
The definition of a named entity graph is done by the @NamedEntityGraph annotation at the entity. It defines a unique name and a list of attributes (the attributeNodes) that have be loaded.The following example shows the definition of the entity graph "graph.Order.items" which will load the list of OrderItem of an Order.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Entity | |
@Table(name = "purchase_order") | |
@NamedEntityGraph(name = "graph.Order.items", | |
attributeNodes = @NamedAttributeNode("items")) | |
public class Order implements Serializable { | |
... |
Now that we have defined the entity graph, we can use it in a query. Therefore we need to create a Map with query hints and set it as an additional parameter on a find or query method call.
The following code snippet shows how to use a named entity graph as a fetch graph in a find statement.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
EntityGraph graph = this.em.getEntityGraph("graph.Order.items"); | |
Map hints = new HashMap(); | |
hints.put("javax.persistence.fetchgraph", graph); | |
return this.em.find(Order.class, orderId, hints); |
Named sub graph
We used the entity graph to define the fetch operation of the Order entity. If we want to do the same for the OrderItem entity, we can do this with an entity sub graph. The definition of a named sub graph is similar to the definition of an named entity graph and can be referenced as an attributeNode.The following code snippets shows the definition of a sub graph to load the Product of each OrderItem. The defined entity graph will fetch an Order with all OrderItems and their Products.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Entity | |
@Table(name = "purchase_order") | |
@NamedEntityGraph(name = "graph.Order.items", | |
attributeNodes = @NamedAttributeNode(value = "items", subgraph = "items"), | |
subgraphs = @NamedSubgraph(name = "items", attributeNodes = @NamedAttributeNode("product"))) | |
public class Order implements Serializable { |
What's happening inside?
OK, from a development point of view entity graphs are great. They are easy to use and we do not need to write additional code to avoid lazy loading issues. But what is happening inside? How many queries are send to the database? Lets have a look at the hibernate debug log.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2014-03-22 21:56:08,285 DEBUG [org.hibernate.loader.plan.build.spi.LoadPlanTreePrinter] (pool-2-thread-1) LoadPlan(entity=blog.thoughts.on.java.jpa21.entity.graph.model.Order) - Returns - EntityReturnImpl(entity=blog.thoughts.on.java.jpa21.entity.graph.model.Order, querySpaceUid=<gen:0>, path=blog.thoughts.on.java.jpa21.entity.graph.model.Order) - CollectionAttributeFetchImpl(collection=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items, querySpaceUid=<gen:1>, path=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items) - (collection element) CollectionFetchableElementEntityGraph(entity=blog.thoughts.on.java.jpa21.entity.graph.model.OrderItem, querySpaceUid=<gen:2>, path=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items.<elements>) - EntityAttributeFetchImpl(entity=blog.thoughts.on.java.jpa21.entity.graph.model.Product, querySpaceUid=<gen:3>, path=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items.<elements>.product) - QuerySpaces - EntityQuerySpaceImpl(uid=<gen:0>, entity=blog.thoughts.on.java.jpa21.entity.graph.model.Order) - SQL table alias mapping - order0_ - alias suffix - 0_ - suffixed key columns - {id1_2_0_} - JOIN (JoinDefinedByMetadata(items)) : <gen:0> -> <gen:1> - CollectionQuerySpaceImpl(uid=<gen:1>, collection=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items) - SQL table alias mapping - items1_ - alias suffix - 1_ - suffixed key columns - {order_id4_2_1_} - entity-element alias suffix - 2_ - 2_entity-element suffixed key columns - id1_0_2_ - JOIN (JoinDefinedByMetadata(elements)) : <gen:1> -> <gen:2> - EntityQuerySpaceImpl(uid=<gen:2>, entity=blog.thoughts.on.java.jpa21.entity.graph.model.OrderItem) - SQL table alias mapping - items1_ - alias suffix - 2_ - suffixed key columns - {id1_0_2_} - JOIN (JoinDefinedByMetadata(product)) : <gen:2> -> <gen:3> - EntityQuerySpaceImpl(uid=<gen:3>, entity=blog.thoughts.on.java.jpa21.entity.graph.model.Product) - SQL table alias mapping - product2_ - alias suffix - 3_ - suffixed key columns - {id1_1_3_} | |
2014-03-22 21:56:08,285 DEBUG [org.hibernate.loader.entity.plan.EntityLoader] (pool-2-thread-1) Static select for entity blog.thoughts.on.java.jpa21.entity.graph.model.Order [NONE:-1]: select order0_.id as id1_2_0_, order0_.orderNumber as orderNum2_2_0_, order0_.version as version3_2_0_, items1_.order_id as order_id4_2_1_, items1_.id as id1_0_1_, items1_.id as id1_0_2_, items1_.order_id as order_id4_0_2_, items1_.product_id as product_5_0_2_, items1_.quantity as quantity2_0_2_, items1_.version as version3_0_2_, product2_.id as id1_1_3_, product2_.name as name2_1_3_, product2_.version as version3_1_3_ from purchase_order order0_ left outer join OrderItem items1_ on order0_.id=items1_.order_id left outer join Product product2_ on items1_.product_id=product2_.id where order0_.id=? |
The log shows that only one query is created. Hibernate uses the entity graph to create a load plan with all 3 entities (Order, OrderItem and Product) and load them with one query.
Conclusion
We defined an entity graph that tells the entity manager to fetch a graph of 3 related entities from the database (Order, OrderItem and Product). The definition and usage of the entity graph is query independent and results in only one select statement. So the main drawbacks of the JPA 2.0 approaches (mentioned in the beginning) are solved.From my point of view, the new entity graph feature is really great and can be a good way to solve lazy loading issues. What do you think about it? Please leave me a comment.
If you enjoyed reading this article and like to learn more about other Java EE7 features, make sure to subscribe to my RSS feed or follow me on twitter and google+.
And if you want to learn more about the new JPA 2.1 features, have a look at my other articles:
- JPA 2.1 - How to implement a Type Converter
- JPA 2.1 Type Converter - The better way to persist enums
- Criteria Update/Delete - The easy way to implement bulk operations with JPA2.1
No comments:
Post a Comment