目录
- 第一版:简单实现
- 第二版:将封装过程提取出来
- 第三版:通过注解+反射实现
- 第四版:通过注解+反射+泛型实现所有entity类的封装
- 第五版:Javassist动态生成字节码,达到与第一版硬编码实现的效率
需求介绍
CREATE TABLE `t_user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=24563 DEFAULT CHARSET=utf8mb4;
-- 可重复执行该语句以达到足够的数据量,我的表里有20万条数据
INSERT INTO `t_user`(`user_name`, `password`) SELECT user_name,`password` FROM t_user
select * from t_user limit 20000
/**
* 用户实体
*/
public class UserEntity {
/**
* 用户 Id
*/
public int _userId;
/**
* 用户名
*/
public String _userName;
/**
* 密码
*/
public String _password;
}
查询t_user并封装成UserEntity。这就是ORM(对象关系映射)
程序用到了两个依赖javassist
会在后面的版本中使用到。
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
</dependencies>
第一版:简单实现
这一版就是sql执行的结果循环遍历通过硬编码的形式封装成UserEntity
对象
package org.ormtest.step000;
import org.ormtest.step000.entity.UserEntity;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class App000 {
static public void main(String[] argvArray) throws Exception {
(new App000()).start();
}
private void start() throws Exception {
// 加载 Mysql 驱动
Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
// 数据库连接地址
String dbConnStr = "jdbc:mysql://127.0.0.1:3306/ormtest?useAffectedRows=true&serverTimezone=Asia/Shanghai";
// 创建数据库连接
Connection conn = DriverManager.getConnection(dbConnStr,"root","123456");
// 简历陈述对象
Statement stmt = conn.createStatement();
// 创建 SQL 查询
String sql = "select * from t_user limit 20000";
// 执行查询
ResultSet rs = stmt.executeQuery(sql);
// 获取开始时间
long t0 = System.currentTimeMillis();
while (rs.next()) {
// 创建新的实体对象
UserEntity ue = new UserEntity();
ue._userId = rs.getInt("user_id");
ue._userName = rs.getString("user_name");
ue._password = rs.getString("password");
//
// 关于上面这段代码,
// 我们是否可以将其封装到一个助手类里??
// 这样做的好处是:
// 当实体类发生修改时, 只需要改助手类就可以了...
//
}
// 获取结束时间
long t1 = System.currentTimeMillis();
// 关闭数据库连接
stmt.close();
conn.close();
// 打印实例化花费时间
System.out.println("实例化花费时间 = " + (t1 - t0) + "ms");
}
}
输出结果
实例化花费时间 = 31ms
第二版:将封装过程提取出来
package org.ormtest.step010;
import org.ormtest.step010.entity.UserEntity;
import org.ormtest.step010.entity.UserEntity_Helper;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class App010 {
static public void main(String[] argvArray) throws Exception {
(new App010()).start();
}
/**
* 测试开始
*/
private void start() throws Exception {
// 加载 Mysql 驱动
Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
// 数据库连接地址
String dbConnStr = "jdbc:mysql://127.0.0.1:3306/ormtest?user=root&password=123456&serverTimezone=Asia/Shanghai";
// 创建数据库连接
Connection conn = DriverManager.getConnection(dbConnStr);
// 简历陈述对象
Statement stmt = conn.createStatement();
// 创建 SQL 查询
String sql = "select * from t_user limit 20000";
// 执行查询
ResultSet rs = stmt.executeQuery(sql);
// 创建助手类
UserEntity_Helper helper = new UserEntity_Helper();
// 获取开始时间
long t0 = System.currentTimeMillis();
while (rs.next()) {
// 创建新的实体对象,
// 这里已经把创建过程封装到助手类中了...
// 这么做的好处 step000 中已经提到过了
UserEntity ue = helper.create(rs);
}
// 获取结束时间
long t1 = System.currentTimeMillis();
// 关闭数据库连接
stmt.close();
conn.close();
// 打印实例化花费时间
System.out.println("实例化花费时间 = " + (t1 - t0) + "ms");
}
}
UserEntity_Helper类
package org.ormtest.step010.entity;
import java.sql.ResultSet;
/**
* 用户实体助手类
*/
public class UserEntity_Helper {
/**
* 将数据集装换为实体对象
*
* @param rs 数据集
* @return
* @throws Exception
*/
public UserEntity create(ResultSet rs) throws Exception {
if (null == rs) {
return null;
}
// 创建新的实体对象
UserEntity ue = new UserEntity();
ue._userId = rs.getInt("user_id");
ue._userName = rs.getString("user_name");
ue._password = rs.getString("password");
//
// 都是硬编码会不会很累?
// 而且要是 UserEntity 和 t_user 加字段,
// 还得改代码...
// 为何不尝试一下反射?
return ue;
}
}
这一版将写在主程序里的封装对象的逻辑提取到了一个Helper类中,这样主程序就不用修改了,只需要修改Helper类。
然后我们来考虑一下硬编码的问题。如果t_user新增字段还是得每次都修改Helper类,这种设计就不够方便。
第三版:通过注解+反射实现
定义一注解类
/**
* 数据表中的列
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
/**
* 列名称
*
* @return
*/
String name();
}
修改实体类加上注解
package org.ormtest.step020.entity;
/**
* 用户实体
*/
public class UserEntity {
/**
* 用户 Id
*/
@Column(name = "user_id")
public int _userId;
/**
* 用户名
*/
@Column(name = "user_name")
public String _userName;
/**
* 密码
*/
@Column(name = "password")
public String _password;
}
修改Helper类
package org.ormtest.step020.entity;
import java.lang.reflect.Field;
import java.sql.ResultSet;
/**
* 用户实体助手类
*/
public class UserEntity_Helper {
/**
* 将数据集装换为实体对象
*
* @param rs 数据集
* @return
* @throws Exception
*/
public UserEntity create(ResultSet rs) throws Exception {
if (null == rs) {
return null;
}
// 创建新的实体对象
UserEntity ue = new UserEntity();
// 获取类的字段数组
Field[] fArray = ue.getClass().getFields();
for (Field f : fArray) {
// 获取字段上注解
Column annoColumn = f.getAnnotation(Column.class);
if (annoColumn == null) {
// 如果注解为空,
// 则直接跳过...
continue;
}
// 获取列名称
String colName = annoColumn.name();
// 从数据库中获取列值
Object colVal = rs.getObject(colName);
if (colVal == null) {
// 如果列值为空,
// 则直接跳过...
continue;
}
// 设置字段值
f.set(ue, colVal);
}
return ue;
}
}
通过反射就实现了t_user可以随意添加字段,只要在UserEntity类加上对应的注解就可以了,无需修改Helper类。但还是不够通用,接下来就使用泛型来实现ORM
第四版:通过注解+反射+泛型实现所有entity类的封装
Helper修改为XxxEntity_Helper。 简单来说就是将需要封装的类型通过外部传入,然后create()
方法就将结果封装成传入的类型即可。
package org.ormtest.step030.entity;
import java.lang.reflect.Field;
import java.sql.ResultSet;
/**
* 实体助手类, 这个更通用
*/
public class XxxEntity_Helper {
/**
* 将数据集装换为实体对象
*
* @param entityClazz 实体类
* @param rs 数据集
* @param <TEntity> 实体类
* @return
* @throws Exception
*/
public <TEntity> TEntity create(Class<TEntity> entityClazz, ResultSet rs) throws Exception {
if (null == rs) {
return null;
}
//
// 更通用的助手类,
// 甭管实体类是哪个, 也甭管实体类有多少属性,
// 全灭!
// 但是,
// 就是性能太差了...
//
// 创建新的实体对象
Object newEntity = entityClazz.newInstance();
// 获取类的字段数组
Field[] fArray = entityClazz.getFields();
for (Field f : fArray) {
// 获取字段上注解
Column annoColumn = f.getAnnotation(Column.class);
if (annoColumn == null) {
// 如果注解为空,
// 则直接跳过...
continue;
}
// 获取列名称
String colName = annoColumn.name();
// 从数据库中获取列值
Object colVal = rs.getObject(colName);
if (colVal == null) {
// 如果列值为空,
// 则直接跳过...
continue;
}
// 设置字段值
f.set(newEntity, colVal);
}
return (TEntity) newEntity;
}
}
package org.ormtest.step030;
import org.ormtest.step030.entity.UserEntity;
import org.ormtest.step030.entity.XxxEntity_Helper;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* 主应用程序类
*/
public class App030 {
/**
* 应用程序主函数
*
* @param argvArray 参数数组
* @throws Exception
*/
static public void main(String[] argvArray) throws Exception {
(new org.ormtest.step030.App030()).start();
}
/**
* 测试开始
*/
private void start() throws Exception {
// 加载 Mysql 驱动
Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
// 数据库连接地址
String dbConnStr = "jdbc:mysql://localhost:3306/ormtest?user=root&password=123456&serverTimezone=Asia/Shanghai";
// 创建数据库连接
Connection conn = DriverManager.getConnection(dbConnStr);
// 简历陈述对象
Statement stmt = conn.createStatement();
// 创建 SQL 查询
String sql = "select * from t_user limit 20000";
// 执行查询
ResultSet rs = stmt.executeQuery(sql);
// 创建助手类
XxxEntity_Helper helper = new XxxEntity_Helper();
// 获取开始时间
long t0 = System.currentTimeMillis();
while (rs.next()) {
// 创建新的实体对象
UserEntity ue = helper.create(UserEntity.class, rs);
}
// 获取结束时间
long t1 = System.currentTimeMillis();
// 关闭数据库连接
stmt.close();
conn.close();
// 打印实例化花费时间
System.out.println("实例化花费时间 = " + (t1 - t0) + "ms");
// 注意: 到这里运行时间翻倍了!
// 利用反射确实可以获得良好的通用性,
// 但是却损失了性能...
}
}
输出结果
实例化花费时间 = 71ms
通用性好了但是运行时间翻倍了!这也不是一个好的设计。
第五版:Javassist动态生成字节码,达到与第一版硬编码实现的效率
优化思路:硬编码的效率高,那我们就使用Javassist生成硬编码。
定义抽象的Helper类
package org.ormtest.step050.entity;
import java.sql.ResultSet;
/**
* 抽象的实体助手
*/
public abstract class AbstractEntityHelper {
/**
* 将数据集转换为实体对象
*
* @param rs 数据集
* @return
*
*/
public abstract Object create(ResultSet rs);
}
用来生成EntityHelper的工厂类
package org.ormtest.step050.entity;
import javassist.*;
import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Map;
/**
* 实体助手工厂
*/
public final class EntityHelperFactory {
/**
* 助手字典
*/
private static final Map<Class<?>, AbstractEntityHelper> _entityHelperMap = new HashMap<>();
/**
* 私有化类默认构造器
*/
private EntityHelperFactory() {
}
/**
* 获取帮助
*
* @param entityClazz 实体类
* @return 实体助手
* @throws Exception
*/
public static AbstractEntityHelper getEntityHelper(Class<?> entityClazz) throws Exception {
if (null == entityClazz) {
// 如果参数对象为空,
// 则直接退出!
return null;
}
// 获取帮助对象
AbstractEntityHelper helperObj = _entityHelperMap.get(entityClazz);
if (helperObj != null) {
// 如果帮助对象不为空,
// 则直接返回!
return helperObj;
}
// 使用 Javassist 动态生成 Java 字节码
///
//
// 获取类池
ClassPool pool = ClassPool.getDefault();
pool.appendSystemPath();
//
// 导入相关类, 生成以下代码:
// import java.sql.ResultSet
// import org.ormtest.entity.UserEntity
// import ...
pool.importPackage(ResultSet.class.getName());
pool.importPackage(entityClazz.getName());
// 抽象的助手类
CtClass abstractEntityHelperClazz = pool.getCtClass(AbstractEntityHelper.class.getName());
// 助手实现类名称
final String helperImplClazzName = entityClazz.getName() + "_Helper";
//
// 创建助手类, 会生成如下代码:
// public class UserEntity_Helper extends AbstractEntityHelper { ...
CtClass helperClazz = pool.makeClass(helperImplClazzName, abstractEntityHelperClazz);
//
// 创建默认构造器, 会生成如下代码:
// public UserEntity_Helper() {}
CtConstructor constructor = new CtConstructor(new CtClass[0], helperClazz);
// 空函数体
constructor.setBody("{}");
// 添加默认构造器
helperClazz.addConstructor(constructor);
// 用于创建函数代码字符串
final StringBuffer sb = new StringBuffer();
// 添加一个函数, 也就是实现抽象类中的 create 函数
sb.append("public Object create(java.sql.ResultSet rs) throws Exception {\n");
// 生成以下代码:
// UserEntity obj = new UserEntity();
sb.append(entityClazz.getName())
.append(" obj = new ")
.append(entityClazz.getName())
.append("();\n");
// 通过反射方式获取类的字段数组,
// 并生成代码
///
//
// 获取类的字段数组并生成代码
Field[] fArr = entityClazz.getFields();
for (Field f : fArr) {
// 获取字段上注解
Column annoColumn = f.getAnnotation(Column.class);
if (annoColumn == null) {
// 如果注解为空,
// 则直接跳过...
continue;
}
// 获取列名称
String colName = annoColumn.name();
if (f.getType() == Integer.TYPE) {
// 生成如下代码:
// obj._userId = rs.getInt("user_id");
sb.append("obj.")
.append(f.getName())
.append(" = rs.getInt(\"")
.append(colName)
.append("\");\n");
} else if (f.getType().equals(String.class)) {
// 生成如下代码:
// obj._userName = rs.getString("user_name");
sb.append("obj.")
.append(f.getName())
.append(" = rs.getString(\"")
.append(colName)
.append("\");\n");
} else {
// 不支持的类型...
// 如果需要支持 float、long、boolean 等类型,
// 接着往下写就可以了
}
}
sb.append("return obj;\n");
sb.append("}");
// 创建解析方法
CtMethod cm = CtNewMethod.make(
sb.toString(), helperClazz
);
// 添加方法
helperClazz.addMethod(cm);
// 调试文件
helperClazz.writeFile("C:/Data/Temp+Test/debug-java");
// 获取 Java 类
Class<?> javaClazz = helperClazz.toClass();
// 创建帮助对象实例
///
//
// 创建帮助实例
helperObj = (AbstractEntityHelper) javaClazz.newInstance();
// 添加到字典
_entityHelperMap.put(entityClazz, helperObj);
return helperObj;
}
}
使用
package org.ormtest.step050;
import org.ormtest.step050.entity.AbstractEntityHelper;
import org.ormtest.step050.entity.EntityHelperFactory;
import org.ormtest.step050.entity.UserEntity;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/**
* 主应用程序类
*/
public class App050 {
/**
* 应用程序主函数
*
* @param argvArray 参数数组
* @throws Exception
*/
static public void main(String[] argvArray) throws Exception {
(new App050()).start();
}
/**
* 测试开始
*/
private void start() throws Exception {
// 加载 Mysql 驱动
Class.forName("com.mysql.cj.jdbc.Driver").newInstance();
// 数据库连接地址
String dbConnStr = "jdbc:mysql://localhost:3306/ormtest?user=root&password=123456&serverTimezone=Asia/Shanghai";
// 创建数据库连接
Connection conn = DriverManager.getConnection(dbConnStr);
// 简历陈述对象
Statement stmt = conn.createStatement();
// 创建 SQL 查询
String sql = "select * from t_user limit 20000";
// 执行查询
ResultSet rs = stmt.executeQuery(sql);
// 创建助手类, 这里采用全新设计的工厂类!
AbstractEntityHelper helper = EntityHelperFactory.getEntityHelper(UserEntity.class);
// 读懂上面这一行,
// 恭喜你已经迈入架构师行列...
// 获取开始时间
long t0 = System.currentTimeMillis();
while (rs.next()) {
// 创建新的实体对象
UserEntity ue = (UserEntity) helper.create(rs);
}
// 获取结束时间
long t1 = System.currentTimeMillis();
// 关闭数据库连接
stmt.close();
conn.close();
// 打印赋值时间消耗
System.out.println("赋值花费时间 = " + (t1 - t0) + "ms");
}
}
输出结果
赋值花费时间 = 34ms
与第一版硬编码的运行时间几乎相同。
然后是调试文件,也就是javassist帮我们生成的字节码文件,可以看到与我们第二版封装的Helper几乎相同
package org.ormtest.step050.entity;
import java.sql.ResultSet;
public class UserEntity_Helper extends AbstractEntityHelper {
public UserEntity_Helper() {
}
public Object create(ResultSet var1) throws Exception {
UserEntity var2 = new UserEntity();
var2._userId = var1.getInt("user_id");
var2._userName = var1.getString("user_name");
var2._password = var1.getString("password");
return var2;
}
}
总结
通过动态生成硬编码,既兼顾了反射的便利性,又保证了程序的性能。
这个思想在很多框架中都有使用到
如:jprotobuf,FastJson
第一次先反射一遍生成硬编码,之后再使用就一直都是硬编码了。这样的性能是非常好的。