Графика на обекта JPA

1. Общ преглед

JPA 2.1 представи функцията Entity Graph като по-сложен метод за справяне с натоварването на производителността.

Той позволява дефиниране на шаблон чрез групиране на свързаните полета за постоянство, които искаме да извлечем, и ни позволява да изберем типа графика по време на изпълнение.

В този урок ще обясним по-подробно как да създадем и използваме тази функция.

2. Какво се опитва да разреши графика на обекта

До JPA 2.0, за да заредим асоциация на обект, обикновено използвахме FetchType. LAZY и FetchType. EAGER като стратегии за извличане . Това инструктира доставчика на JPA да извлече допълнително свързаната асоциация или не. За съжаление тази мета конфигурация е статична и не позволява превключване между тези две стратегии по време на изпълнение.

Тогава основната цел на JPA Entity Graph е да подобри производителността по време на изпълнение при зареждане на свързаните с обекта асоциации и основни полета.

Накратко, доставчикът на JPA зарежда цялата графика в една заявка за избор и след това избягва извличане на асоцииране с повече SELECT заявки. Това се счита за добър подход за подобряване на производителността на приложението.

3. Дефиниране на модела

Преди да започнем да изследваме Entity Graph, трябва да дефинираме моделните обекти, с които работим. Да приемем, че искаме да създадем блог сайт, където потребителите да могат да коментират и споделят публикации.

И така, първо ще имаме потребителски обект:

@Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String email; //... }

Потребителят може да споделя различни публикации, така че ние също се нуждаем от обект на публикация :

