10 03Hibernate之實現原理

Hibernate之所以可以實現簡單Java類與數據表的映射,都取決於*.hbm.xml文件的功能,而且動態生成的SQL語句發現使用的依然是PreparedStatement進行處理,所以可以得出來,整個的Hibernate就屬於一個反射與JDBC的結合體,所有使用的SQL語句實際上都應該是自動生成的。

由於hibernate.cfg.xml文件主要是數據庫文件以及映射文件的配置,那麼這些基本的操作將不再進行重複的解析,假設已經明確的知道了映射文件是哪一個。

同時考慮到編寫方便,因爲如果使用了DOM4J,那麼它會首先自動的下載DTD文件對給定的XML文件進行驗證。以下的文件就是要讀取的信息內容:
範例:要讀取的Member.hbm.xml文件

<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping>
	<!-- 定義簡單Java類的完整名稱,通過反射加載 -->
	<!-- 每一個映射文件都要求有一個與之對應的POJO類存在,此文件的作用就是描述POJO類與數據表的字段映射 -->
	<!-- name屬性描述的是POJO類,而table屬性描述的是這個類對應的數據表 -->
	<!-- catalog表示的是操作的模式名稱(數據庫名稱),一般可以省略 -->
    <class name="org.lks.pojo.Member" table="member" catalog="hedb">
    	<!-- 每一張實體表都一定會包含有一個主鍵的列,所以此處描述主鍵列 -->
    	<!-- name描述的在POJO類中的屬性名稱,type描述的是POJO類屬性的類型 -->
        <id name="mid" type="java.lang.String">
        	<!-- 此POJO類的屬性與數據表中的列的對應關係 -->
        	<!-- name表示列名稱,而length表示的是name列在表中定義的長度 -->
            <column name="mid" length="50" />
            <!-- 表示主鍵的生成方式,本處使用的是指派模式 -->
            <generator class="assigned"></generator>
        </id>
        <!-- 後面定義的都是類中的屬性以及表中的非主鍵字段的映射關係 -->
        <!-- 此處描述的是映射name類屬性以及name數據列的關係 -->
        <property name="mname" type="java.lang.String">
            <column name="mname" length="50" />
        </property>
        <property name="mage" type="java.lang.Integer">
            <column name="mage" />
        </property>
        <property name="msalary" type="java.lang.Double">
            <column name="msalary" precision="22" scale="0" />
        </property>
        <property name="mbirthday" type="java.util.Date">
            <column name="mbirthday" length="10" />
        </property>
        <property name="mnote" type="java.lang.String">
            <column name="mnote" length="65535" />
        </property>
    </class>
</hibernate-mapping>

整個的操作分爲以下幾個步驟:
(1)解析文件;
(2)動態生成SQL語句;
(3)動態使用PreparedStatement設置內容。

1 解析Member.hbm.xml文件

既然要模擬Hibernate實現原理,那麼不妨就直接建立一個MySession的類,這個類裏面也提供有一個保存方法。
範例:MySession的基本結構

package org.lks.util;

public class MySession {
	public void save(Object obj){
		
	}
}

package org.lks.test;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import org.lks.pojo.Member;
import org.lks.util.MySession;

public class MySessionDemo {
	public static void main(String[] args) throws ParseException {
		Member pojo = new Member();
		pojo.setMid("3171301102");
		pojo.setMname("hhy");
		pojo.setMage(20);
		pojo.setMsalary(2000.0);
		pojo.setMnote("hhy big fool!");
		pojo.setMbirthday(new SimpleDateFormat("yyyy-MM-dd").parse("1999-08-21"));
		MySession ms = new MySession();
		ms.save(pojo);
	}
}

在之前使用的時候實際上就是相當於在Session裏面直接調用了save()方法保存。
1、現在相當於只有一個POJO類,那麼必須通過這個POJO類找到對應的*.hbm.xml文件,那麼假設這些個文件所在的路徑都是在D:\

private static final String DIR = "D:" + File.separator;
String fileName = this.pojoObject.getClass().getSimpleName() + ".hbm.xml"; //映射文件名稱

