高效地获取多个包

3

我正在开发一款多语言应用程序。因此,许多对象在名称和描述字段中都有所谓的LocalizedStrings集合,而不是普通字符串。每个LocalizedString基本上是一个区域设置和本地化到该区域设置的字符串对。

让我们以一个实体为例,比如一本书 - 对象。

public class Book{

 @OneToMany
 private List<LocalizedString> names;

 @OneToMany
 private List<LocalizedString> description;

 //and so on...
}

当用户请求图书列表时,系统会查询所有书籍,并获取用户选择的语言环境下的每本书的名称和描述,最后向用户展示结果。这种方法虽然可行,但是性能问题很大。使用Hibernate时,一次查询获取了所有书籍,之后遍历每个对象并查询它的本地化字符串,导致了“n+1查询问题”。获取50个实体列表在服务器日志中产生了大约6000行SQL命令。
尝试让集合变成eager,但出现了“不能同时获取多个包”的问题。之后尝试将集合的获取策略设置为子查询,希望一次查询可以获取所有书籍,然后再进行一次查询以获取所有书籍的本地化字符串。但是子查询没有按照期望的方式工作,基本上与第一种情况相同。
目前我已经没有更多的优化方法。简而言之,在获取一个包含一个或多个集合的集合元素时,有哪些获取策略的替代方案?

嗨,Jens,看一下这些链接:http://www.jroller.com/eyallupu/entry/hibernate_exception_simultaneously_fetch_multiple、http://www.jroller.com/eyallupu/entry/solving_simultaneously_fetch_multiple_bags和https://forum.hibernate.org/viewtopic.php?t=974127。希望它们有用。 - Arthur Ronald
2个回答

3

你说过

我尝试将集合的获取策略设置为subselect,希望它能为所有书籍做一次查询

可以这样做,但需要访问某个属性来触发subselect

@Entity
public class Book{

    private List<LocalizedString> nameList = new ArrayList<LocalizedString>();

    @OneToMany(cascade=javax.persistence.CascadeType.ALL)
    @org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
    public List<LocalizedString> getNameList() {
        return this.nameList;
    }

    private List<LocalizedString> descriptionList = new ArrayList<LocalizedString>();

    @OneToMany(cascade=javax.persistence.CascadeType.ALL)
    @org.hibernate.annotations.Fetch(org.hibernate.annotations.FetchMode.SUBSELECT)
    private List<LocalizedString> getDescriptionList() {
        return this.descriptionList;
    }



}

按照以下步骤操作

public class BookRepository implements Repository {

    public List<Book> getAll(BookFetchingStrategy fetchingStrategy) {
        switch(fetchingStrategy) {
            case BOOK_WITH_NAMES_AND_DESCRIPTIONS:
                List<Book> bookList = session.createQuery("from Book").list();

                // Notice empty statement in order to start each subselect
                for (Book book : bookList) {
                    for (Name address: book.getNameList());
                    for (Description description: book.getDescriptionList());
                }

            return bookList;
        }
    }

    public static enum BookFetchingStrategy {
        BOOK_WITH_NAMES_AND_DESCRIPTIONS;
    }

}

我已经完成了以下操作以填充数据库。
SessionFactory sessionFactory = configuration.buildSessionFactory();

Session session = sessionFactory.openSession();
session.beginTransaction();

// Ten books
for (int i = 0; i < 10; i++) {
    Book book = new Book();
    book.setName(RandomStringUtils.random(13, true, false));

    // For each book, Ten names and descriptions
    for (int j = 0; j < 10; j++) {
        Name name = new Name();
        name.setSomething(RandomStringUtils.random(13, true, false));

        Description description = new Description();
        description.setSomething(RandomStringUtils.random(13, true, false));

        book.getNameList().add(name);
        book.getDescriptionList().add(description);
    }

    session.save(book);
}

session.getTransaction().commit();
session.close();

并且可以检索

session = sessionFactory.openSession();
session.beginTransaction();

List<Book> bookList = session.createQuery("from Book").list();

for (Book book : bookList) {
    for (Name address: book.getNameList());
    for (Description description: book.getDescriptionList());
}

session.getTransaction().commit();
session.close();

I see Hibernate:
select
    book0_.id as id0_,
    book0_.name as name0_ 
from
    BOOK book0_

Hibernate:返回100行(如预期)
select
    namelist0_.BOOK_ID as BOOK3_1_,
    namelist0_.id as id1_,
    namelist0_.id as id1_0_,
    namelist0_.something as something1_0_ 
from
    NAME namelist0_ 
where
    namelist0_.BOOK_ID in (
        select
            book0_.id 
        from
            BOOK book0_
    )

Hibernate:返回100行(如预期)。
select
    descriptio0_.BOOK_ID as BOOK3_1_,
    descriptio0_.id as id1_,
    descriptio0_.id as id2_0_,
    descriptio0_.something as something2_0_ 
from
    DESCRIPTION descriptio0_ 
where
    descriptio0_.BOOK_ID in (
        select
            book0_.id 
        from
            BOOK book0_
    )

三个选择语句。没有“n + 1”选择问题。请注意,我使用属性访问策略而不是字段。记住这一点。


你不需要访问某些属性来触发子查询。更准确地说,是因为你没有将获取类型设置为急切,所以在设置这个过程中出现了问题。如果你将获取类型设置为急切并将获取模式设置为子查询,它将自动触发你的子查询。 - Zach Burlingame

1

您可以在您的包上设置一个batch-size,当一个未初始化的集合被初始化时,Hibernate将使用单个查询初始化一些其他集合

更多信息请参见Hibernate文档


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接