基准测试:easy-query vs jOOQ

发布: (2025年12月27日 GMT+8 00:45)
8 min read
原文: Dev.to

Source: Dev.to

要为您进行翻译,我需要您提供要翻译的具体文本内容(文章正文)。请粘贴或上传您希望翻译的内容,我会在保持原有格式、Markdown 语法和技术术语不变的前提下,将其翻译成简体中文。

概述

本文展示了比较 easy‑queryjOOQHibernate 的 JMH 基准测试结果。源码分析解释了 easy‑query 为什么能够实现更优的性能。

测试环境

配置
操作系统Windows 10 (Build 26200)
JDKOpenJDK 21.0.9+10‑LTS
数据库H2 2.2.224 (内存)
连接池HikariCP 4.0.3
JMH1.37
easy‑query3.1.66‑preview3
jOOQ3.19.1
Hibernate6.4.1.Final

测试参数

  • 预热:10 次迭代,每次 5 秒
  • 测量:15 次迭代,每次 5 秒
  • Fork 数:3 个 JVM 进程
  • 线程:单线程

基准测试结果

查询操作

测试场景easy‑queryjOOQHibernate胜者
按 ID 查询298 303 ops/s132 786 ops/s264 571 ops/s🏆 easy‑query (相较 jOOQ 提升 2.25×)
查询列表247 088 ops/s68 773 ops/s141 050 ops/s🏆 easy‑query (相较 jOOQ 提升 3.59×)
COUNT382 545 ops/s197 704 ops/s385 362 ops/sHibernate ≈ easy‑query

复杂查询

测试场景easy‑queryjOOQHibernate胜者
JOIN 查询138 963 ops/s5 859 ops/s56 437 ops/s🏆 easy‑query (相较 jOOQ 提升 23.72×)
子查询(GROUP BY + HAVING)100 725 ops/s5 296 ops/s15 594 ops/s🏆 easy‑query (相较 jOOQ 提升 19.01×)

写操作

测试场景easy‑queryjOOQHibernate胜者
单条插入63 866 ops/s50 257 ops/s57 385 ops/s🏆 easy‑query (相较 jOOQ 提升 1.27×)
批量插入(1000)72.06 ops/s43.54 ops/s70.66 ops/s🏆 easy‑query (相较 jOOQ 提升 1.66×)
按 ID 更新125 902 ops/s100 361 ops/s92 470 ops/s🏆 easy‑query (相较 jOOQ 提升 1.25×)

可视化

SELECT BY ID (ops/s – higher is better)
EasyQuery   █████████████████████████████████████████████████ 298,303
Hibernate   ████████████████████████████████████████████     264,571
jOOQ        ████████████                                      132,786

JOIN QUERY (ops/s – higher is better)
EasyQuery   █████████████████████████████████████████████████ 138,963
Hibernate   ████████████████                                   56,437
jOOQ        █                                                5,859

Source:

为什么 easy‑query 如此快速?

easy‑query 在多个层面实现了深度优化,借鉴了 fastjson2 等高性能框架的技术。

1. 使用 Lambda 函数映射替代反射

核心的性能提升来自于用 LambdaMetafactory 生成的函数取代反射方法调用。

传统反射(慢)

Method methodGetId = Bean.class.getMethod("getId");
Bean bean = createInstance();
int value = (Integer) methodGetId.invoke(bean);   // 每次调用都有开销

反射会产生:

  • JVM 本地方法栈入口
  • JNI 层的切换
  • C 级别的检查

easy‑query 的解决方案 —— 生成直接调用 getter 的 lambda:

// sql-core/src/main/java/com/easy/query/core/common/bean/DefaultFastBean.java

/**
 * Bean 的 get/set 方法的快速实现。
 * Lambda getter 调用几乎没有开销。
 * Setter 也几乎没有开销,除第一次创建 lambda 并缓存外。
 */
public class DefaultFastBean implements FastBean {

    private Property getLambdaProperty(FastBeanProperty prop) {
        Class propertyType = prop.getPropertyType();
        Method readMethod = prop.getReadMethod();
        String getFunName = readMethod.getName();
        final MethodHandles.Lookup caller = MethodHandles.lookup();

        // 使用 LambdaMetafactory 生成函数映射
        final CallSite site = LambdaMetafactory.altMetafactory(
                caller,
                "apply",
                MethodType.methodType(Property.class),
                methodType.erase().generic(),
                caller.findVirtual(beanClass, getFunName, MethodType.methodType(propertyType)),
                methodType,
                FLAG_SERIALIZABLE);

        return (Property) site.getTarget().invokeExact();
    }
}

性能对比(fastjson2 基准)

方法10 000 次调用耗时
Method.invoke(反射)25 ms
Lambda 函数映射1 ms

≈ 快 25 倍!

