Hibernate中不支持复杂子查询from (select ……)解决方案

问题分析

楼主之前在维护公司之前一个项目时遇到一个坑,就是涉及到一个复杂子查询形如from(select......)形式的hql语句不支持,简单说就是先要通过子查询查询出来一张新的虚拟表,然后和其他表做关联才能得到业务所需要的最终数据。
原SQL语句如下:

SELECT k.term_id,
        sum(k.work_time) worktime
FROM 
    (SELECT o.term_id,
        o.report_date,
        o.work_time,
         o.term_brand,
        o.model_name
    FROM rep_hardware_fault_rate o
    GROUP BY  o.term_id,o.report_date,
    o.work_time, o.term_brand,o.model_name) k, view_device_dept_info v
WHERE k.term_id=v.term_id
GROUP BY  k.term_brand;

我在网上查了大量资料,发现有一些求助的帖子中有类似的问题描述,但是都没有相应的解决方案。后面楼主想了下要不就简化SQL语句然后再代码中处理(这种效率很低,最笨的方法),或者在数据库中新建一个视图,但这种处理方法也不是十分完美,就这一块业务用到了,会增加数据库的开销,而且假如说有很多类似的业务,那不是得建很多张视图,这种办法可持续性也不好。后面楼主还是没放弃,就觉得应该有其他人也遇到过类似的问题,肯定有比较完美的解决方案~终于功夫不有心人,楼主参考大量的博客和资料终于找到了一种比较完美的解决方案,即建立虚拟视图法。

具体解决方案

简单说就是将select子查询到的虚拟表建立一个实体类映射成一个虚拟视图,然后再进行关联查询操作。这里要用到一个@Subselect注解,即
subselect (可选): 它将一个不可变(immutable)并且只读的实体映射到一个数据库的子查询中。当你想用视图代替一张基本表的时候,这是有用的,但最好不要这样做。
对Hibernate映射来说视图和表是没有区别的,这是因为它们在数据层都是透明的( 注意:一些数据库不支持视图属性,特别是更新的时候)。有时你想使用视图,但却不能在数据库中创建它(例如:在遗留的schema中)。这样的话,你可以映射一个不可变的(immutable)并且是只读的实体到一个给定的SQL子查询表达式:定义这个实体用到的表为同步(synchronize),确保自动刷新(auto-flush)正确执行, 并且依赖原实体的查询不会返回过期数据。subselect在属性元素和一个嵌套映射元素中都可见。

核心代码

好啦,废话不多说,直接上核心代码,以供大家参考和借鉴。

  1. 实体类
    注意,虽然我们查询出来的视图没有id,但是这里必须加主键,否则hql无法正常映射,应该是必须遵从的规范。
    这里的@Subselect注解是查询数据库的表数据结果,将其映射为一个实体类;@Synchronize是定义这个实体用到的表为同步(synchronize),确保自动刷新(auto-flush)正确执行。
@Entity
@Subselect(" select o.TERM_ID,o.REPORT_DATE,o.WORK_TIME,o.TERM_BRAND,o.MODEL_NAME " +
           " from REP_HARDWARE_FAULT_RATE o  " +
           " group by o.TERM_ID,o.REPORT_DATE,o.WORK_TIME,o.TERM_BRAND,o.MODEL_NAME ")
/**
 *如果子查询涉及2个表,则这样写
 *@Synchronize( { "test_item", "test_bid" })
 */
@Synchronize({"REP_HARDWARE_FAULT_RATE"})

public class ViewDeviceForWorkTime {
    
    /**
     * 主键Id
     * 这里必须写,不写会报错,hql映射必须要加
     */
    @Id
    @GeneratedValue(generator = "system-uuid")
    @GenericGenerator(name = "system-uuid", strategy = "uuid")
    private String id;

    /**
     * 设备Id
     * 可以加Column,也可以不加,后台配置了驼峰映射法
     */
    @Column(name = "TERM_ID")
    private String termId;