2、既然已經知道了Member.hbm.xml文件的完整路徑,那麼自然可以進行SAX解析操作,而且所有的開發框架幾乎都是使用了DOM4J組件,也就是說如果你的項目裏面已經添加了Hibernate支持,那麼就表示你已經可以使用DOM4J。後面的重點就在於praseHBM()方法裏面了。
(1)要讀取必須知道文件的路徑,使用File類定義文件的路徑;

File file = new File(MySession.DIR + fileName);

(2)DOM4J裏面提供有專門的解析工具類——SAXReader類,那麼利用此類讀取文件;

SAXReader sax = new SAXReader();
Document docuemnt = sax.read(file);

(3)隨後需要進行解析操作。

private String tableName ;  //表示要操作的表名稱
//保存類中的屬性與列的對應關係,因爲有可能列名稱和屬性名稱不一致
private Map<String,String> fieldColumnMap = new HashMap<String,String>();
//保存每一個屬性名稱以及與其對應類型的信息
private Map<String,String> fieldTypeMap = new HashMap<String,String>();
private String idColumn = null; //保存id列
private String generator = null; //主鍵生成方式
public void parseHBM(String fileName) throws Exception{ //負責解析Memeber.hbm.xml文件
	File file = new File(MySession.DIR + fileName);
	SAXReader sax = new SAXReader(); //定義SAX解析器
	Document document = sax.read(file); //設置要讀取的文件內容
	Element hmRoot = document.getRootElement(); //讀取根元素(<hibernate-mapping>)
	Element classElement = hmRoot.element("class"); //讀取class節點(<class>)
	String className = classElement.attributeValue("name"); //讀取類名稱 
	//判斷當前解析文件是否爲指定類型對應的映射文件
	if(className.equals(this.pojoObject.getClass().getName())){
		this.tableName = classElement.attributeValue("table"); //表名稱
		//接着應該繼續讀取"<class>"的子節點,有兩個子節點:<id>、<property>
		Element idElement = classElement.element("id");  //取得<id>節點
		String idName = idElement.attributeValue("name");  //取得name屬性值
		String idType = idElement.attributeValue("type");  //取得type屬性值
		this.fieldTypeMap.put(idName,idType);  //保存操作,用於生成SQL操作
		Element idColumnElement = idElement.element("column"); //取得<id>元素下的<column>子元素
		this.idColumn = idColumnElement.attributeValue("name");  //取得主鍵列的名稱
		Element generatorElement = idElement.element("generator"); //取得<generator>節點
		this.generator = generatorElement.attributeValue("class"); //讀取生成方式
		
		//在整個.hbm.xml文件裏面只有一個<id>元素,但是會有多個<property>,那麼此時就需要使用循環處理
		Iterator<Element> allProperties = classElement.elements("property").iterator();
		while(allProperties.hasNext()){
			Element property = allProperties.next();
			String propertyName = property.attributeValue("name");
			String propertyType = property.attributeValue("type");
			String columnName = property.element("column").attributeValue("name");
			this.fieldColumnMap.put(propertyName, columnName); //保存成員與數據列的對應關係
			this.fieldTypeMap.put(propertyName, propertyType); //保存成員與數據類型的對應關係
		}
	}else{
		System.out.println("【出現異常】映射文件沒有找到!");
	}
	
}

如果在實際的工作中應該在進行分解,例如:針對於ID的解析有一個專門的處理類,針對於property的解析有專門的處理類,這樣就可以實現代碼的細分。

2 創建SQL語句

在創建SQL語句的過程之中,最爲麻煩的地方在於要考慮主鍵的生成情況,如果是自動增長,則應該出現主鍵列,如果不是自動增長,是手工配置,那麼則應該明確的寫上主鍵列。創建一個可以生成增加SQL語句的工具類:
(1)在本程序之中最主要的目的是要進行兩個Map集合的迭代操作;
(2)所有的SQL語句都應該動態生成,一旦是動態生成字符串,那麼首先就必須想到使用StringBuffer類或StringBuilder類。

