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();
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章