JPA 2.1 Entity Graph

转自:http://www.javacodegeeks.com/2014/05/jpa-2-1-entity-graph-part-1-named-entity-graphs.html

         http://www.javacodegeeks.com/2014/05/jpa-2-1-entity-graph-part-2-define-lazyeager-loading-at-runtime.html

Part 1: Named entity graphs

Lazy loading was often an issue with JPA 2.0. You have to define at the entity if you want to use FetchType.LAZY (default) or FetchType.EAGER to load the relation and this mode is always used. FetchType.EAGER is only used if we want to always load the relation. FetchType.LAZY is used in almost all of the cases to get a well performing and scalable application.

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:

01@Entity
02@Table(name = "purchaseOrder")
03@NamedEntityGraph(name = "graph.Order.items",
04               attributeNodes = @NamedAttributeNode(value = "items", subgraph = "items"),
05               subgraphs = @NamedSubgraph(name = "items", attributeNodes = @NamedAttributeNode("product")))
06public class Order implements Serializable {
07 
08   @Id
09   @GeneratedValue(strategy = GenerationType.AUTO)
10   @Column(name = "id", updatable = false, nullable = false)
11   private Long id = null;
12   @Version
13   @Column(name = "version")
14   private int version = 0;
15 
16   @Column
17   private String orderNumber;
18 
19   @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
20   private Set<OrderItem> items = new HashSet<OrderItem>();
21 
22   ...

The OrderItem entity:

01@Entity
02public class OrderItem implements Serializable
03{
04 
05   @Id
06   @GeneratedValue(strategy = GenerationType.AUTO)
07   @Column(name = "id", updatable = false, nullable = false)
08   private Long id = null;
09   @Version
10   @Column(name = "version")
11   private int version = 0;
12 
13   @Column
14   private int quantity;
15 
16   @ManyToOne
17   private Order order;
18 
19   @ManyToOne(fetch = FetchType.LAZY)
20   private Product product;

The Product entity:

01@Entity
02public class Product implements Serializable
03{
04 
05   @Id
06   @GeneratedValue(strategy = GenerationType.AUTO)
07   @Column(name = "id", updatable = false, nullable = false)
08   private Long id = null;
09   @Version
10   @Column(name = "version")
11   private int version = 0;
12 
13   @Column
14   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.

1@Entity
2@Table(name = "purchase_order")
3@NamedEntityGraph(name = "graph.Order.items",
4      attributeNodes = @NamedAttributeNode("items"))
5public class Order implements Serializable {
6 
7   ...

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.

1EntityGraph graph = this.em.getEntityGraph("graph.Order.items");
2 
3Map hints = new HashMap();
4hints.put("javax.persistence.fetchgraph", graph);
5 
6return 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.

1@Entity
2@Table(name = "purchase_order")
3@NamedEntityGraph(name = "graph.Order.items",
4               attributeNodes = @NamedAttributeNode(value = "items", subgraph = "items"),
5               subgraphs = @NamedSubgraph(name = "items", attributeNodes = @NamedAttributeNode("product")))
6public 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.

12014-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_}
2 
32014-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?

Part 2: Define lazy/eager loading at runtime

This is my second post on JPA 2.1 Entity Graphs. The first post described the usage of named entity graphs. These can be used to define a graph of entities and/or attributes at compile time that shall be fetched with a find or query method. Dynamic entity graphs do to the same but in a dynamic way. This means you can use the EntityGraph API to define your entity graph at runtime.

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:

01@Entity
02@Table(name = "purchaseOrder")
03@NamedEntityGraph(name = "graph.Order.items",
04               attributeNodes = @NamedAttributeNode(value = "items", subgraph = "items"),
05               subgraphs = @NamedSubgraph(name = "items", attributeNodes = @NamedAttributeNode("product")))
06public class Order implements Serializable {
07 
08   @Id
09   @GeneratedValue(strategy = GenerationType.AUTO)
10   @Column(name = "id", updatable = false, nullable = false)
11   private Long id = null;
12   @Version
13   @Column(name = "version")
14   private int version = 0;
15 
16   @Column
17   private String orderNumber;
18 
19   @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
20   private Set<OrderItem> items = new HashSet<OrderItem>();
21 
22   ...

The OrderItem entity:

01@Entity
02public class OrderItem implements Serializable
03{
04 
05   @Id
06   @GeneratedValue(strategy = GenerationType.AUTO)
07   @Column(name = "id", updatable = false, nullable = false)
08   private Long id = null;
09   @Version
10   @Column(name = "version")
11   private int version = 0;
12 
13   @Column
14   private int quantity;
15 
16   @ManyToOne
17   private Order order;
18 
19   @ManyToOne(fetch = FetchType.LAZY)
20   private Product product;

The Product entity:

01@Entity
02public class Product implements Serializable
03{
04 
05   @Id
06   @GeneratedValue(strategy = GenerationType.AUTO)
07   @Column(name = "id", updatable = false, nullable = false)
08   private Long id = null;
09   @Version
10   @Column(name = "version")
11   private int version = 0;
12 
13   @Column
14   private String name;

Dynamic entity graph

So let’s 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(ClassrootType) method of the entity manager to create an entity graph for the Order entity. In the next step, we create a list of all attributes of the Order entity that shall be fetched with this entity graph. We only need to add the attribute items, because we will use this entity graph as a loadgraph and all other attributes are eager by default.

If 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.

1EntityGraph<Order> graph = this.em.createEntityGraph(Order.class);
2graph.addAttributeNodes("items");
3 
4Map<String, Object> hints = new HashMap<String, Object>();
5hints.put("javax.persistence.loadgraph", graph);
6 
7this.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.

1EntityGraph<Order> graph = this.em.createEntityGraph(Order.class);
2Subgraph<OrderItem> itemGraph = graph.addSubgraph("items");
3itemGraph.addAttributeNodes("product");
4 
5Map<String, Object> hints = new HashMap<String, Object>();
6hints.put("javax.persistence.loadgraph", graph);
7 
8return 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.

012014-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)
02    - Returns
03       - EntityReturnImpl(entity=blog.thoughts.on.java.jpa21.entity.graph.model.Order, querySpaceUid=, path=blog.thoughts.on.java.jpa21.entity.graph.model.Order)
04          - CollectionAttributeFetchImpl(collection=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items, querySpaceUid=, path=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items)
05             - (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.)
06    - QuerySpaces
07       - EntityQuerySpaceImpl(uid=, entity=blog.thoughts.on.java.jpa21.entity.graph.model.Order)
08          - SQL table alias mapping - order0_
09          - alias suffix - 0_
10          - suffixed key columns - {id1_2_0_}
11          - JOIN (JoinDefinedByMetadata(items)) :  ->
12             - CollectionQuerySpaceImpl(uid=, collection=blog.thoughts.on.java.jpa21.entity.graph.model.Order.items)
13                - SQL table alias mapping - items1_
14                - alias suffix - 1_
15                - suffixed key columns - {order_id4_2_1_}
16                - entity-element alias suffix - 2_
17                - 2_entity-element suffixed key columns - id1_0_2_
18                - JOIN (JoinDefinedByMetadata(elements)) :  ->
19                   - EntityQuerySpaceImpl(uid=, entity=blog.thoughts.on.java.jpa21.entity.graph.model.OrderItem)
20                      - SQL table alias mapping - items1_
21                      - alias suffix - 2_
22                      - suffixed key columns - {id1_0_2_}
23 
242014-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.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值