public String createInsertSQL(){  //創建SQL語句
	StringBuffer sql = new StringBuffer();
	StringBuffer columnBuf = new StringBuffer(); //負責處理列的信息保存
	StringBuffer valueBuf = new StringBuffer(); //負責處理“?”的保存
	sql.append(" INSERT INTO ").append(this.tableName).append(" ( ");
	if("assigned".equals(this.generator)){ //必須知道主鍵的操作類型
		columnBuf.append(this.idColumn).append(",");
		valueBuf.append("?").append(",");
	}else if("native".equals(this.generator)){ //不需要設置ID字段
		
	}
	//通過迭代fieldColumnMap集合取出列的名稱
	Iterator<Map.Entry<String, String>> iter = this.fieldColumnMap.entrySet().iterator();
	while(iter.hasNext()){
		Map.Entry<String, String> me = iter.next();
		if(!this.idColumn.equals(me.getValue())){  //id列不處理
			columnBuf.append(me.getValue()).append(",");
			valueBuf.append("?").append(",");
		}
	}
	columnBuf.delete(columnBuf.length() - 1, columnBuf.length());
	valueBuf.delete(valueBuf.length() - 1, valueBuf.length());
	sql.append(columnBuf).append(")").append(" VALUES ").append(" ( ").append(valueBuf).append(" ) ");
	return sql.toString();
}

由於此處還是簡單實現,所以針對於此部分的實現沒有定義一個專門的生成處理類。
(3)雖然是生成了SQL,可是裏面的“?”與要進行增加的處理操作不對應,我們根本就不知道“?”表示的是什麼,所有的PreparedStatement處理的時候都是採用索引的方式完成的,那麼可以定義一個與之對應的索引標記進行值的標記。

//保存生成SQL語句的佔位符索引與POJO類值的對應關係
private Map<Integer,FieldNameAndValue> indexValueMap = new HashMap<Integer,FieldNameAndValue>();

private class FieldNameAndValue{ //存放內容以及成員關係
	private String fieldName;
	private Object fieldValue;
	
	public FieldNameAndValue(String fieldName, Object fieldValue){
		this.fieldName = fieldName;
		this.fieldValue = fieldValue;
	}
}
public Object getFieldValue(String fieldName){//取得成員的內容
		try{
			return this.pojoObject.getClass().getMethod("get" + this.Capitalized(fieldName)).invoke(pojoObject);
		}catch(Exception e){
			e.printStackTrace();
		}
		
		return null;
	}

由於使用的是PreparedStatement處理,所以代碼必須要考慮索引的問題。

3 數據的JDBC保存

本次假設已經知道了所有相關的數據庫連接信息,後面只需要處理數據的保存即可。

public void saveDatabase() throws Exception{  //進行數據的保存
	String sql = this.createInsertSQL(); //取得要執行的SQL
	System.out.println(sql);
	Class.forName("com.mysql.cj.jdbc.Driver");
	Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/hedb?serverTimezone=UTC", "root", "19990821");
	PreparedStatement pstmt = conn.prepareStatement(sql);
	int foot = 1; //操作的索引內容
	for(int i = 0; i < this.indexValueMap.size(); i++){
		FieldNameAndValue fa = this.indexValueMap.get(foot);
		System.out.println(fa);
		if("java.lang.String".equals(fa.fieldName)){
			String value = (String)fa.fieldValue;
			pstmt.setString(foot, value);
		}else if("java.lang.Integer".equals(fa.fieldName)){
			Integer value = (Integer)fa.fieldValue;
			pstmt.setInt(foot, value);
		}else if("java.lang.Double".equals(fa.fieldName)){
			Double value = (Double)fa.fieldValue;
			pstmt.setDouble(foot, value);
		}else if("java.util.Date".equals(fa.fieldName)){
			Date value = (Date)fa.fieldValue;
			pstmt.setDate(foot, new java.sql.Date(value.getTime()));
		}
		foot++;
	}
	System.out.println(pstmt.executeUpdate());
	conn.close();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章