問題分析
樓主之前在維護公司之前一個項目時遇到一個坑,就是涉及到一個復雜子查詢形如from(select......)形式的hql語句不支持枝恋,簡單說就是先要通過子查詢查詢出來一張新的虛擬表殖氏,然后和其他表做關聯(lián)才能得到業(yè)務所需要的最終數(shù)據(jù)。
原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;
我在網(wǎng)上查了大量資料,發(fā)現(xiàn)有一些求助的帖子中有類似的問題描述茵乱,但是都沒有相應的解決方案。后面樓主想了下要不就簡化SQL語句然后再代碼中處理(這種效率很低搜变,最笨的方法)采缚,或者在數(shù)據(jù)庫中新建一個視圖,但這種處理方法也不是十分完美挠他,就這一塊業(yè)務用到了扳抽,會增加數(shù)據(jù)庫的開銷,而且假如說有很多類似的業(yè)務殖侵,那不是得建很多張視圖贸呢,這種辦法可持續(xù)性也不好。后面樓主還是沒放棄拢军,就覺得應該有其他人也遇到過類似的問題楞陷,肯定有比較完美的解決方案~終于功夫不有心人,樓主參考大量的博客和資料終于找到了一種比較完美的解決方案茉唉,即建立虛擬視圖法固蛾。
具體解決方案
簡單說就是將select子查詢到的虛擬表建立一個實體類映射成一個虛擬視圖,然后再進行關聯(lián)查詢操作度陆。這里要用到一個@Subselect注解艾凯,即
subselect (可選): 它將一個不可變(immutable)并且只讀的實體映射到一個數(shù)據(jù)庫的子查詢中。當你想用視圖代替一張基本表的時候坚芜,這是有用的览芳,但最好不要這樣做。
對Hibernate映射來說視圖和表是沒有區(qū)別的鸿竖,這是因為它們在數(shù)據(jù)層都是透明的( 注意:一些數(shù)據(jù)庫不支持視圖屬性沧竟,特別是更新的時候)。有時你想使用視圖缚忧,但卻不能在數(shù)據(jù)庫中創(chuàng)建它(例如:在遺留的schema中)悟泵。這樣的話,你可以映射一個不可變的(immutable)并且是只讀的實體到一個給定的SQL子查詢表達式:定義這個實體用到的表為同步(synchronize)闪水,確保自動刷新(auto-flush)正確執(zhí)行糕非, 并且依賴原實體的查詢不會返回過期數(shù)據(jù)。subselect在屬性元素和一個嵌套映射元素中都可見球榆。
核心代碼
好啦朽肥,廢話不多說,直接上核心代碼持钉,以供大家參考和借鑒衡招。
- 實體類
注意,雖然我們查詢出來的視圖沒有id每强,但是這里必須加主鍵始腾,否則hql無法正常映射州刽,應該是必須遵從的規(guī)范。
這里的@Subselect注解是查詢數(shù)據(jù)庫的表數(shù)據(jù)結果浪箭,將其映射為一個實體類穗椅;@Synchronize是定義這個實體用到的表為同步(synchronize),確保自動刷新(auto-flush)正確執(zhí)行奶栖。
@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;
}
}
映射數(shù)據(jù)庫中的表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.業(yè)務處理
這里和大家的寫法可能有所差別,這里只貼出樓主實際的業(yè)務邏輯敏簿,供大家參考明也,只要大家理解這個思路就好了。
//查詢應工作時間
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 ");
//這里是設置查詢的參數(shù)惯裕,省略
wql.append(paramsSql);
wql.append(" group by o.termBrand ");
// 設置查詢的參數(shù)
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 ......)這類單獨成一個虛擬表的子查詢,所以我們就把這個子查詢查詢出來的虛擬表給它建立一個虛擬視圖的實體映射類蜻势,而且不會影響數(shù)據(jù)庫的真實操作撑刺,再讓它隨著數(shù)據(jù)庫對應的表同步刷新即可。