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 ……)子查询的方法

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Hibernate的检索方式:(查询/加载) 1. 通过OID加载 session.get(Users.class, 1); * 2. 通过HQL/SQL 检索 hibernate query language (面向对象的查询语言) * a) 不再操纵表,它操纵的是持久化类的对象 b) 面向对象的 3. QBC ( query by criteria ) 更加面向对象 4. QBE ( query by Example ) 5. SQL Hibernate的检索策略: 1. 延迟检索(加载)映射文件改变lazy a) Lazy的取值: i. Many-to-one 1. false 2. proxy 3. no-proxy ii. set 一对多 1. true   2. false   3. extra 根据对set容器的不同,可以产生高效的sql访问数据库 2. 批量检索:batch-size=3 a) 可以使用批量检索: b) 在内存,如果有多个set(代理)容器需要初始化, 则当访问任何一个代理set容器时,一次初始化n个set容器,减少sql语句; c) 产生的语句是:select * from ….. where FK in (?,?,?... …n); 3. 迫切(fetch)左外连接检索: a) 在映射文件设置:fetch(抓取) i. Select 什么都不做 ii. Subselect 当访问内存的一个代理容器时,一次将所有的set容器用一个子查询,全部初始化 iii. Join 1. 对OID检索方式有用 2. 对hql检索方式没有用 b) 在hql要使用迫切左外连接时,必须加 left join fetch 对象.关系属性 i. 如果不加fetch关键字,则hibernate不会抓取关系属性,但会遍历关系属性所对应的表 ii. 不加fetch关键字时,select 要指定返回的对象,否则它要返回数组 iii. 条件:持久化类之间有关系属性映射 Hibernate级联操作对象的关系属性: 映射文件设置: 1. Cascade a) none b) Save-update 当使用session的api对当前对象进行save,update操作时,对它的关系属性也进行save或者update c) Delete d) All = delete + save-update e) Delete-orphan 只是删除父子关系的子对象 f) All-delete-orphan 父子关系的表设为此值,表示,当删除一个父对象时,将这个set容器的子对象全部删除 2. Inverse 只能在set映射时使用;它的作用是:是否根据set容器存放的对象,产生update语句,维护关系(子)表的外键属性;取值为true时,将不产生update语句。当inverse设为true时,通过父亲增儿子时,必须建立双向关系. Session的一级缓存:(相当于一系列的map容器,它是需要维护的) 1. 提高效率 2. 维护缓存的对象和数据库对应表的记录之间进行同步 3. 当一级缓存的对象状态(属性)发展生改变时,session在特定的时刻清理缓存: a) 清理缓存的时间点: i. Transaction.commit(); 事务提交时 ii. Session.flush(); 4. 管理session一级缓存的方法 a) Session.evict(obj)从session的一级缓存移出一个对象 b) Session.clear() 将一级缓存的所有对象全部清空 c) Session.close() 关闭一个session 对象的状态和对象的生命周期: 持久化类的对象,在hibernate应用可以处于三种状态(根据对象和session之间的关系进行划分): 1. 临时态,瞬态:特点: a) 在数据库没有记录和它对应 b) 和session没有任何关系 c) New 出来的对象,都处于临时态 2. 持久态:特点: a) 处于session的一级缓存 b) 数据库有一条记录和它对应 c) Session会在特定的时刻(清理缓存时)维护这个对象和数据库的记录进行同步 d) 在同一个session的缓存,具有相同OID的持久态对象,只有一个(同一个session的一缓存,不可能同时有两个OID相同的同一个持久化类的对象存在) 3. 游离态,脱管 a) 不在一级缓存之 b) 数据库可能有记录和它对应,也可能没有记录和它对应 c) 从一个持久态对象转化过来的,从session的一级缓存出来的,因为调用了session的一些方法,产生了这种对象(session.close()) SessionFctory: Hibernate映射一对多关系: public class Dept implements java.io.Serializable { // Fields private Integer deptid; private String deptname; private Integer deptnum; private Integer actNum; private Date cdate; private Set emps = new HashSet(0) ; //getter/setter方法 略… } 映射文件: <hibernate-mapping> <class name="org.wllt.www.po.Dept " table="dept " catalog="hibernate"> <id name="deptid" type="java.lang.Integer" > <column name="deptid" /> <generator class="native" /> </id> <property name="deptname" type="java.lang.String"> <column name="deptname" length="20" /> </property> <property name="deptnum" type="java.lang.Integer"> <column name="deptnum" /> </property> <property name="actNum" type="java.lang.Integer"> <column name="actNum" /> </property> <property name="cdate" type="java.util.Date"> <column name="cdate" length="19" /> </property> <set name="emps" inverse ="true" lazy="true" cascade="none" batch-size="2" fetch="join" > <key> <column name="deptid" /> </key> <one-to-many class="org.wllt.www.po.Emp" /> </set> </class> </hibernate-mapping> Hibernate映射多对一: public class Emp implements java.io.Serializable { private Integer empid; private Dept dept; private String empname; //getter/setter方法略 } <hibernate-mapping> <class name="org.wllt.www.po.Emp" table="emp" catalog="hibernate"> <id name="empid" type="integer"> <column name="empid" /> <generator class="native" /> </id> <many-to-one name="dept" class="org.wllt.www.po.Dept" fetch="select" cascade ="none" lazy ="proxy"> <column name="deptid" /> </many-to-one> <property name="empname" type="string"> <column name="empname" length="30" /> </property> </class> </hibernate-mapping> Hibernate映射一对一(通过主键实现一对一的关系) //主PO对象(一个员工对个应一个身份证) public class Employees implements Serializable{ private Integer empid; private String name; private String sex; private Date birthday; private Double salary; //关系属性: private Idcard idcard; } <hibernate-mapping package ="org.wllt.www.po"> <class name="Employees" table="emp"> <id name="empid" type="java.lang.Integer"> <generator class="increment"/> </id> … 属性映射略 <one-to-one name="idcard" class="Idcard" fetch="select" lazy="proxy" cascade ="save-update"> </one-to-one> </class> </hibernate-mapping> //子PO对象 public class Idcard { private Integer empid; private String cardno; private String addr; private String fzjg; private Date enddate; //关系属性 private Employees emp; } <hibernate-mapping package="org.wllt.www.po3"> <class name="Idcard"> <id name="empid" type="java.lang.Integer"> <generator class="foreign "> <param name="property ">emp</param> </generator> </id> <one-to-one name="emp" class="Employees" fetch="join" constrained="true" cascade="save-update" > </one-to-one> <property name="cardno" type="java.lang.String" /> <property name="addr" type="java.lang.String" /> <property name="fzjg" type="java.lang.String" /> <property name="enddate" type="java.util.Date"/> </class> </hibernate-mapping> Hibernate映射一对一(通过外键实现一对一的关系) 子表引用主表的主键做外键,这个外键建立了unique约束、not-null约束 //主对象: public class Dept implements java.io.Serializable { private Integer deptid; private String deptname; private Integer deptnum; private Integer actNum; private Date cdate; //关系属性 private Phonenote phonenote; } <hibernate-mapping> <class name="org.wllt.www.po.Dept" table="dept" catalog="hibernate"> <id name="deptid" type="java.lang.Integer"> <column name="deptid" /> <generator class="native" /> </id> <-- …property 映射略 --> <one-to-one name="phonenote" class="org.wllt.www.po.Phonenote" property-ref="dept" cascade ="all" fetch="join" > </one-to-one> </class> </hibernate-mapping> 子对象 public class Phonenote implements java.io.Serializable { // Fields private Integer phonid; private Dept dept; private String phonecode; } <hibernate-mapping> <class name="org.wllt.www.po.Phonenote" table="phonenote" catalog="hibernate"> <id name="phonid" type="java.lang.Integer"> <column name="phonid" /> <generator class="native" /> </id> <many-to-one name="dept" class="org.wllt.www.po.Dept" fetch="select" cascade ="all"> <column name="deptid" unique="true" /> </many-to-one> <property name="phonecode" type="java.lang.String"> <column name="phonecode" length="20" not-null="true" /> </property> </class> </hibernate-mapping> Hibernate映射多对多(两个一对多实现多对多) 表:材料表和产品表多对多,在数据库间表即产品材料表用来存放两个表之间的关系 Java类:材料PO,产品PO,间PO,间PO的复合主键类(由于是两个一对多形成的多对多,所以,这里只讲一个一对多,另一个是相同的映射方法) //产品类: public class Product { private Integer cpbh; private String cpmc; private String gg; private String sh; private String nbxh; private String txm; private String bzfs; private Set proMals = new HashSet(0); } <hibernate-mapping package="org.wllt.www.po"> <class name="Product" table="prod"> <id name="cpbh" type="java.lang.Integer"> <column name="pro_id"></column> <generator class="increment"/> </id> <-- property 映射略 --> <set name="proMals" inverse="true" cascade="all-delete-orphan"> <key> <column name="pro_id"></column> </key> <one-to-many class="ProMal"/> </set> </class> </hibernate-mapping> 间PO public class ProMal { private ProMalId id; } 复合主键类: public class ProMalId implements Serializable{ private Product product; private Clzb clzb; } <hibernate-mapping package="org.wllt.www.po"> <class name="ProMal" table="pro_mat"> <composite-id name="id" class ="ProMalId"> <key-many-to-one name ="product" class ="Product" lazy="proxy" column="pro_id" > </key-many-to-one> <key-many-to-one name="clzb" class="Clzb"column="mat_id"> </key-many-to-one> </composite-id> </class> </hibernate-mapping> Hibernate映射多对多(两个PO类直接实现多对多) 表:材料表和产品表多对多,在数据库间表即产品材料表用来存放两个表之间的关系 Java类:材料PO,产品PO,这两个PO分别包含一个set容器, 相互放对方的对象,没有间PO, 程序员因为得不到间PO对象,所以不能直接操作数据库的间表。间表的记录的维护工作,只能交给hibernate来处理,这时,set容器的inverse属性必须设为false 主PO: public class Users implements java.io.Serializable { private Integer uerid; private String name; private String passwd; private Set roles = new HashSet(0); } <hibernate-mapping> <class name="org.wllt.www.po2.Users" table="users" atalog="hibernate"> <id name="uerid" type="java.lang.Integer"> <column name="UERID" /> <generator class="increment" /> </id> <-- property的映射略 --> <set name="roles" inverse ="false" table ="USERANDROLE"> <key> <column name="UERID" not-null="true" /> </key> <many-to-many class="org.wllt.www.po2.Roles " column ="ROLESID"/> </set> </class> </hibernate-mapping>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值