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();
}