跳到主要内容

Hibernate 自动进行数据封装

1. 前言

Hibernate 可以构建各种复杂的 SQL 语句,但其本质都是反射机制结合映射关系完成的。

框架也仅是一款程序产品,人为编写的产物。要相信,只要你愿意,你完全可以实现自己的 JDBC 框架。

本节课和大家继续聊聊 Hibernate 是如何自动封装数据的。

2. 理想状态

程序中的数据通过 SQL 语句传递给数据库,从 IO 流的角度上理解是数据的输出。

把数据库中的数据封装成程序能识别、使用的类型,这个过程叫数据库封装。从 IO 流的角度上讲是数据的输入。

数据封装的实现还是利用反射机制。

数据封装的思路也简单。

表结构对应类结构,一行记录或说一个实体对应一个对象,字段对应属性。还是同样的配方。

即将编写实例之前,先做一个理想化的假设:

  • 表名和类名相同;
  • 表中的字段名和类中的属性名相同;
  • 实体类中没有特别复杂的数据类型。

理想状态设定便于大家由浅入深理解实现过程。

真实项目中,很难达到理想化状态 。因为 JAVA 程序和关系型数据库的命名规范有差异性,不管谁迁就谁,都会让彼此难受。

在上一节课的自定义 Session 对象中添加一个封装方法:

public class MySession<T> {
public T get(Class<T> clz, Serializable id) throws Exception {
String sql = createSql\_(clz, id);
// JDBC 常规操作…… 得到结果集
ResultSet rs=this.getRs(sql);
//数据封装
T entity= this.wrap(clz, rs);
return entity;
}
/\*\*
\*构建 SQL ,具体代码参考本系列的《Hibernate 自动生成SQL语句》
\*/
private String createSql(Class<T> clz, Serializable id) {
return null;
}
/\*\*
\*得到结果集,此方法就是常规的 JDBC 操作
\*/
private ResultSet getRs(String sql) {
return null;
}
/\*\*
\*数据封装
\*/
private T wrap(Class<T> clz,ResultSet rs) throws InstantiationException, IllegalAccessException {
return null;
}
}

关注 wrap()方法即可,直接让代码说话:

private T  wrap(Class<T> clz,ResultSet rs) throws InstantiationException, IllegalAccessException {
// 通过反射创建对象
T entity = clz.newInstance();
String attName = null;
Object value = null;
// 检查结果集中是否存在数据
if (rs.next()) {
// 属性信息
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
// 表的字段名即类的属性名
attName = field.getName();
// 从结果集获取值
value = rs.getObject(attName);
//允许直接访问私有属性
field.setAccessible(true);
//数据封装
field.set(entity, value);
}
}
return entity;
}

上面代码没有进行类型转换相关操作,依靠 JAVA 自动类型转换完成,对于常用的基本类型,不会出多大问题。但对于较复杂的数据类型,需要编码进行强制类型转换。

下面这 2 行代码,通过打破私有属性的壁垒,直接给属性赋值。只适合用于验证、测试环境下。

field.setAccessible(true);
field.set(entity, value);

测试一下代码:

Student stu = new MySession<Student>().get(Student.class, new Integer(1));
System.out.println(stu);

在一切理想的状态下,一个简易的 JDBC 框架就开始工作了。

虽然它很薄弱,且不堪重负,但是,它是黑暗中的一道光,能指导你认识框架本质。

3. 非理想状态

再来一个非理想状态下的实现:

  • 类名与表名不同名;
  • 类中的属性名与表中的字段名有不同的命名;

整体思想和上面代码没有区别,通过属性名找到字段名,因为属性名和字段名不相同,所以需要通过属性上面的注解信息得到字段名。

让代码自己说话:

private T wrap\_(Class<T> clz, ResultSet rs) throws InstantiationException, IllegalAccessException, SQLException,NoSuchMethodException, SecurityException, IllegalArgumentException,InvocationTargetException {
// 使用反射创建对象
T entity = clz.newInstance();
// 属性名
String attName = null;
// 列名
String columnName = null;
Object value = null;
Column columnAnnotaiton = null;
// 检查结果集中是否存在数据
if (rs.next()) {
// 属性信息
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
// 属性名
attName = field.getName();
// 查看属性上面的注解
columnAnnotaiton = field.getAnnotation(Column.class);
if (columnAnnotaiton != null) {
columnName = columnAnnotaiton.name();
} else {
columnName = attName;
}
// 从结果集中获取值
value = rs.getObject(columnName);
// 属性的 set 方法名
String setMethodName = "set" + attName.substring(0, 1).toUpperCase() + attName.substring(1);
// 属性的 set 方法
Method setMethod = clz.getDeclaredMethod(setMethodName, new Class[] { field.getType() });
setMethod.invoke(entity, new Object[] { value });
}
}
return entity;
}

上面代码一样没有考虑复杂类型存在情况。

4. 小结

本节课程和大家聊了聊 Hibernate 是如何自动实现数据封装的,从实现原理上讲,还是比较容易理解的。

但是,真实环境的需求总是千变万化的,理想只存于理想中。

Hibernate 的实现和我们实现的最大区别在于,它总是能适应不同的需求,要适应不同的需求,无论是代码的设计还是编码都会有难度。

但是,千里之行,始于脚下,一步一步,终能看透所有的本质。