自定义类 Hibernate 框架的基本过程

1. 前言

不要一味地迷信,要善于质疑,善于打破。这应该是我们在学习框架时应该保持的心态。

Hibernate 是所有 JDBC 框架中封装最高的、使用起来最便利的框架之一。对于初学者而言,要么在感叹它的神奇,要么敬畏它的存在。

但是,作为一个真正的开发者,应该有破有立的想法。

本节课程试图通过一个简易的 JDBC 框架的实现流程描述,让大家更清晰地了解 Hibernate 框架的设计过程。

TIps: 框架的编写是一个比较复杂的过程,并不是一言两语能说清楚的,本文中的代码介于伪代码和真实代码之间 ,仅仅用来帮助大家理解 Hibernate 的框架基础原理。

2. 读取配置信息

简易框架主要模拟 3 个组件,分别对应 Hibernate 中的 Configuration、SessionFactory、Session 组件。

Configuration 组件的作用:解析配置文件中的信息。

Tips: 简易框架中,保持配置文件格式及内容和 Hiberante 的主配置文件一致。

查看一下 Configuration 类的结构:

public class Configuration {
	//保存配置文件中的 property 元素信息
	private Map<String,String> pros;
	//保存配置文件中的映射类
	private List<String> mappings;
	
	public Configuration() {
		this.pros=new HashMap<String, String>();
		this.mappings=new ArrayList<String>();
	}
	
	/**
	 * 读取配置文件
	 */
	public void configure() throws DocumentException {
		configure("/hibernate.cfg.xml");
	}

	private void configure(String resource) throws DocumentException {
		InputStream stream =Configuration.class.getResourceAsStream(resource);
		doConfigure( stream);
	}

	/**
	 * 使用 DOM4j XML 解析器解决配置文件中的信息
	 */
	private void doConfigure(InputStream stream) throws DocumentException {
		SAXReader saxReader=new SAXReader();
		Document doc= saxReader.read(stream);
		Element rootEle= doc.getRootElement();
		Element sfEle= rootEle.addElement("session-factory");		
		List<Element> propertys= sfEle.elements("property");
		for (Element ele : propertys) {
			this.pros.put(ele.attributeValue("name"), ele.getText());
		}
		List<Element> mappings= sfEle.elements("mapping");
		for (Element m : mappings) {
		    this.mappings.add(m.attributeValue("class"));
		}	
	}
    /**
    *创建会话工厂
    *
    */
	public SessionFactory buildSessionFactory()  {
		return new SessionFactory(this.pros,this.mappings);
	}
}

Hibernate 的主配置文件中有很多配置项,因受限于文章篇幅和本文初设目标,这里只解析 2 类信息:

  • 数据库连接信息;
  • 实体类路径信息。

基础好的学习者可以查阅 Hibernate 的源代码。

3. 会话工厂

会话工厂的核心作用:建立和数据库系统的连接,创建会话对象。

创建时需要依赖 Configuration 组件解析出来的信息。Hiberante 的会话工厂的源代码实现比较复杂,代码具有良好的结构性,会衍生出很多辅助性组件。

因为只是说明问题,本文中的工厂仅仅用来创建会话对象,不考虑性能、缓存、事务等各方面问题,也不刻意讲究代码的结构性。

简易框架中的会话工厂的代码简单但具有说明力:

public class SessionFactory {
	private Map<String, String> pros;
	private List<String> mappings;
	private ThreadLocal<Session> threadLocal;
	public SessionFactory(Map<String, String> pros, List<String> mappings) {
		this.pros = pros;
		this.mappings = mappings;
		this.threadLocal = new ThreadLocal<Session>();
	}
	public Connection getConn() {
		String driverClass = this.pros.get("connection.driver_class");
		String url = this.pros.get("connection.url");
		String userName = this.pros.get("connection.username");
		String password = this.pros.get("connection.password");
		try {
			Class.forName(driverClass);
			return DriverManager.getConnection(url, userName, password);
		} catch (ClassNotFoundException | SQLException e) {
			e.printStackTrace();
		}
		return null;
	}
	public Session openSession() {
		return new Session(this.getConn(), this.mappings);
	}
	public Session getCurrentSession() {
		Session session = this.threadLocal.get();
		if (session == null) {
			session = this.openSession();
			this.threadLocal.set(session);
		}
		return session;
	}
}

会话工厂类中有 2 个创建 Session 的方法,一个是普通的创建方法,另一个是从线程上下文中检查是否已经存在 Session 对象。

4. 会话对象

Session 对象提供具体的方法用来对数据库中的数据进行相关操作。

public class Session<T> {
private Connection conn;
private List<String> mappings;

public Session(Connection conn, List<String> mappings) {
	this.conn=conn;
	this.mappings=mappings;
}

public T get(Class<T> clz,Serializable ser) {
	//构建 SQL 		
	//得到结果集
	//封装数据
	return null;
}

public Object load(Class<T> clz,Serializable ser) {
	return null;
}
public int save() {
	return 0;
}

//其它方法
}

在前面的《Hibernate 自动生成 SQL 语句》和《 Hibernate 自动进行数据封装 》章节中已经讲解了如何通过反射构建 SQL 和数据封装。请查阅相关内容。

因为延迟加载是 Hibernate 中一个比较重要的特性,所以,这里讲解一下 load 方法的实现原理。load()方法调用时并没有直接访问数据库,而是返回给用户一个代理对象,只有当使用者调用代理对象的 getXXX()方法时,才会进入数据库。

做到这一点并不难,使用代理设计模式便可。

代理设计模式中创建代理对象的方案有 2 种:

  • 静态代理对象;
  • 动态代理对象。

一般都会使用动态方式创建代理对象,动态代理对象的常用创建方案有 2 种:

  • 使用 JDK 中提供的 Proxy 类创建;
  • 第三方实现,如 cglib 库。

使用 Proxy 创建时有一个前提条件,需要被代理的对象提供接口规范,使用 cglib 就不需要,因是第三方实现,则需要在项目中导入依赖包。

通过代理设计模式的回调机制,load()方法就能实现当需要时再触发对应的数据库操作。

5. 小结

本节课以简易代码的方式描述了 Configuration、SessionFactory、Session 3 个组件的功能实现,以及其相互之间的依赖关系。

但并没有更深入的接轨 Hibennate 的源代码,Hiberanate 源代码中有很多商业性的解决方案,值得大家深究参考。

希望通过本次课程,能让大家更进一步的认识 Hibernate 。