关键优势:

  • 生成的函数可以 缓存并复用
  • 随后的调用等同于普通的 Java 方法调用。
  • 执行完全在 Java 栈上进行——没有 JNI 开销

2. 属性访问器缓存

在启动时(或首次使用时)easy‑query 为每个实体属性创建一个 lambda 并存入缓存:

// Getter 缓存
public Property getBeanGetter(FastBeanProperty prop) {
    // 实际使用时会缓存:
    // return EasyMapUtil.computeIfAbsent(propertyGetterCache, prop.getName(),
    //                                   k -> getLambdaProperty(prop));
    return getLambdaProperty(prop);
}

// Setter 缓存
public PropertySetterCaller getBeanSetter(FastBeanProperty prop) {
    // return EasyMapUtil.computeIfAbsent(propertySetterCache, prop.getName(),
    //                                   k -> getLambdaPropertySetter(prop));
    return getLambdaPropertySetter(prop);
}

// 构造函数缓存
public Supplier getBeanConstructorCreator() {
    // return EasyMapUtil.computeIfAbsent(constructorCache, beanClass,
    //                                   k -> getLambdaCreate());
    return getLambdaCreate();
}
  • 首次访问 —— 生成 lambda(约 50 µs)。
  • 后续访问 —— 使用缓存的 lambda(约 0.1 µs)。

3. 编译期代理生成(APT)

easy‑query 并不是在运行时创建动态代理,而是使用 注解处理工具 (APT) 在编译期间生成代理类:

// 编译期生成的代理类已经知道所有属性
@EntityProxy
public class User {
    // 字段、getter、setter 等在编译时提前生成
}
  • 没有运行时字节码生成 → 启动成本更低。
  • 所有属性元数据以普通 Java 代码形式存在 → 更有利于 JIT 优化。

要点

  • Lambda‑based accessor generation 消除了沉重的反射开销。
  • Caching 将首次开销转化为一次性成本。
  • Compile‑time proxy generation 完全避免了运行时代理的创建。

综上所述,这些技术使得 easy‑query 能够始终超越 jOOQ,并且在复杂查询和批量写入操作中常常胜过 Hibernate。

1. 实体定义与代理(生成)

// Entity class
public class User {
    private String id;
    private String name;
    private Integer age;
}

// Generated proxy class UserProxy
public class UserProxy {
    public StringProperty id() { /* ... */ }
    public StringProperty name() { /* ... */ }
    public IntegerProperty age() { /* ... */ }
}

4. 直接 JDBC 执行

easy-query 直接使用 JDBC API:

// easy-query directly creates PreparedStatement
PreparedStatement ps = EasyJdbcExecutorUtil.createPreparedStatement(
        connection, sql, parameters, easyJdbcTypeHandler);
ResultSet rs = ps.executeQuery();

5. 轻量级 SQL 生成

SQL 是通过简单的字符串拼接构建的:

// Clean SQL building
easyEntityQuery.queryable(User.class)
        .where(u -> u.age().ge(25))
        .orderBy(u -> u.username().desc())
        .limit(10)
        .toList();

生成的 SQL

SELECT id, username, email, age, phone, address
FROM t_user
WHERE age >= ?
ORDER BY username DESC
LIMIT ?

没有复杂的抽象层——只有直接的 SQL 生成。

6. ResultSet 映射优化

结合 Lambda‑函数映射,ResultSet‑到‑对象的转换几乎没有开销:

// Using cached Setter Lambda to set property values
PropertySetterCaller setter = fastBean.getBeanSetter(prop);
setter.call(entity, resultSet.getObject(columnIndex));

这相当于直接调用 entity.setName(value),没有反射开销。

easy‑query 优化概述

优化项easy‑query
使用 Lambda 函数映射而非反射
编译时代理类(APT)
直接 JDBC 执行
属性访问器缓存
轻量级 SQL 生成

结论

easy-query 通过以下方式实现高性能:

  • 核心技术 – 使用 LambdaMetafactory 生成函数映射(与 fastjson2 使用的技术相同)。
  • 编译期优化 – APT 生成的代理类消除了运行时的动态代理。
  • 精简架构 – 直接使用 JDBC,未引入额外的抽象层。
  • 缓存策略 – 函数映射和元数据被缓存以供重复使用。

“Lambda uses LambdaMetafactory to generate function mappings instead of reflection. Generated functions can be cached for reuse, and subsequent calls are entirely within Java stack calls, virtually identical to native method calls.” – Fastjson2 author

easy-query 在整个 ORM 中贯彻了这一理念,提供接近原生 JDBC 的性能,同时仍然具备强类型、Lambda 语法和隐式关联等特性。

相关链接

  • 基准代码:
  • easy‑query GitHub:
  • jOOQ:
Back to Blog

相关文章

阅读更多 »

懒加载 vs. 急加载 & JPA 关系

!Forem 徽标https://media2.dev.to/dynamic/image/width=65,height=,fit=scale-down,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2...