Hibernate 自動進行數據封裝
1. 前言
Hibernate 可以構建各種復雜的 SQL 語句,但其本質都是反射機制結合映射關系完成的。
框架也僅是一款程序產品,人為編寫的產物。要相信,只要你愿意,你完全可以實現自己的 JDBC 框架。
本節課和大家繼續聊聊 Hibernate 是如何自動封裝數據的。
2. 理想狀態
程序中的數據通過 SQL 語句傳遞給數據庫,從 IO 流的角度上理解是數據的輸出。
把數據庫中的數據封裝成程序能識別、使用的類型,這個過程叫數據庫封裝。從 IO 流的角度上講是數據的輸入。
數據封裝的實現還是利用反射機制。
數據封裝的思路也簡單。
表結構對應類結構,一行記錄或說一個實體對應一個對象,字段對應屬性。還是同樣的配方。
即將編寫實例之前,先做一個理想化的假設:
- 表名和類名相同;
- 表中的字段名和類中的屬性名相同;
- 實體類中沒有特別復雜的數據類型。
理想狀態設定便于大家由淺入深理解實現過程。
真實項目中,很難達到理想化狀態 。因為 JAVA 程序和關系型數據庫的命名規范有差異性,不管誰遷就誰,都會讓彼此難受。
在上一節課的自定義 Session 對象中添加一個封裝方法:
public class MySession<T> {
public T get(Class<T> clz, Serializable id) throws Exception {
String sql = createSql_(clz, id);
// JDBC 常規操作…… 得到結果集
ResultSet rs=this.getRs(sql);
//數據封裝
T entity= this.wrap(clz, rs);
return entity;
}
/**
*構建 SQL ,具體代碼參考本系列的《Hibernate 自動生成SQL語句》
*/
private String createSql(Class<T> clz, Serializable id) {
return null;
}
/**
*得到結果集,此方法就是常規的 JDBC 操作
*/
private ResultSet getRs(String sql) {
return null;
}
/**
*數據封裝
*/
private T wrap(Class<T> clz,ResultSet rs) throws InstantiationException, IllegalAccessException {
return null;
}
}
關注 wrap()方法即可,直接讓代碼說話:
private T wrap(Class<T> clz,ResultSet rs) throws InstantiationException, IllegalAccessException {
// 通過反射創建對象
T entity = clz.newInstance();
String attName = null;
Object value = null;
// 檢查結果集中是否存在數據
if (rs.next()) {
// 屬性信息
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
// 表的字段名即類的屬性名
attName = field.getName();
// 從結果集獲取值
value = rs.getObject(attName);
//允許直接訪問私有屬性
field.setAccessible(true);
//數據封裝
field.set(entity, value);
}
}
return entity;
}
上面代碼沒有進行類型轉換相關操作,依靠 JAVA 自動類型轉換完成,對于常用的基本類型,不會出多大問題。但對于較復雜的數據類型,需要編碼進行強制類型轉換。
下面這 2 行代碼,通過打破私有屬性的壁壘,直接給屬性賦值。只適合用于驗證、測試環境下。
field.setAccessible(true);
field.set(entity, value);
測試一下代碼:
Student stu = new MySession<Student>().get(Student.class, new Integer(1));
System.out.println(stu);
在一切理想的狀態下,一個簡易的 JDBC 框架就開始工作了。
雖然它很薄弱,且不堪重負,但是,它是黑暗中的一道光,能指導你認識框架本質。
3. 非理想狀態
再來一個非理想狀態下的實現:
- 類名與表名不同名;
- 類中的屬性名與表中的字段名有不同的命名;
整體思想和上面代碼沒有區別,通過屬性名找到字段名,因為屬性名和字段名不相同,所以需要通過屬性上面的注解信息得到字段名。
讓代碼自己說話:
private T wrap_(Class<T> clz, ResultSet rs) throws InstantiationException, IllegalAccessException, SQLException,NoSuchMethodException, SecurityException, IllegalArgumentException,InvocationTargetException {
// 使用反射創建對象
T entity = clz.newInstance();
// 屬性名
String attName = null;
// 列名
String columnName = null;
Object value = null;
Column columnAnnotaiton = null;
// 檢查結果集中是否存在數據
if (rs.next()) {
// 屬性信息
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
// 屬性名
attName = field.getName();
// 查看屬性上面的注解
columnAnnotaiton = field.getAnnotation(Column.class);
if (columnAnnotaiton != null) {
columnName = columnAnnotaiton.name();
} else {
columnName = attName;
}
// 從結果集中獲取值
value = rs.getObject(columnName);
// 屬性的 set 方法名
String setMethodName = "set" + attName.substring(0, 1).toUpperCase() + attName.substring(1);
// 屬性的 set 方法
Method setMethod = clz.getDeclaredMethod(setMethodName, new Class[] { field.getType() });
setMethod.invoke(entity, new Object[] { value });
}
}
return entity;
}
上面代碼一樣沒有考慮復雜類型存在情況。
4. 小結
本節課程和大家聊了聊 Hibernate 是如何自動實現數據封裝的,從實現原理上講,還是比較容易理解的。
但是,真實環境的需求總是千變萬化的,理想只存于理想中。
Hibernate 的實現和我們實現的最大區別在于,它總是能適應不同的需求,要適應不同的需求,無論是代碼的設計還是編碼都會有難度。
但是,千里之行,始于腳下,一步一步,終能看透所有的本質。