繁体   English   中英

获取集合子集的策略

[英]Strategy to fetch a subset of a collection

我有一个场景,我的应用程序可以访问有限时间窗口的会话,在此期间它必须从数据库中获取数据到内存中,然后只使用内存中的数据来处理请求。

数据模型是一个简单的一对多关联,例如:

<class name="com.foo.Road" table="road">
    <id name="oid" column="oid"/>

    <map name="carCountMap" fetch="subselect">
        <key column="road_oid" foreign-key="oid"/>
        <index column="time_oid" type="long" />
        <one-to-many class="com.foo.CarCount" />
    </map>

    <map name="truckCountMap" fetch="subselect">
        <key column="road_oid" foreign-key="oid"/>
        <index column="time_oid" type="long" />
        <one-to-many class="com.foo.TruckCount" />
    </map>
</class>

现在假设汽车和卡车计数数据存在了几年,这远远超过了内存。 此外,我真的只对过去3个月加载车数非常感兴趣。

我的问题是,使用hibernate加载这些数据的最佳方法是:

  • road.getCarCountMap()仅返回过去3个月中车辆计数的集合(可能为空)
  • 我最终得到的是一些需要很长时间才能处理的疯狂笛卡尔产品
  • 关闭会话后,不会抛出LazyInitializationException

我尝试过的一些事情是:

1.使carCountMap集合急切并在映射上指定where属性,例如:

<map name="carCountMap" fetch="subselect" lazy="false" where="time_oid > 1000"> (类似于truckCountMap

这最符合我想要的集合语义,但不幸的是它迫使我硬编码一个值,所以我不能真正参考过去3个月。 time_oid增加1。

2.将map定义为lazy并使用hql查询手动连接3个表:

    from Road r
    left outer join fetch r.carCountMap ccm
    left outer join fetch r.truckCoutnMap tcm
    where (ccm.time.oid > :startDate)
      or (tcm.time.oid > :startDate)

这样的问题是结果查询返回数百万行,而它应该是10k道路*每月4次测量(每周)* 3个月= ~120k。 这个查询在大约一个小时内完成,这很荒谬,因为方法#1(在我关注的情况下加载完全相同的数据)在3分钟内完成。

3.将地图定义为延迟并首先使用条件加载道路,然后运行其他查询以填充集合

    List roadList = session.createCriteria(Road.class).list();

    session.getNamedQuery("fetchCcm").setLong("startDate", startDate).list();
    session.getNamedQuery("fetchTcm").setLong("startDate", startDate).list();

    return roadList;

这会触发正确的查询,但检索到的汽车和卡车计数不会附加到roadListRoad对象。 所以当我尝试访问任何Road对象的计数时,我得到一个LazyInitializationException

4.将地图定义为惰性,使用criteria.list()加载所有道路,迭代过去3个月内的所有测量日期,以强制加载这些值。

我还没有尝试过,因为它听起来很笨重,我不相信它会摆脱LazyInitializationException

  • 我用这些方法遇到的问题是否有任何变通方法?
  • 有没有更好的方法?

在挖掘了一些之后,看起来hibernate过滤器是我需要的确切解决方案。

它们基本上提供了一个构造,在集合或类上具有where属性,并在运行时绑定参数。

在映射文件中,定义过滤器并将其附加到集合:

<class name="com.foo.Road" table="road">
    <id name="oid" column="oid"/>

    <map name="carCountMap" fetch="subselect">
        <key column="road_oid" foreign-key="oid"/>
        <index column="time_oid" type="long" />
        <one-to-many class="com.foo.CarCount" />
        <filter name="byStartDate" condition="time_oid > :startDate" />
    </map>

    <map name="truckCountMap" fetch="subselect">
        <key column="road_oid" foreign-key="oid"/>
        <index column="time_oid" type="long" />
        <one-to-many class="com.foo.TruckCount" />
        <filter name="byStartDate" condition="time_oid > :startDate" />
    </map>
</class>

<filter-def name="byStartDate">
    <filter-param name="startDate" type="long"/>
</filter-def>

然后在dao中,启用过滤器,绑定参数并运行查询:

session.enableFilter("byStartDate").setParameter("startDate", calculatedStartDateOid);
return session.createCriteria(Road.class).list();

我认为你的问题实际上由两部分组成:

  • 如何在域模型中表达数据子集
  • 如何获取具有所需性能级别的数据

关于第一部分,我认为当您尝试将数据子集加载到Road字段时,您正在滥用域模型。

也许最好在Road和交通测量之间建立单向关系,即从Road类中删除这些地图。 它看起来很合理,因为您可能不会立即需要所有这些数据。 然后,您可以创建一个由Road和这些交通地图组成的DTO(未映射!) RoadStatistics ,并使用您想要的任何数据填充它。

关于问题的第二部分是什么,我认为您需要使用纯SQL进行一些实验以优化查询,然后将最佳查询转换为HQL或Criteria。 如果您的域模型不限制加载数据的方式,则可以轻松完成此转换(请参阅第一部分)。 也许您需要通过创建一些索引等来优化数据库模式。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM