EntityGraphs和JOIN FETCH子句提供了一种简单有效的方法来获取实体并初始化其关联。但是,如果尝试将其与使用继承的域模型一起使用,则会很快遇到问题:
您不能在多态查询中使用此方法来获取在子类上定义的关联。换句话说,您的JOIN FETCH子句或EntityGraph需要引用由超类定义的实体属性。否则,Hibernate将抛出异常,因为某些子类的属性未知。
但是,有一个基于Hibernate的一级缓存的简单解决方法,它可以确保Hibernate Session中的每个数据库记录只有一个实体对象。让我们看一个例子,我将向您展示该解决方法的工作原理。
模型:
Publication有两个子类:BlogPost和Book。Publication还聚合关联了Author。
存储时,可使用InheritanceType.SINGLE_TABLE将Publication,Book和BlogPost实体映射到同一数据库表:
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) public abstract class Publication { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) protected Long id; protected String title; @Version private int version; @ManyToOne(fetch = FetchType.LAZY) protected Author author; protected LocalDate publishingDate; ... }
@Entity @DiscriminatorValue("Blog") public class BlogPost extends Publication { private String url; ... }
|
Book实体还定义了与Publisher实体的一对多关联。(疑问:根据类图,应该是BlogPost关联了Publisher实体,可能是类图画错啦)@Entity @DiscriminatorValue("Book") public class Book extends Publication { private int pages; @ManyToOne private Publisher publisher; ... }
|
InheritanceType.SINGLE_TABLE使我们能够定义一个多态一个一对多的关联之间的映射Author 和Publication 。
@Entity public class Author { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE) private Long id; @Version private int version; private String firstName; private String lastName; @OneToMany(mappedBy="author") private Set<Publication> publications = new HashSet<Publication>(); ... }
|
抓取带有BlogPosts, Books 和Publishers的Authors
如何初始化Book和Publisher实体之间的关联?如果您希望在1个查询中执行此操作,那么我会让您感到失望。Hibernate不支持。但是使用以下解决方法,您只需要2个查询。这比没有它所需的n + 1个查询要好得多。
那么它是怎样工作的?就像我说的那样,Hibernate仅在由多态关联的所有实体类定义的属性上支持JOIN FETCH子句或EntityGraphs。因此,您需要一个额外的查询才能将Book和其Publisher一起获得。在下一步中,您需要在处理第二个查询的结果时重用这些对象。
Hibernate的一级缓存
通过使用Hibernate的1级缓存,并保证在Hibernate Session中,数据库记录仅由1个实体对象映射,您可以非常高效地实现这一点。第一个查询获取用例所需的所有Book实体及其Publisher。使用JOIN FETCH子句来初始化Book与Publisher之间的关联。
第一句:
Query q1 = em.createQuery("SELECT b FROM Book b JOIN b.author a JOIN FETCH b.publisher p WHERE a.firstName = :fName");
|
当Hibernate处理此查询的结果时,它将所有Book实体对象添加到其一级缓存中。然后,当它需要处理另一个返回Book实体的查询的结果时,Hibernate首先检查这个Book实体对象是否已经存储在一级缓存中。如果是这样,那就从那里得到它。
这是此替代方法的关键要素。它使您可以在第二个查询中忽略Book和Publisher实体之间的关联。因为Hibernate将从一级缓存中获取所有Book实体对象,所以无论如何都将初始化与Publisher实体的关联。
第二句:Query q2 = em.createQuery("SELECT p FROM Publication p JOIN p.author a WHERE a.firstName = :fName", Publication.class);
|
测试代码:
Query q1 = em.createQuery("SELECT b FROM Book b JOIN b.author a JOIN FETCH b.publisher p WHERE a.firstName = :fName"); q1.setParameter("fName", "Thorben"); List<Book> bs = q1.getResultList(); for (Book b : bs) { log.info(b); } Query q2 = em.createQuery("SELECT p FROM Publication p JOIN p.author a WHERE a.firstName = :fName", Publication.class); q2.setParameter("fName", "Thorben"); List<Publication> ps = q2.getResultList(); for (Publication p : ps) { if (p instanceof BlogPost) { BlogPost blog = (BlogPost) p; log.info("BlogPost - "+blog.getTitle()+" was published at "+blog.getUrl()); } else { Book book = (Book) p; log.info("Book - "+book.getTitle()+" was published by "+book.getPublisher().getName()); log.info(book); } }
|
正如您在日志文件中看到的那样,Hibernate仅执行了两个预期查询。即使第二个查询未初始化Book与Publisher之间的关联,也可以使用延迟获取的关联。如记录的对象引用所示,Hibernate 在两个查询的结果中使用了相同的Book实体对象。
12:18:05,504 DEBUG [org.hibernate.SQL] - select book0_.id as id2_1_0_, publisher2_.id as id1_2_1_, book0_.author_id as author_i8_1_0_, book0_.publishingDate as publishi3_1_0_, book0_.title as title4_1_0_, book0_.version as version5_1_0_, book0_.pages as pages6_1_0_, book0_.publisher_id as publishe9_1_0_, publisher2_.name as name2_2_1_ from Publication book0_ inner join Author author1_ on book0_.author_id=author1_.id inner join Publisher publisher2_ on book0_.publisher_id=publisher2_.id where book0_.DTYPE='Book' and author1_.firstName=? 12:18:05,537 INFO [org.thoughts.on.java.TestJpaInheritance] - org.thoughts.on.java.model.Book@3458eca5 12:18:05,551 DEBUG [org.hibernate.SQL] - select publicatio0_.id as id2_1_, publicatio0_.author_id as author_i8_1_, publicatio0_.publishingDate as publishi3_1_, publicatio0_.title as title4_1_, publicatio0_.version as version5_1_, publicatio0_.pages as pages6_1_, publicatio0_.publisher_id as publishe9_1_, publicatio0_.url as url7_1_, publicatio0_.DTYPE as dtype1_1_ from Publication publicatio0_ inner join Author author1_ on publicatio0_.author_id=author1_.id where author1_.firstName=? 12:18:05,555 INFO [org.thoughts.on.java.TestJpaInheritance] - Book - Hibernate Tips - More than 70 solutions to common Hibernate problems was published by Myself 12:18:05,555 INFO [org.thoughts.on.java.TestJpaInheritance] - org.thoughts.on.java.model.Book@3458eca5 12:18:05,555 INFO [org.thoughts.on.java.TestJpaInheritance] - BlogPost - Best way to fetch an association defined by a subclass was published at https://thorben-janssen.com/fetch-association-of-subclass/
|