【问题标题】:Hibernate Union alternativesHibernate Union 替代方案
【发布时间】:2010-09-17 02:13:45
【问题描述】:

使用休眠实现联合查询有哪些替代方法?我知道hibernate目前不支持联合查询,现在我看到的唯一方法是使用视图表。

另一种选择是使用普通的 jdbc,但这样我会失去所有示例/条件查询的好处,以及 hibernate 对表/列执行的休眠映射验证。

【问题讨论】:

  • 其中一种解决方案是使用聚合实体,如here 所述。

标签: java sql hibernate union


【解决方案1】:

使用视图。可以使用实体名称将相同的类映射到不同的表/视图,因此您甚至不会有太多重复。在那里,做到这一点,工作正常。

普通 JDBC 有另一个隐藏的问题:它不知道 Hibernate 会话缓存,因此如果某些内容被缓存到事务结束并且没有从 Hibernate 会话中刷新,则 JDBC 查询将找不到它。有时可能会非常令人费解。

【讨论】:

  • 更令人费解的是,当您拥有二级缓存时,却使用来自应用程序其他部分的普通 JDBC(例如用于报告)。
【解决方案2】:

我必须同意弗拉基米尔的观点。我也研究过在 HQL 中使用 UNION 并且找不到解决方法。奇怪的是,我可以发现(在 Hibernate 常见问题解答中)UNION 不受支持,与 UNION 有关的错误报告标记为“已修复”,人们的新闻组说这些语句将在 UNION 被截断,以及其他新闻组的人报告它有效美好的... 经过一天的折腾,我最终将我的 HQL 移植回纯 SQL,但在数据库中的视图中执行此操作将是一个不错的选择。在我的例子中,部分查询是动态生成的,所以我不得不在代码中构建 SQL。

【讨论】:

  • 感谢您列举您发现的各种理论,哪些理论不正确。 “合理的开发人员”可能期望的内容与与之不同的事物之间的差异是一些最有用的注释。
【解决方案3】:

视图是一种更好的方法,但由于 hql 通常返回一个列表或集合...您可以使用 list_1.addAll(list_2)。与工会相比完全糟糕,但应该可以工作。

【讨论】:

  • 返回不同的类型怎么办?所以一个类型 A 列表和第二个类型 B 列表但具有相同的属性(如用于实时数据的表 A 和用于历史数据的表 B)
【解决方案4】:

我也经历过这种痛苦 - 如果查询是动态生成的(例如 Hibernate Criteria),那么我找不到实用的方法来做到这一点。

对我来说,好消息是我只是在调查联合以解决在 Oracle 数据库中使用“或”时的性能问题。

Patrick 发布的解决方案(使用集合以编程方式组合结果)虽然丑陋(特别是因为我也想做结果分页)对我来说已经足够了。

