如何在Hibernate中优化获取多个集合



我正在尝试使用Hibernate来提高应用程序的性能,因为Hibernate对数据库执行了太多SQL调用。我认为数据提取可以组合在一起,以减少调用并提高性能,但我在这里有点不知所措。我已经查看了Hibernate文档中关于子选择和批量获取的内容,这确实有帮助,但我认为它并没有完全消除这个问题。

在下面的例子中,我需要获得部队士兵列表的详细信息,并将其显示在网页上。

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public List<Soldier> getSoldiers() {
   ...
}

在没有太多SQL语句的情况下,很容易将获取策略设置为子选择、批处理或急于检索该部队的所有士兵。

@Entity
public class Soldier {
    @Id
    String soldierId
    String firstName;
    String lastName;
    @OneToMany(mappedBy="address")
    public List<Soldier> getAddress() {
     ...
    @OneToMany(mappedBy="combatHistory")
    public List<Soldier> getCombatHistory() {
      ...
    @OneToMany(mappedBy="medicalHistory")
    public List<Soldier> getMedicalHistory() {
      ...
}

每个士兵实体与其他延迟加载的实体具有多个一对多关系。我需要初始化这些集合并从中检索值。如果一个士兵有3个一对多关系,而一个部队有1000个士兵,则会产生3 x 1000个SQL调用!

有没有办法通过减少通话次数来优化这一点?既然我已经知道我需要检索的士兵ID,我可以检索实体并在一级缓存中提供它们吗?

例如,我可以查询

from Address as a where a.soldierId in (...)
from CombatHistory as a where a.soldierId in (...)
from MedicalHistory as a where a.soldierId in (...)

如果Address、CombatHistory等实体可以缓存,那么当在每个士兵中访问集合时,将不会执行SQL select。这将把每个集合的呼叫次数减少到一次(在这种情况下为3次),而不是每个士兵每个集合一次(3 x 1000)

我在文档中没有看到太多关于解决这个问题的内容,所以我们非常感谢任何提示和想法。请记住,由于这些集合是Lists而不是Sets,因此不能在多个集合上执行左联接提取,否则Hibernate将返回异常。

HibernateException: cannot simultaneously fetch multiple bags 

您也可以在集合上使用注释@Fetch(FetchMode.SUBSELECT)如果"触摸"该类型的一个集合,那么所有集合都将在一个SQL请求中获取。

@Entity
public class Country implements java.io.Serializable {
    private long id;
    private int version;
    private String country;
    private Set<City> cities = new HashSet<City>(0);
    @Fetch(FetchMode.SUBSELECT)
    @OneToMany(mappedBy = "country", cascade = CascadeType.ALL)
    public Set<City> getCities() {
        return cities;
    }

    ...
}

以下是如何使用它的示例:

    public List<Country> selectCountrySubSelect() {
        List<Country> list = getSession().createQuery("select c from Country c").list();
        // You don't have to initialize every collections
        // for (Country country : list) {
        // Hibernate.initialize(country.getCities());
        // }
        // but just "touch" one, and all will be initialized
        Hibernate.initialize(((Country) list.get(0)).getCities());
        return list;
    }

日志:

DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections():  - 2 collections were found in result set for role: business.hb.Country.cities
DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection():  - Collection fully initialized: [business.hb.Country.cities#1]
DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollection():  - Collection fully initialized: [business.hb.Country.cities#2]
DEBUG org.hibernate.engine.loading.internal.CollectionLoadContext.endLoadingCollections():  - 2 collections initialized for role: business.hb.Country.cities
DEBUG org.hibernate.engine.internal.StatefulPersistenceContext.initializeNonLazyCollections():  - Initializing non-lazy collections

即使获取集合的所有项,hibernate也不会确保集合的所有项目都已加载,并且不会填充集合。然而,由于士兵在会话缓存中,您可以一次加载一个集合。

from Soldier s left join fetch s.Address where s.soldierId in (...)
from Soldier s left join fetch s.CombatHistory where s.soldierId in (...)
from Soldier s left join fetch s.MedicalHistory where s.soldierId in (...)

这将把往返次数减少到3次。

最新更新