If you have missed the first post and want to read how to define a named entity graph or how lazy loading issues were solved with JPA 2.0, check this post: JPA 2.1 Entity Graph - Part 1: Named entity graphs
The example entities
We will use the same example as in the previous post. So you can skip this paragraph if you have read the other one.We will use 3 entities. These are Order, OrderItem and Product. An Order might include multiple OrderItems and each OrderItem belongs to one Product. The FetchType of all these relations it FetchType.LAZY. So the entity manager will not fetch them from the database by default and initialize them with a proxy instead.
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; |
Dynamic entity graph
So lets define a dynamic entity graph. We will do the same as in the first post and define a simple entity graph that tells the entity manager to fetch an Order with all associated OrderItems. Therefore we use the createEntityGraph(ClassIf we would use this entity graph as a fetchgraph, we would need to add all attributes to the list that should be fetched from the database.
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<Order> graph = this.em.createEntityGraph(Order.class); | |
graph.addAttributeNodes("items"); | |
Map<String, Object> hints = new HashMap<String, Object>(); | |
hints.put("javax.persistence.loadgraph", graph); | |
this.em.find(Order.class, orderId, hints); |
OK, dynamically defining which attributes of an entity shall be fetched from the database is nice. But what if we need a graph of entities? Like fetching an Order with all its OrderItems and their Product?
This can be done with a sub graph. A sub graph is basically an entity graph that is embedded into another entity graph or entity sub graph. The definition of a sub graph is similar to the definition of an entity graph. To create and embed the sub graph into an entity graph, we need to call the addSubgraph(String attributeName) method on an EntityGraph object. This will create a sub graph for the attribute with the given name. In the next step, we need to define the list of attributes that shall be fetched with this sub graph.
The following snippet shows the definition of an entity graph with an entity sub graph which tell the entity manager to fetch an Order with its OrderItems and their 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
EntityGraph<Order> graph = this.em.createEntityGraph(Order.class); | |
Subgraph<OrderItem> itemGraph = graph.addSubgraph("items"); | |
itemGraph.addAttributeNodes("product"); | |
Map<String, Object> hints = new HashMap<String, Object>(); | |
hints.put("javax.persistence.loadgraph", graph); | |
return this.em.find(Order.class, orderId, hints); |
What's happening inside?
As in the previous post, we want to have a look at the hibernate log and find out what hibernate is doing. As we can see, the result of a dynamic entity graph is the same as of a named entity graph. It creates a load plan and one select statement with all 3 entities.
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-04-07 20:08:15,260 DEBUG [org.hibernate.loader.plan.build.spi.LoadPlanTreePrinter] (default task-2) LoadPlan(entity=blog.thoughts.on.java.jpa21.entity.graph.model.Order) | |
- Returns | |
- EntityReturnImpl(entity=blog.thoughts.on.java.jpa21.entity.graph.model.Order, querySpaceUid=, path=blog.thoughts.on.java.jpa21.entity.graph.model.Order) | |
- CollectionAttributeFetchImpl(collection=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items, querySpaceUid=, 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=, path=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items.) | |
- QuerySpaces | |
- EntityQuerySpaceImpl(uid=, 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)) : -> | |
- CollectionQuerySpaceImpl(uid=, 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)) : -> | |
- EntityQuerySpaceImpl(uid=, entity=blog.thoughts.on.java.jpa21.entity.graph.model.OrderItem) | |
- SQL table alias mapping - items1_ | |
- alias suffix - 2_ | |
- suffixed key columns - {id1_0_2_} | |
2014-04-07 20:08:15,260 DEBUG [org.hibernate.loader.entity.plan.EntityLoader] (default task-2) 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_ from purchaseOrder order0_ left outer join OrderItem items1_ on order0_.id=items1_.order_id where order0_.id=? |
Conclusion
After defining a named entity graph in the first post, we now used the EntityGraph API to define an dynamic entity graph. Using this entity graph, we can fetch a graph of multiple entities with only one query from the database. This can be used to solve LazyInitializationException and to improve the performance applications.What do you think about (dynamic) entity graphs? From my point of view this is a very useful extension compared to JPA 2.0. Especially the dynamic entity graphs are useful to define your fetch strategy based on runtime information like method parameters.
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
- JPA 2.1 Entity Graph - Part 1: Named entity graphs
I'm really very curious where this whole Annotatiomania™ leads us. Once we declare something like a named entity graph with declarative tools like annotations, I really start wondering why we have stopped writing SQL.
ReplyDeleteI understand that this whole set of features will go through a variety of caches in Hibernate and leverage the whole platform. But the sheer complexity of a project that yields hundreds of such entity graphs makes me think if querying shouldn't better be done with the optimal query language for RDBMS.
Have you already seen these JEE7 things in the wild?
No, I have not seen it in a "real" project so far. But that will change as soon as critical applications switch to JEE7 application servers.
ReplyDeleteHaving hundreds of these annotations might get a little messy ;-)
But it would be similar with predefined SQL statements. You have to put the predefined stuff somewhere in the end.
The only alternative would be to create the statement at runtime, based on the user input. And with JPA 2.1 you could even create a dynamic entity graph for the query ;-)
But creating everything at runtime has also several drawbacks...
Independent of your approach to define a database query, structuring a hughe application is not an easy job. And I don't think, that there is a one size fits all approach. You always need to find the best solution for your current application.
Regards,
Thorben