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