【讨论】:

    【解决方案5】:

    也许我有一个更直接的问题要解决。我的“例如”在 JPA 中,Hibernate 作为 JPA 提供者。

    我将三个选择(在第二种情况下是两个)拆分为多个选择,并组合了我自己返回的集合,有效地替换了“联合全部”。

    【讨论】:

      【解决方案6】:

      你可以使用id in (select id from ...) or id in (select id from ...)

      例如而不是不工作

      from Person p where p.name="Joe"
      union
      from Person p join p.children c where c.name="Joe"
      

      你可以的

      from Person p 
        where p.id in (select p1.id from Person p1 where p1.name="Joe") 
          or p.id in (select p2.id from Person p2 join p2.children c where c.name="Joe");
      

      不过,至少在使用 MySQL 之后,您会遇到性能问题。有时,对两个查询进行穷人联接会更容易:

      // use set for uniqueness
      Set<Person> people = new HashSet<Person>((List<Person>) query1.list());
      people.addAll((List<Person>) query2.list());
      return new ArrayList<Person>(people);
      

      执行两个简单的查询通常比执行一个复杂的查询要好。

      编辑:

      举个例子,这里是来自子选择解决方案的 MySQL 查询的 EXPLAIN 输出:

      mysql> explain 
        select p.* from PERSON p 
          where p.id in (select p1.id from PERSON p1 where p1.name = "Joe") 
            or p.id in (select p2.id from PERSON p2 
              join CHILDREN c on p2.id = c.parent where c.name="Joe") \G
      *************************** 1. row ***************************
                 id: 1
        select_type: PRIMARY
              table: a
               type: ALL
      possible_keys: NULL
                key: NULL
            key_len: NULL
                ref: NULL
               rows: 247554
              Extra: Using where
      *************************** 2. row ***************************
                 id: 3
        select_type: DEPENDENT SUBQUERY
              table: NULL
               type: NULL
      possible_keys: NULL
                key: NULL
            key_len: NULL
                ref: NULL
               rows: NULL
              Extra: Impossible WHERE noticed after reading const tables
      *************************** 3. row ***************************
                 id: 2
        select_type: DEPENDENT SUBQUERY
              table: a1
               type: unique_subquery
      possible_keys: PRIMARY,name,sortname
                key: PRIMARY
            key_len: 4
                ref: func
               rows: 1
              Extra: Using where
      3 rows in set (0.00 sec)
      

      最重要的是,1. row 不使用任何索引并考虑 200k+ 行。坏的!此查询的执行耗时 0.7 秒,其中两个子查询都以毫秒为单位。

      【讨论】:

      • 如果你想要分页,你不能使用两个子查询,不是吗?
      • +1 来自我。虽然执行两个简单查询通常比执行一个复杂查询要好,但执行使用适当索引的单个查询通常更好。
      【解决方案7】:

      我有一个针对 HQL 中的 union 的关键场景(为此我费了很大力气)的解决方案。

      例如而不是不工作:-

      select i , j from A a  , (select i , j from B union select i , j from C) d where a.i = d.i 
      

      或

      select i , j from A a  JOIN (select i , j from B union select i , j from C) d on a.i = d.i 
      

      你可以在 Hibernate HQL 中做 ->

      Query q1 =session.createQuery(select i , j from A a JOIN B b on a.i = b.i)
      List l1 = q1.list();
      
      Query q2 = session.createQuery(select i , j from A a JOIN C b on a.i = b.i)
      List l2 = q2.list();
      

      然后你可以添加两个列表 ->

      l1.addAll(l2);
      

      【讨论】:

      • 联合是在进程中,不是数据库做的,和沃尔特建议的一样。
      • 那么,如果你想在这里排序呢?您最终可能会通过实现另一个合并算法来复制排序机制。非常不好
      【解决方案8】:



      正如 Patrick 所说,从每个 SELECT 中附加 LIST 是个好主意,但请记住它的作用类似于 UNION ALL。为避免这种副作用,只需控制对象是否已添加到最终集合中。如果没有,那么添加它。
      您应该关心的其他一点是,如果您在每个 SELECT 中有任何 JOIN,则结果将是一个对象数组列表(@987654321 @) 所以你必须迭代它以只保留你需要的对象。

      希望它有效。

      【讨论】:

        【解决方案9】:

        这是一个特殊情况,但可能会激发您创建自己的工作。这里的目标是计算两个不同表中记录满足特定条件的记录总数。我相信这种技术适用于您需要跨多个表/源聚合数据的任何情况。

        我设置了一些特殊的中间类,因此调用命名查询的代码简短而实用,但是您可以使用通常与命名查询结合使用的任何方法来执行查询。

        QueryParms parms=new QueryParms();
        parms.put("PROCDATE",PROCDATE);
        
        Long pixelAll = ((SourceCount)Fetch.row("PIXEL_ALL",parms,logger)).getCOUNT();
        

        正如您在此处看到的,命名查询开始看起来非常像联合语句:

        @Entity
        @NamedQueries({
                @NamedQuery(
                    name  ="PIXEL_ALL",
                    query = "" +
                            "  SELECT new SourceCount(" +
                            "     (select count(a) from PIXEL_LOG_CURR1 a " +
                            "       where to_char(a.TIMESTAMP, 'YYYYMMDD') = :PROCDATE " +
                            "     )," +
                            "     (select count(b) from PIXEL_LOG_CURR2 b" +
                            "       where to_char(b.TIMESTAMP, 'YYYYMMDD') = :PROCDATE " +
                            "     )" +
                            ") from Dual1" +
                            ""
            )
        })
        
        public class SourceCount {
            @Id
            private Long   COUNT;
        
            public SourceCount(Long COUNT1, Long COUNT2) {
                this.COUNT = COUNT1+COUNT2;
            }
        
            public Long getCOUNT() {
                return COUNT;
            }
        
            public void setCOUNT(Long COUNT) {
                this.COUNT = COUNT;
            }
        }
        

        这里的部分魔法是创建一个虚拟表并在其中插入一条记录。在我的例子中,我将它命名为 dual1,因为我的数据库是 Oracle,但我认为你称之为虚拟表并不重要。

        @Entity
        @Table(name="DUAL1")
        public class Dual1 {
            @Id
            Long ID;
        }
        

        别忘了插入你的虚拟记录:

        SQL> insert into dual1 values (1);
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-12-31
          • 2012-11-06
          • 1970-01-01
          • 2012-05-02
          • 2013-09-20
          • 2013-02-08
          • 2020-08-26
          相关资源
          最近更新 更多