@Entity public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String subject; @OneToMany(mappedBy = "post") private List comments = new ArrayList(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn private User user; //... }

Потребителят може също да коментира споделените публикации, така че накрая ще добавим обект на коментар :

@Entity public class Comment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String reply; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn private Post post; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn private User user; //... }

Както виждаме, обектът Post има връзка с обектите Comment и User . Обектът за коментар има асоциация с обектите за публикации и потребители .

След това целта е да се зареди следната графика по различни начини:

Post -> user:User -> comments:List comments[0]:Comment -> user:User comments[1]:Comment -> user:User

4. Зареждане на свързани обекти със стратегии FetchType

Методът FetchType определя две стратегии за извличане на данни от базата данни:

  • FetchType.EAGER : Доставчикът на постоянство трябва да зареди свързаното анотирано поле или свойство. Това е поведението по подразбиране закоментирани полета @Basic, @ManyToOne и @OneToOne .
  • FetchType.LAZY : Доставчикът на постоянство трябва да зарежда данни при първия достъп, но може да бъде зареден с нетърпение. Това е поведението по подразбиране за полетата, коментирани @OneToMany, @ManyToMany и @ ElementCollection .

Например, когато зареждаме обект Post , свързаните обекти Comment не се зареждат като FetchType по подразбиране, тъй като @OneToMany е LAZY. Ние можем да отмени това поведение чрез промяна на FetchType да нетърпелив:

@OneToMany(mappedBy = "post", fetch = FetchType.EAGER) private List comments = new ArrayList();

За сравнение, когато зареждаме обект на коментар , неговият обект родител на Post се зарежда като режим по подразбиране за @ManyToOne, който е EAGER. Също така можем да изберем да не зареждаме обекта Post , като променим тази анотация на LAZY:

@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id") private Post post;

Имайте предвид, че тъй като LAZY не е изискване, доставчикът на постоянство все още може да зареди с нетърпение обекта на Post , ако иска. За да използваме правилно тази стратегия, трябва да се върнем към официалната документация на съответния доставчик на постоянство.

Тъй като ние използвахме анотации, за да опишем нашата стратегия за извличане, нашата дефиниция е статична и няма начин да превключваме между LAZY и EAGER по време на изпълнение .

Тук влиза в игра Entity Graph, както ще видим в следващия раздел.

5. Дефиниране на графика на обект

За да дефинираме графика на обекта, можем да използваме анотациите върху обекта или да продължим програмно, използвайки JPA API.

5.1. Дефиниране на графика на обект с пояснения

Анотацията @ NamedEntityGraph позволява да се посочат атрибутите, които да се включват, когато искаме да заредим обекта и свързаните с него асоциации.

Така че нека първо дефинираме графика на обекта, която зарежда публикацията и свързаните с нея обекти User и Comment :

@NamedEntityGraph( name = "post-entity-graph", attributeNodes = { @NamedAttributeNode("subject"), @NamedAttributeNode("user"), @NamedAttributeNode("comments"), } ) @Entity public class Post { @OneToMany(mappedBy = "post") private List comments = new ArrayList(); //... }

В този пример използвахме @NamedAttributeNode, за да дефинираме свързаните обекти, които да бъдат заредени, когато се зареди основната същност.

Нека сега дефинираме по-сложна графика на обекта, където искаме също да заредим потребителските s, свързани с коментарите .

За тази цел ще използваме атрибута на подграф @NamedAttributeNode . Това позволява препратка към именен подграф, дефиниран чрез анотацията @NamedSubgraph :

@NamedEntityGraph( name = "post-entity-graph-with-comment-users", attributeNodes = { @NamedAttributeNode("subject"), @NamedAttributeNode("user"), @NamedAttributeNode(value = "comments", subgraph = "comments-subgraph"), }, subgraphs = { @NamedSubgraph( name = "comments-subgraph", attributeNodes = { @NamedAttributeNode("user") } ) } ) @Entity public class Post { @OneToMany(mappedBy = "post") private List comments = new ArrayList(); //... }

Дефиницията на анотацията @NamedSubgraph е подобна на @ NamedEntityGraph и позволява да се определят атрибути на свързаната асоциация. По този начин можем да изградим пълна графика.

В примера по-горе, с определено " пост-лице-графиката-с-коментар ползватели графика, ние можем да се зареди Пост, свързаното с потребителя, за Коментари и User -те, свързани с коментар.

Finally, note that we can alternatively add the definition of the Entity Graph using the orm.xml deployment descriptor:

  ...     ... 

5.2. Defining an Entity Graph with the JPA API

We can also define the Entity Graph through the EntityManager API by calling the createEntityGraph() method:

EntityGraph entityGraph = entityManager.createEntityGraph(Post.class);

To specify the attributes of the root entity, we use the addAttributeNodes() method.

entityGraph.addAttributeNodes("subject"); entityGraph.addAttributeNodes("user");

Similarly, to include the attributes from the related entity, we use the addSubgraph() to construct an embedded Entity Graph and then we the addAttributeNodes() as we did above.

entityGraph.addSubgraph("comments") .addAttributeNodes("user");

Now that we have seen how to create the Entity Graph, we'll explore how to use it in the next section.

6. Using the Entity Graph

6.1. Types of Entity Graphs

JPA defines two properties or hints by which the persistence provider can choose in order to load or fetch the Entity Graph at runtime:

  • javax.persistence.fetchgraph – Only the specified attributes are retrieved from the database. As we are using Hibernate in this tutorial, we can note that in contrast to the JPA specs, attributes statically configured as EAGER are also loaded.
  • javax.persistence.loadgraph – In addition to the specified attributes, attributes statically configured as EAGER are also retrieved.

In either case, the primary key and the version if any are always loaded.

6.2. Loading an Entity Graph

We can retrieve the Entity Graph using various ways.

Let's start by using the EntityManager.find() method. As we've already shown, the default mode is based on the static meta-strategies FetchType.EAGER and FetchType.LAZY.

So let's invoke the find() method and inspect the log:

Post post = entityManager.find(Post.class, 1L);

Here is the log provided by Hibernate implementation:

select post0_.id as id1_1_0_, post0_.subject as subject2_1_0_, post0_.user_id as user_id3_1_0_ from Post post0_ where post0_.id=?

As we can see from the log, the User and Comment entities are not loaded.

We can override this default behavior by invoking the overloaded find() method which accepts hints as a Map. We can then provide the graph type which we want to load:

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph"); Map properties = new HashMap(); properties.put("javax.persistence.fetchgraph", entityGraph); Post post = entityManager.find(Post.class, id, properties);

If we look again in the log, we can see that these entities are now loaded and only in one select query:

select post0_.id as id1_1_0_, post0_.subject as subject2_1_0_, post0_.user_id as user_id3_1_0_, comments1_.post_id as post_id3_0_1_, comments1_.id as id1_0_1_, comments1_.id as id1_0_2_, comments1_.post_id as post_id3_0_2_, comments1_.reply as reply2_0_2_, comments1_.user_id as user_id4_0_2_, user2_.id as id1_2_3_, user2_.email as email2_2_3_, user2_.name as name3_2_3_ from Post post0_ left outer join Comment comments1_ on post0_.id=comments1_.post_id left outer join User user2_ on post0_.user_id=user2_.id where post0_.id=?

Let's see how we can achieve the same thing using JPQL:

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-with-comment-users"); Post post = entityManager.createQuery("select p from Post p where p.id = :id", Post.class) .setParameter("id", id) .setHint("javax.persistence.fetchgraph", entityGraph) .getSingleResult();

And finally, let's have a look at a Criteria API example:

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-with-comment-users"); CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Post.class); Root root = criteriaQuery.from(Post.class); criteriaQuery.where(criteriaBuilder.equal(root.get("id"), id)); TypedQuery typedQuery = entityManager.createQuery(criteriaQuery); typedQuery.setHint("javax.persistence.loadgraph", entityGraph); Post post = typedQuery.getSingleResult();

In each of these, the graph type is given as a hint. While in the first example we used the Map, in the two later examples we've used the setHint() method.

7. Conclusion

In this article, we've explored using the JPA Entity Graph to dynamically fetch an Entity and its associations.

The decision is made at runtime in which we choose to load or not the related association.

Ефективността очевидно е ключов фактор, който трябва да се вземе предвид при проектирането на JPA обекти. Документацията на JPA препоръчва използването на стратегията FetchType.LAZY , когато е възможно, и Entity Graph, когато трябва да заредим асоциация.

Както обикновено, целият код е достъпен в GitHub.