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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值