【Java】Javassist动态生成硬编码实现ORM

目录

  1. 第一版:简单实现
  2. 第二版:将封装过程提取出来
  3. 第三版:通过注解+反射实现
  4. 第四版:通过注解+反射+泛型实现所有entity类的封装
  5. 第五版: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

第一次先反射一遍生成硬编码,之后再使用就一直都是硬编码了。这样的性能是非常好的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值