模拟hibernate 01
最近学习了hibernate框架,于是决定自己模拟一个框架出来。
Hibernate框架是用来处理数据库表与实体类的映射关系,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架。
说道映射关系,我们就会想到使用配置文件来处理。首先,我们要有一个实体类和它所对应的配置文件,配置文件里面放实体类的类名和对应的表名,还有实体类的属性与表属性的对应关系,这样,一个基本的映射关系就建立起来了。
package com.demo.myhibernate2.entity;
/**
* 用户实体类
* @author yuriko
* @date 2018/07/31
*/
public class User {
private int uId; //用户id,自增
private String uName; //用户名
private String uPassword; //密码
private int uStatus; //账号状态
public User() {
super();
}
public User(int uId, String uName, String uPassword, int uStatus) {
super();
this.uId = uId;
this.uName = uName;
this.uPassword = uPassword;
this.uStatus = uStatus;
}
public int getuId() {
return uId;
}
public void setuId(int uId) {
this.uId = uId;
}
public String getuName() {
return uName;
}
public void setuName(String uName) {
this.uName = uName;
}
public String getuPassword() {
return uPassword;
}
public void setuPassword(String uPassword) {
this.uPassword = uPassword;
}
public int getuStatus() {
return uStatus;
}
public void setuStatus(int uStatus) {
this.uStatus = uStatus;
}
public String toString() {
return "User [uId=" + uId + ", uName=" + uName + ", uPassword=" + uPassword + ", uStatus=" + uStatus + "]";
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!-- User.xml -->
<hibernate>
<!-- 用户类与用户表的映射关系 -->
<class name="com.demo.myhibernate2.entity.User" table="tbuser">
<property name="uId" column="uId"/>
<property name="uName" column="uName"/>
<property name="uPassword" column="uPassword"/>
<property name="uStatus" column="uStatus"/>
</class>
</hibernate>
然后我们还需要一个连接数据库信息的配置文件,里面另外加上所有实体类的存储位置,这样在解析连接数据库的信息时,可以一并把实体类的配置文件的位置一起找到。
<?xml version="1.0" encoding="UTF-8" ?>
<!-- Hibernate.cfg.xml -->
<hibernate-configurnation>
<session-factory>
<!-- 配置数据库连接信息 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mcdonald?useUnicode=true&characterEncoding=UTF-8</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123456</property>
<!-- 实体类文件位置 -->
<mapping resource="com/demo/myhibernate2/entity/User.xml"></mapping>
</session-factory>
</hibernate-configurnation>
然后就需要解析这些配置文件了,现在我们有两个方面的东西需要解析,一个是数据库的连接信息,还有一个是实体类的映射关系。数据库的连接信息很好处理,因为只有name和value两个属性,所以我们只要使用HashMap就能存储这种一对一的关系。但是实体类的映射关系就稍微麻烦一些了,因为这里涉及到了类名,表名,属性名,而且属性名还可能有多个,实体类也可能有多个,所以实体类的映射关系也可能会有多个。最后,我是决定使用两个HashMap来存储这里的映射关系,一个叫做ormMap,用来存储表名,类名,属性的映射关系。另一个叫做mappings,用来存储多个实体类的映射关系,也就是多个ormMap。为了保证大家能理解这里的数据结构,我这里顺便给出现在的结构关系。
有了这样的关系,剩下解析配置文件就不难了。
package com.demo.myhibernate2.core;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* 解析配置文件
* @author yuriko
* @date 2018/07/31
*/
public class ParseXML {
//保存连接数据库信息
private static HashMap<String, String> configMap = new HashMap<String, String>();
//保存实体类信息
private static HashMap<String, HashMap<String, String>> maps = new HashMap<String, HashMap<String, String>>();
/**
* 得到数据库配置信息
* @param string
* @return
*/
public static String getValue(String string) {
return configMap.get(string);
}
/**
* 得到映射关系
* @param string
* @return
*/
public static HashMap<String, String> getMap(Class clazz) {
return maps.get(clazz.getName());
}
public static void init() {
try {
InputStream is = ParseXML.class.getClassLoader().getResourceAsStream("Hibernate.cfg.xml");
SAXReader sax = new SAXReader();
Document doc = sax.read(is);
Element element = (Element) doc.selectSingleNode("//session-factory");
configMap.put("hibernate.connection.driver_class", element.selectSingleNode("property[@name='hibernate.connection.driver_class']").getText());
configMap.put("hibernate.connection.url", element.selectSingleNode("property[@name='hibernate.connection.url']").getText());
configMap.put("hibernate.connection.username", element.selectSingleNode("property[@name='hibernate.connection.username']").getText());
configMap.put("hibernate.connection.password", element.selectSingleNode("property[@name='hibernate.connection.password']").getText());
List<Element> elements = doc.selectNodes("//mapping");
for (Element element2 : elements) {
String xmlFile = element2.attributeValue("resource");
doc = sax.read(ParseXML.class.getClassLoader().getResourceAsStream(xmlFile));
element = (Element) doc.selectSingleNode("//class");
HashMap<String, String> map = new HashMap<String, String>();
map.put("tableName", element.attributeValue("table"));
map.put("className", element.attributeValue("name"));
List<Element> list = element.selectNodes("//property");
for (Element element3 : list) {
map.put(element3.attributeValue("name"), element3.attributeValue("column"));
}
maps.put(element.attributeValue("name"), map);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
下面是把hibernate.cfg.xml中的name配置成常量,还有数据库的连接,这个就不多说了。
package com.demo.myhibernate2.core;
/**
* 常量配置
* @author yuriko
* @date 2018/07/31
*/
public class SYSParam {
public static final String DRIVER_CLASS = "hibernate.connection.driver_class";
public static final String URL = "hibernate.connection.url";
public static final String USERNAME = "hibernate.connection.username";
public static final String PASSWORD = "hibernate.connection.password";
}
package com.demo.myhibernate2.core;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import static com.demo.myhibernate2.core.ParseXML.getValue;
/**
* JDBCUtils
* @author yuriko
* @date 2018/07/31
*/
public class JDBCCommon {
/**
* 建立连接
* @return
*/
public static Connection getConnection() {
Connection conn = null;
try {
Class.forName(getValue(SYSParam.DRIVER_CLASS));
conn = DriverManager.getConnection(getValue(SYSParam.URL), getValue(SYSParam.USERNAME), getValue(SYSParam.PASSWORD));
//System.out.println("success");
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 执行sql的通用方法
* @param sql
* @return
*/
public static int execute(String sql) {
Connection conn = null;
Statement stat = null;
int row = 0;
try {
conn = getConnection();
stat = conn.createStatement();
row = stat.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
close(conn, stat);
}
return row;
}
/**
* 关闭连接
* @param conn
* @param stat
*/
public static void close(Connection conn, Statement stat) {
try {
if (stat != null) stat.close();
if (conn != null) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 关闭连接
* @param conn
* @param stat
* @param rs
*/
public static void close(Connection conn, Statement stat, ResultSet rs) {
try {
if (rs != null) rs.close();
if (stat != null) stat.close();
if (conn != null) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
然后是对数据库进行增删改查时涉及到的赋值和取值,这里另外封装成了一个类,方便使用,并且采用了反射技术。
package com.demo.myhibernate2.core;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 对数据库进行增删改查时涉及到的设值与取值
* @author yuriko
* @date 2018/07/31
*/
public class ValueUtils {
/**
* 设值
* @param object 需要赋值的对象
* @param paramName 需要赋值的属性名
* @param paramValue 要赋予的属性值
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
public static void setValue(Object object, String paramName, Object paramValue) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method[] methods = object.getClass().getMethods();
for (Method method : methods) {
//判断是否与类中得到的set方法名相同
if (method.getName().equalsIgnoreCase("set"+paramName)) {
Class paramType = method.getParameterTypes()[0];
//判断赋值的类型
if (paramType == int.class || paramType == Integer.class) {
paramValue = Integer.parseInt(paramValue.toString());
}else if (paramType == long.class || paramType == Long.class) {
paramValue = Long.parseLong(paramValue.toString());
}else if (paramType == float.class || paramType == Float.class) {
paramValue = Float.parseFloat(paramValue.toString());
}else if (paramType == double.class || paramType == Double.class) {
paramValue = Double.parseDouble(paramValue.toString());
}else if (paramType == String.class) {
paramValue = paramValue.toString();
}
method.invoke(object, paramValue);
break;
}
}
}
/**
* 取值
* @param object 需要取值的对象
* @param paramName 需要取的属性名
* @return 取到的值
* @throws IllegalAccessException
* @throws IllegalArgumentException
* @throws InvocationTargetException
*/
public static Object getValue(Object object, String paramName) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Method[] methods = object.getClass().getMethods();
Object paramValue = null;
for (Method method : methods) {
//判断是否与类中得到的get方法名相同
if (method.getName().equalsIgnoreCase("get"+paramName)) {
paramValue = method.invoke(object, new Object[]{});
break;
}
}
return paramValue;
}
}
最后是对数据库的增删改查的通用操作,在这个方法中,最重要的就是如何拼接sql语句,毕竟我们做的操作全部都要根据sql语句来进行,另外还有使用反射技术进行取值和赋值操作。这里我们以插入数据库作为例子,那么我们要得到的sql语句就应该是:insert into 表名 (列名) values(值)。这里的表名好办,根据上面的数据结构,根据我们要进行插入的类名,可以得到对应的ormMap,在ormMap中,我们就可以得到表名。同样,列名在ormMap中也能找得到,把ormMap转换成set集合,除开表名和类名,我们就能通过getValue()得到列名,再通过字符串的拼接,就可以得到一个完整的列名。至于值的话,我们可以在循环中得到实体类的属性名,然后根据属性名通过反射技术来得到对应的属性值即可。
增加与查找的代码如下:
package com.demo.myhibernate2.core;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
/**
* 增删改查通用方法
* @author yuriko
* @date 2018/07/31
*/
public class Session {
/**
* 保存信息
* @param object
* @return
*/
public int save(Object object) {
int row = 0;
try {
HashMap<String, String> map = ParseXML.getMap(object.getClass());
String tableName = map.get("tableName");
//先使用${column}、${values}表示列名和值,最后再进行替换
String sql = "insert into " + tableName + " (${column}) values(${values})";
String column = ""; //存储列名
String values = ""; //存储值
Set<Entry<String, String>> entrySet = map.entrySet();
for (Entry<String, String> entry : entrySet) {
//查找所有属性,过滤掉表名和类名
if (entry.getKey().equals("tableName") || entry.getKey().equals("className")) {
continue;
}
//拼接列名
column += entry.getValue() + ",";
Object paramValue = ValueUtils.getValue(object, entry.getKey());
//判断是否是字符串类型,字符串类型需要加上单引号
if (paramValue instanceof String) {
values += "'" + paramValue + "',";
}else{
values += paramValue + ",";
}
}
//去掉最后多余的逗号
column = column.substring(0, column.length()-1);
values = values.substring(0, values.length()-1);
//替换原先sql语句的${column}和${values}
sql = sql.replace("${column}", column).replace("${values}", values);
//调用写好的sql通用方法
JDBCCommon.execute(sql);
} catch (Exception e) {
e.printStackTrace();
}
return row;
}
/**
* 查询全表
* @param clazz
* @return
*/
public List<Object> find(Class clazz) {
List<Object> list = new ArrayList<Object>();
HashMap<String, String> map = ParseXML.getMap(clazz);
String tableName = map.get("tableName");
//使用${column}表示要查找的属性名,之后再进行替换
String sql = "select ${column} from " + tableName;
Set<Entry<String, String>> entrySet = map.entrySet();
//存储列名
String column = "";
for (Entry<String, String> entry : entrySet) {
//查找所有属性,过滤掉表名和类名
if (entry.getKey().equals("tableName") || entry.getKey().equals("className")) {
continue;
}
//拼接列名
column += entry.getKey() + ",";
}
//去掉最后多余的逗号
column = column.substring(0, column.length()-1);
//替换原先sql语句的${column}
sql = sql.replace("${column}", column);
try {
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
conn = JDBCCommon.getConnection();
stat = conn.createStatement();
rs = stat.executeQuery(sql);
while (rs.next()) {
Object object = clazz.newInstance();
Set<Entry<String, String>> set = map.entrySet();
for (Entry<String, String> entry : set) {
if (entry.getKey().equals("tableName") || entry.getKey().equals("className")) {
continue;
}
//取得属性名
String paramName = entry.getKey();
//取得列名
String columnName = entry.getValue();
Object paramValue = rs.getObject(columnName);
//使用反射技术进行赋值
ValueUtils.setValue(object, paramName, paramValue);
}
//将查找到的对象存入list中
list.add(object);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}
这样,一个基本的模拟hibernate就完成了,最后再使用工厂类进行生产即可。
package com.demo.myhibernate2.core;
/**
* 工厂类
* @author yuriko
* @date 2018/07/31
*/
public class Factory {
//第一次加载的时候就进行配置文件的解析
static {
ParseXML.init();
}
public static Session getSession() {
return new Session();
}
}
但是,这里的模拟仍然有很多的问题,比如我们的查找无法指定查询哪些属性,也无法进行条件查询,还有许多需要改进的地方。如果原文的内容有错,也欢迎大家指出。
下一篇: junit整合Spring
推荐阅读
-
100-Days-Of-ML-Code(Day_01)完整理解
-
PHP模拟发送POST请求之三、用Telnet和fsockopen()模拟发送POST信息,telnetfsockopen_PHP教程
-
HIbernate的配置文件详解
-
模拟OICQ的实现思路和核心程序(二)
-
struts2+Spring3+hibernate3零配置并且正式环境和开发环境不需要多大改动 Struts
-
利用vue + koa2 + mockjs模拟数据的方法教程
-
elasticsearch6.7 01.入门指南(2)
-
eclipse安装hibernate插件方法
-
eclipse安装hibernate插件方法
-
用Jersey构建RESTful服务5-Jersey+MySQL5.6+Hibernate4.3