Hibernate 如何自動生成 SQL 語句
1. 前言
本節課和大家一起聊聊 Hibernate 是如何自動生成 SQL 語句的。通過本節課程的學習,你將了解到:
- 反射在框架中的重要性;
- 元數據描述對 Hibernate 的重要性。
2. 理想狀態
Hibernate 是全自動的 JDBC 框架,能自動構建 SQL 語句、能自動封裝數據。
做為開發者,不能在使用的便利性中迷失自己,應該要學會多思考:Hibernate 是如何自動構建 SQL 語句的?
答案本身很簡單:使用反射機制。
先來一個最理想化的構建實例:假設實體類名和表名相同、實體類中的屬性和表中的字段命名相同。
編寫自己的 Session 類:
public class MySession<T> {
public T get(Class clz,Serializable id) {
String sql=createSql(clz,id);
//其它操作……
return null;
}
private String createSql(Class clz, Serializable id) {
return null;
}
}
get()方法接受 2 個參數,這 2 個參數便是構建 SQL 的核心。傳遞給內部的 createSql()方法用來進行 SQL 語句構造。
關注 createSql() 方法中的代碼:
- 聲明變量;
// SQL 查詢模板
String sql = "select {0} from {1} where {2}= {3}";
// 表名
String tableName = null;
// 字段列表
StringBuffer selFields = new StringBuffer();
// 主鍵字段
String keyField = null;
- 看來就是要為 SQL 查詢模板中的占位符找到具體值。因為類名和表名相同,所以表名很容易找到;
// 類名就是表名
tableName = clz.getSimpleName();
- 因為屬性名與表中的字段名相同,所以表的字段信息也很容易找到;
// 屬性名就是查詢的字段名,找到屬性等于找到字段信息
Field[] fields = clz.getDeclaredFields();
- 這里有一個較麻煩的地方,怎么找到主鍵字段,這里假設第一個屬性對應的是主鍵字段;
Field[] fields = clz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (i == 0)
keyField = fields[i].getName();
selFields.append(fields[i].getName()).append(",");
}
// 刪除最后一個,
selFields.deleteCharAt(selFields.length() - 1);
- 最后構建 SQL ;
sql = MessageFormat.format(sql, new Object[] {selFields,tableName,keyField,id});
測試輸出大家自己去完成。
3. 非理想狀態
前面假設了一種特別理想的狀態。但是,現實總比理想殘酷。
很多情況下,表名與類名、屬性名和字段名都不同名,主鍵字段對應的屬性也不一定放在第一個。
這種情形下,又如何構建 SQL 。此時,注解就起到了作用。大家還記得常用的注解嗎?
- @Table;
- @Id;
- @Column。
有了這 3 個注解,查找表名、字段信息、主鍵字段就不需要再靠強制性的代碼規范了。
重構 createSql() 方法中的代碼。
本質上沒有發生改變,還是為 SQL 查詢模板中的占位符找到所有具體值。
- 找到表名。其本思路是,如果有 @Table 注解,表名就是注解提供的名字,如果沒有注解,則表名與類名相同;
// 查找類上面是否有 @Table 注解
Table tableAnnotaion= (Table) clz.getAnnotation(Table.class);
if(tableAnnotaion==null)
//則認為類名與表名相同
tableName = clz.getSimpleName();
else
//表名為注解中提供的值
tableName=tableAnnotaion.name();
- 找到表的所有字段信息。為了簡化代碼,假設 @Id 或 @Column 注解直接標注在屬性上面。即使標注在 get()方法上面也不難;
// 屬性信息
Field[] fields = clz.getDeclaredFields();
//是否存在 @Id 注解
boolean ishasIdAnnotation = false;
Id idAnnotation=null;
Column columnAnnotation = null;
String fieldName = null;
for (Field field : fields) {
// @Id 注解
idAnnotation = field.getAnnotation(Id.class);
// @ Column 注解
columnAnnotation = field.getAnnotation(Column.class);
if (idAnnotation != null) {
keyField = field.getName();
ishasIdAnnotation = true;
}
if (columnAnnotation == null)
// 有 @Column 注解則從注解中取值
fieldName = field.getName();
else
//沒有 @Column 注解則和屬性表相同
fieldName = columnAnnotation.name();
selFields.append(fieldName).append(",");
}
if (!ishasIdAnnotation) {
throw new Exception("@Id 注解是必須的!");
}
// 刪除最后一個,
selFields.deleteCharAt(selFields.length() - 1);
- 構建 SQL 語句。
sql = MessageFormat.format(sql, new Object[] { selFields, tableName, keyField, id });
測試 createSql()方法,在控制臺可看到通過反射自動構建的 SQL 語句:
select stuId,stuName,stuSex,stuPassword from student where stuId= 1
測試結果需要以你自己的實體類和表做參考。
4. 小結
本課程給出了 2 種情形下構建 SQL 語句的實現。
一種對編碼規范要求非常嚴格,因為編碼規范有很多人為因素,很難保證類結構和表結構如同鏡像,不出現差異性。顯然,在這種嚴格的編碼規范下,構建 SQL 的性能消耗是最低的,所以,一入職場,第一堂課就是培訓編碼規范性。
第二種情形應該是一種常態,所以需要使用注解的方式標識差異性,當然,反射時付出的性能代價會增加。
本課程沒有討論構建多表查詢的實現,有了這些基礎,相信都將不會很難。