欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

模拟hibernate 01

程序员文章站 2022-03-14 10:29:07
...

最近学习了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&amp;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。为了保证大家能理解这里的数据结构,我这里顺便给出现在的结构关系。

模拟hibernate 01

有了这样的关系,剩下解析配置文件就不难了。

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

但是,这里的模拟仍然有很多的问题,比如我们的查找无法指定查询哪些属性,也无法进行条件查询,还有许多需要改进的地方。如果原文的内容有错,也欢迎大家指出。

相关标签: 框架