    /**
     * 记录日期
     */
    private String reportDate;

    /**
     * 应工作时间
     */
    private String workTime;

    /**
     * 设备品牌
     */
    private String termBrand;

    /**
     * 设备型号
     */
    private String modelName;



    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getTermId() {
        return termId;
    }

    public void setTermId(String termId) {
        this.termId = termId;
    }

    public String getReportDate() {
        return reportDate;
    }

    public void setReportDate(String reportDate) {
        this.reportDate = reportDate;
    }

    public String getWorkTime() {
        return workTime;
    }

    public void setWorkTime(String workTime) {
        this.workTime = workTime;
    }

    public String getTermBrand() {
        return termBrand;
    }

    public void setTermBrand(String termBrand) {
        this.termBrand = termBrand;
    }

    public String getModelName() {
        return modelName;
    }

    public void setModelName(String modelName) {
        this.modelName = modelName;
    }
}

映射数据库中的表view_device_dept_info。

@Entity
@Table(name = "VIEW_DEVICE_DEPT_INFO")
public class ViewDeviceDeptInfoForOpenRate {
    @Id
    private String deviceId;
    private String termId;
    private String termSeq;
    private String counterCode;
    private String termAddr;
    private String typeId;
    private String brandId;
    private String modelId;
    private String termIp;
    private String areaAddr;
    private String status;
    private String companyId;
    private String companyName;
    private String deptId;
    private String deptCode;
    private String deptName;
    private Integer deptLevel;
    private String deptAddr;
    private String deptId1;
    private String deptName1;
    private String deptId2;
    private String deptName2;
    private String deptId3;
    private String deptName3;
    private String deptId4;
    private String deptName4;
    private String deptId5;
    private String deptName5;
    private String deptId6;
    private String deptName6;

    public String getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(String deviceId) {
        this.deviceId = deviceId;
    }

   ......这里省略后面的get,set方法

}

2.业务处理
这里和大家的写法可能有所差别,这里只贴出楼主实际的业务逻辑,供大家参考,只要大家理解这个思路就好了。

//查询应工作时间
 StringBuffer wql = new StringBuffer();
            wql.append(" select o.termBrand,sum(o.workTime) as workTime ");
            wql.append(" from ViewDeviceForWorkTime o,ViewDeviceDeptInfoForOpenRate v ");
            wql.append(" where o.termId = v.termId ");
            //这里是设置查询的参数,省略
            wql.append(paramsSql);
            wql.append(" group by o.termBrand ");
  // 设置查询的参数
  Query queryWorkTime = createQuery(wql.toString());
            for (int i = 0; i < queryObj.length; i++) {
                if (!"".equals(queryObj[i])) {
                    queryWorkTime.setParameter(i, queryObj[i]);   
                }
            }
 Object[] list = queryWorkTime .list().toArray();

小结

这里我们就很好的解决了hql的这类子查询问题,总的来说就是hql不直接支持类似from(select ......)这类单独成一个虚拟表的子查询,所以我们就把这个子查询查询出来的虚拟表给它建立一个虚拟视图的实体映射类,而且不会影响数据库的真实操作,再让它随着数据库对应的表同步刷新即可。

参考博客

Hibernate中子查询(subselect)的使用

hibernate使用from (select ……)子查询的方法

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容

  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,268评论 0 9
  • Arleta Pech是公认的优秀静物画家,且是享有国际声誉的顶级水彩艺术家。她出版过很多艺术作品和教程。Arle...
    AmeliaL阅读 632评论 2 10
  • 遮风挡雨、无所不能,这棵大树便是我们的父亲。 “总是向你索取,却不曾说谢谢你。直到长大以后,才懂得你不容易....
    萤火虫菇娘阅读 460评论 0 2
  • 10年前的一天早晨,母亲告诉我隔壁的王大叔娶回来了一个哑巴,哑巴的爸妈送哑巴过来,看到光棍多年的王大叔家里一贫如洗...
    夕雁无边阅读 432评论 0 1