乐趣区

模仿hibernate框架,详解hibernate部分方法设计

导读
源码地址
公司的持久层采用的 hibernate 框架,这也是很多公司使用的一种持久层框架。它将瞬时态的数据转化为持久态、或将持久态的数据转化为瞬时态数据。我比较喜欢看源码,看别人的架构思想,因为,笔者想向架构师的方向进发。看了别人的源码,突然想模拟 hibernate 框架,自己写个框架出来。这里去除了 hibernate 框架晦涩的地方,当做自己学习材料还是不错的。里面涉及到反射、连接池等等。这个项目中,你可以知道数据库连接池是怎么建的,又是怎么回收的。使用警惕代码块加载配置文件
以下详细介绍我个人的项目,但肯定没有人家源码写得好,这里仅作为学习使用。
如果不懂的,可以私信我。

配置文件
本项目以 idea 为开发环境和以 maven 搭建的,分为 java 包和 test 包。java 包的配置文件放在 resources 下,代码放在 com.zby.simulationHibernate 包下,如下是配置文件:

连接池
我们在使用 hibernate 时,一般会配置连接池,比如,初始化连接数是多少,最大连接数是多少?这个连接的是什么?我们在启动项目时,hibernate 根据初始的连接数,来创建多少个数据库连接对象,也就是 jdbc 中的 Connection 对象。
为什么要有这个连接池?因为,每次开启一个连接和关闭一个连接都是消耗资源的,我们开启了这些连接对象之后,把它们放在一个容器中,我们何时需要何时从容器中取出来。当不需要的时候,再将踏进放回到容器中。因而,可以减少占用的资源。
如下,是初始化的连接对象:
package com.zby.simulationHibernate.util.factory;

import com.zby.simulationHibernate.util.exception.GenericException;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

/**
* Created By zby on 21:23 2019/1/23
* 数据库的连接
*/
public class Connect {

/**
* 连接池的初始值
*/
private static int initPoolSize = 20;

/**
* 创建 property 的配置文件
*/
protected static Properties properties;

/**
* 连接池的最小值
*/
protected static int minPoolSize;

/**
* 连接池的最大值
*/
protected static int maxPoolSize;

//【2】静态代码块
static {
// 加载配置文件
properties = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream(“db.properties”);
try {
properties.load(is);
minPoolSize = Integer.valueOf(properties.getProperty(“jdbc.minConnPool”));
if (minPoolSize <= initPoolSize)
minPoolSize = initPoolSize;
maxPoolSize = Integer.valueOf(properties.getProperty(“jdbc.maxConnPool”));
if (minPoolSize > maxPoolSize)
throw new GenericException(“ 连接池的最小连接数不能大于最大连接数 ”);
} catch (IOException e) {
System.out.println(“ 未找到配置文件 ”);
e.printStackTrace();
}
}

/**
* Created By zby on 16:50 2019/1/23
* 获取数据连接
*/
protected java.sql.Connection createConnect() {
String driverName = properties.getProperty(“jdbc.driver”);
if (StringUtils.isEmpty(driverName)) {
driverName = “com.mysql.jdbc.Driver”;
}
String userName = properties.getProperty(“jdbc.username”);
String password = properties.getProperty(“jdbc.password”);
String dbUrl = properties.getProperty(“jdbc.url”);

try {
Class.forName(driverName);
return DriverManager.getConnection(dbUrl, userName, password);
} catch (ClassNotFoundException e) {
System.out.println(“ 找不到驱动类 ”);
e.printStackTrace();
} catch (SQLException e) {
System.out.println(“ 加载异常 ”);
e.printStackTrace();
}
return null;
}
}

