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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章