問題分析
樓主之前在維護公司之前一個專案時遇到一個坑,就是涉及到一個複雜子查詢形如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在屬性元素和一個巢狀對映元素中都可見。
核心程式碼
好啦,廢話不多說,直接上核心程式碼,以供大家參考和借鑑。
- 實體類
注意,雖然我們查詢出來的檢視沒有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 ......)這類單獨成一個虛擬表的子查詢,所以我們就把這個子查詢查詢出來的虛擬表給它建立一個虛擬檢視的實體對映類,而且不會影響資料庫的真實操作,再讓它隨著資料庫對應的表同步重新整理即可。