创建 Session 会话
我们在使用 hibernate 时,不是直接使用连接对象,而是,以会话的方式创建一个连接。创建会话的方式有两种。一种是 openSession,这种是手动提交事务。getCurrentSession 是自动提交事务。
如代码所示:
package com.zby.simulationHibernate.util.factory;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
* Created By zby on 15:43 2019/1/23
*/
public class SqlSessionFactory implements SessionFactory {

/**
* 连接池
*/
private static List<Connection> connections;

/**
* 连接对象
*
* @return
*/
private static Connect connect = new Connect();

protected static List<Connection> getConnections() {
return connections;
}

// 静态代码块,初始化常量池
static {
connections = new ArrayList<>();
Connection connection;
for (int i = 0; i < Connect.minPoolSize; i++) {
connection = connect.createConnect();
connections.add(connection);
}
}

@Override
public Session openSession() {
return getSession(false);
}

@Override
public Session getCurrentSession() {
return getSession(true);
}

/**
* 获取 session
*
* @param autoCommit 是否自动提交事务
* @return
*/
private Session getSession(boolean autoCommit) {
//【1】判断连接池有可用的连接对象
boolean hasNoValidConn = hasValidConnction();
//【2】没有可用的连接池,使用最大的连接池
if (!hasNoValidConn) {
for (int i = 0; i < (Connect.maxPoolSize – Connect.minPoolSize); i++) {
connections.add(connect.createConnect());
}
}
//【3】有可用的连接
for (Iterator iterator = connections.iterator(); iterator.hasNext();) {
Connection connection = null;
try {
connection = (Connection) iterator.next();
connection.setAutoCommit(autoCommit);
Session session = new Session(connection);
iterator.remove();
return session;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}

/**
* Created By zby on 21:50 2019/1/23
* 当我们没开启一个连接,连接池的数目减少 1,直到连接池的数量为 0
*/
private boolean hasValidConnction() {
return null != connections && connections.size() != 0;
}
}

数据查找
我们既然使用这个框架,必然要有数据查找的功能。返回结果分为两种,一种是以实体类直接返回,调用 AddEntity 方法。但是,有时时多张表查询的结果,这种情况下,直接以实体类肯定不可以的,因而,我们需要使用自定义接收对象,并将查找结果进行过滤,再封装成我们想要的对象。
第一种,以实体类返回
/**
* Created By zby on 23:19 2019/1/23
* 体检反射的实体类
*/
public SqlQuery addEntity(Class<T> persistenceClass) {
this.persistenceClass = persistenceClass;
return this;
}
第二种,过滤后返回数据
/**
* Created By zby on 19:18 2019/1/27
* 创建类型
*/
public SqlQuery addScalar(String tuple, String alias) {
if (CommonUtil.isNull(aliasMap)) {
aliasMap = new HashMap<>();
}
for (Map.Entry<String, String> entry : aliasMap.entrySet()) {
String key = entry.getKey();
if (key.equals(tuple))
throw new GenericException(“alias 已经存在,即 alias=” + key);
String value = aliasMap.get(key);
if (value.equals(alias) && key.equals(tuple))
throw new GenericException(“ 当前 alias 的 type 已经存在,alias=” + key + “,type=” + value);
}
aliasMap.put(tuple, alias);
return this;
}

/**
* Created By zby on 9:20 2019/1/28
* 数据转换问题
*/
public SqlQuery setTransformer(ResultTransformer transformer) {
if (CommonUtil.isNull(aliasMap)) {
throw new IllegalArgumentException(“ 请添加转换的属性数量 ”);
}
transformer.transformTuple(aliasMap);
this.transformer = transformer;
return this;
}

以集合的方式返回数据:
/**
* Created By zby on 17:02 2019/1/29
* 设置查找参数
*/
public SqlQuery setParamter(int start, Object param) {
if (CommonUtil.isNull(columnParamer))
columnParamer = new HashMap<>();
columnParamer.put(start, param);
return this;
}

/**
* Created By zby on 16:41 2019/1/24
* 查找值
*/
public List<T> list() {
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
statement = connection.prepareStatement(sql);
if (CommonUtil.isNotNull(columnParamer)) {
for (Map.Entry<Integer, Object> entry : columnParamer.entrySet()) {
int key = entry.getKey();
Object value = entry.getValue();
statement.setObject(key + 1, value);
}
}
resultSet = statement.executeQuery();
PersistentObject persistentObject = new PersistentObject(persistenceClass, resultSet);
if (CommonUtil.isNotNull(aliasMap))
return persistentObject.getPersist(transformer);
return persistentObject.getPersist();
} catch (Exception e) {
e.printStackTrace();
} finally {
SessionClose.closeConnStateResSet(connection, statement, resultSet);
}
return null;
}
返回唯一值
/**
* Created By zby on 16:41 2019/1/24
* 查找值
*/
public T uniqueResult() {
List<T> list = list();
if (CommonUtil.isNull(list))
return null;
if (list.size() > 1)
throw new GenericException(“ 本来需要返回一个对象,却返回 ” + list.size() + “ 个对象 ”);
return list.get(0);
}
测试
@Test
public void testList() {
Session session = new SqlSessionFactory().openSession();
String sql = “SELECT ” +
” customer_name AS customerName, ” +
” `name` AS projectName ” +
“FROM ” +
” project where id >= ? and id <= ?”;
SqlQuery query = session.createSqlQuery(sql);
query.setParamter(0, 1);
query.setParamter(1, 2);
query.addScalar(“customerName”, StandardBasicTypes.STRING)
.addScalar(“projectName”, StandardBasicTypes.STRING);
query.setTransformer(Transforms.aliasToBean(ProjectData.class));
List<ProjectData> projects = query.list();
for (ProjectData project : projects) {
System.out.println(project.getCustomerName() + ” ” + project.getProjectName());
}
}

@Ignore
public void testListNoData() {
Session session = new SqlSessionFactory().openSession();
String sql = “SELECT ” +
” customer_name AS customerName, ” +
” `name` AS projectName ” +
“FROM ” +
” project where id >= ? and id <= ?”;
SqlQuery query = session.createSqlQuery(sql).
setParamter(0, 1).
setParamter(1, 2).
addEntity(Project.class);
List<Project> projects = query.list();
for (Project project : projects) {
System.out.println(project.getCustomerName() + ” ” + project.getGuestCost());
}
}

保存数据
我们这里以 merger 来保存数据,因为这个方法非常的特殊。如果该瞬时态的独享有主键,而且,其在数据库中依旧存在该主键的数据,我们此时就更新数据表。如果数据表中没有当前主键的数据,我们向数据库中添加该对象的值。如果该瞬时态的对象没有主键,我们直接在数据表中添加该对象。
如代码所示:
/**
* Created By zby on 15:41 2019/1/29
* 合并,首先判断 id 是否存在,若 id 存在则更新,若 id 不存在,则保存数据
*/
public T merge(T t) {
if (CommonUtil.isNull(t))
throw new IllegalArgumentException(“ 参数为空 ”);
Class<T> clazz = (Class<T>) t.getClass();
Field[] fields = clazz.getDeclaredFields();
boolean isContainsId = CommonUtil.isNotNull(PropertyUtil.containId(fields)) ? true : false;
long id = PropertyUtil.getIdValue(fields, t, propertyAccessor);
if (isContainsId) {
return id > 0L ? update(t) : save(t);
}
return save(t);
}

/**
* Created By zby on 17:37 2019/1/29
* 保存数据
*/
public T save(T t) {
if (CommonUtil.isNull(t))
throw new RuntimeException(“ 不能保存空对象 ”);
PreparedStatement statement = null;
ResultSet resultSet = null;
StringBuilder columnJoint = new StringBuilder();
StringBuilder columnValue = new StringBuilder();
try {
Field[] fields = t.getClass().getDeclaredFields();
String sql = ” insert into ” + ClassUtil.getClassNameByGenericity(t) + “(“;
for (int i = 0; i < fields.length; i++) {
String propertyName = fields[i].getName();
Object propertyValue = propertyAccessor.getPropertyValue(t, propertyName);
if (CommonUtil.isNotNull(propertyValue)) {
String columnName = PropertyUtil.propertyNameTransformColumnName(propertyName, true);
if (StandardBasicTypes.BOOLEAN.equalsIgnoreCase(fields[i].getGenericType().toString())) {
columnJoint.append(“is_” + columnName + “,”);
columnValue.append(propertyValue + “,”);
} else if (StandardBasicTypes.LONG.equalsIgnoreCase(fields[i].getGenericType().toString())
|| StandardBasicTypes.FLOAT.equalsIgnoreCase(fields[i].getGenericType().toString())
|| StandardBasicTypes.DOUBLE.equalsIgnoreCase(fields[i].getGenericType().toString())
|| StandardBasicTypes.INTEGER.equalsIgnoreCase(fields[i].getGenericType().toString())) {
columnJoint.append(columnName + “,”);
columnValue.append(propertyValue + “,”);
} else if (StandardBasicTypes.DATE.equalsIgnoreCase(fields[i].getGenericType().toString())) {
columnJoint.append(columnName + “,”);
columnValue.append(“‘” + DateUtil.SIMPLE_DATE_FORMAT.format((Date) propertyValue) + “‘,”);
} else {
columnJoint.append(columnName + “,”);
columnValue.append(“‘” + propertyValue + “‘,”);
}
}
}
columnJoint = StringUtil.replace(columnJoint, “,”);
columnValue = StringUtil.replace(columnValue, “,”);
sql += columnJoint + “) VALUES(” + columnValue + “)”;
System.out.println(sql);
statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
statement.executeUpdate();
resultSet = statement.getGeneratedKeys();
while (resultSet.next()) {
return load((Class<T>) t.getClass(), resultSet.getLong(1));
}
return t;
} catch (SQLException e) {
System.out.println(“ 保存数据出错, 实体对象为 =” + t);
e.printStackTrace();
} finally {
SessionClose.closeConnStateResSet(connection, statement, resultSet);
}
return null;
}
测试代码:
@Test
public void testSave() {
Session session = new SqlSessionFactory().getCurrentSession();
Project project = new Project();
project.setCustomerName(“hhhh”);
project.setCreateDatetime(new Date());
project.setDeleted(true);
project = (Project) session.save(project);
System.out.println(project.getId());
}

通过 id 加载对象
有时,我们只要根据当前对象的 id,获取当前对象的全部信息,因而,我们可以这样写:
/**
* Created By zby on 16:36 2019/1/29
* 通过 id 获取对象
*/
public T load(Class<T> clazz, Long id) {
if (CommonUtil.isNull(clazz))
throw new IllegalArgumentException(“ 参数为空 ”);
String className = ClassUtil.getClassNameByClass(clazz);
String sql = ” select * from ” + className + ” where id= ? “;
SqlQuery query = createSqlQuery(sql)
.setParamter(0, id)
.addEntity(clazz);
return (T) query.uniqueResult();
}
测试代码:
@Test
public void testload() {
Session session = new SqlSessionFactory().openSession();
Project project = (Project) session.load(Project.class, 4L);
System.out.println(project);
}

回收连接对象
当我们使用完该连接对象后,需要将对象放回到容器中,而不是直接调用 connection.close() 方法, 而是调用这个方法:
/**
* Created By zby on 16:10 2019/3/17
* 获取容器的对象,如果是关闭 session,则将连接对象放回到容器中
* 如果是开启 session,则从容器中删除该连接对象
*/
protected static List<Connection> getConnections() {
return connections;
}

/**
* Created By zby on 22:45 2019/1/23
* <p>
* 当关闭当前会话时,这并非真正的关闭会话
* 只是将连接对象放回到连接池中
*/
public static void closeConn(Connection connection) {
SqlSessionFactory.getConnections().add(connection);
}
总结
写框架其实是不难的,难就难在如何设计框架。或者说,难就难在基础不牢。如果基础打不牢的话,很难网上攀升。

退出移动版