首页 > 开源 > SqlToy6.0.9:Lambda补齐最后一块拼图,一套 ORM 完整覆盖“对象操作+SQL优先"

SqlToy6.0.9:Lambda补齐最后一块拼图,一套 ORM 完整覆盖“对象操作+SQL优先"

OSChina资讯 2026-10-23 11:09 3 阅读 查看原文

 

喜欢 MyBatis-Plus 的顺滑链式,又舍不得 sqltoy 的动态 SQL、缓存翻译和四层分页优化?现在不用二选一了。

SqlToy 是"JPA 式对象操作"与"SQL 优先查询"的融合体:简单的单表 CRUD 走对象,复杂的查询直接写 SQL(#[...] 动态段比 MyBatis 的 <if>/<foreach> 直观一个量级),再加缓存翻译、行转列、跨 25 种数据库方言这些硬能力。过去它相对生态的唯一"短板",是没有一套类型安全的 Lambda 条件 API——单表条件要么手写 where 字符串,要么引入第三方增强包 sqltoy-plus。

6.0.8 把 sqltoy-plus 的能力并入核心并深度重构,6.0.9 完成跨库实证修补与体验细化。 两个版本落地后,从单表 CRUD、多表 join、分组聚合,到 CTE、UNION、@fast 快速分页,lambda 层对 sqltoy 的功能面实现了完整覆盖,同时在线文档全面焕新(128 页、中英双语)。

<dependency>
    <groupId>com.sagframe</groupId>
    <artifactId>sagacity-sqltoy-spring-starter</artifactId>
    <version>6.0.9</version>
</dependency>

一、Lambda 条件体系:三个入口,一套构建器

入口只有三个,分别对应查、改、删:

lightDao.lambdaQuery(StaffInfo.class)   // 查询(单表或 join)
lightDao.lambdaUpdate(StaffInfo.class)  // 更新(单表或 join)
lightDao.lambdaDelete(StaffInfo.class)  // 删除(单表或 join)

设计上最值得称道的一点:单表只是 join 的退化形态。不调用 join 步骤就是单表(条件内部转 EntityQuery/EntityUpdate,复用 sqltoy 实体链路的全部内核能力);调用 leftJoin(...).on(...) 即切换 join 通道拼装完整 SQL。不需要预先选择构建器类型,一套 API 通吃。

典型例子 1:单表动态查询——IfPresent 语法糖让"参数有没有就拼不拼"退化成一行一个条件,再也不用手写 if 判空:

List<StaffInfo> list = lightDao.lambdaQuery(StaffInfo.class)
        .eqIfPresent(StaffInfo::getStatus, status)          // null 自动跳过
        .likeIfPresent(StaffInfo::getStaffName, name)       // 空白串自动跳过
        .inIfPresent(StaffInfo::getOrganId, organIds)       // 空集合自动跳过
        .betweenIfPresent(StaffInfo::getEntryTime, start, end) // 半开区间自动退化为 >= / <=
        .orderByDesc(StaffInfo::getEntryTime)
        .find();

典型例子 2:多表 join 查 VO——字段按实体自动限定别名,AS 别名直接映射 VO 属性:

List<StaffInfoVO> rows = lightDao.lambdaQuery(StaffInfoVO.class)
        .selectAll()
        .from(StaffInfo.class)
        .leftJoin(OrganInfo.class).on(StaffInfo::getOrganId, OrganInfo::getOrganId)
        .select(JoinCol.of(OrganInfo::getOrganName).as("organName"))
        .eqIfPresent(StaffInfo::getStatus, status)
        .find();

典型例子 3:条件更新与表达式自增:

lightDao.lambdaUpdate(StaffInfo.class)
        .set(StaffInfo::getStatus, 2)
        .setExpr(StaffInfo::getLoginCount, "loginCount + 1") // 基于当前值计算
        .eq(StaffInfo::getStaffId, staffId)
        .update();

典型例子 4:分组聚合——JoinCol.sum/avg/count/min/max 配合 groupBy/having,单表聚合无需 join:

List<OrganSalaryStatVO> stats = lightDao.lambdaQuery(OrganSalaryStatVO.class)
        .select(JoinCol.of(StaffInfo::getOrganId).as("organId"),
                JoinCol.sum(StaffInfo::getSalary).as("totalSalary"),
                JoinCol.count(StaffInfo::getStaffId).as("staffCount"))
        .from(StaffInfo.class)
        .groupBy(StaffInfo::getOrganId)
        .having("sum(SALARY) > :min", MapKit.map("min", 10000))
        .find();

二、关键点清单:不只是"能写",更是"写得安全、写得跨库"

  • 类型安全:以 User::getStatus 方法引用组织条件,字段改名、拼写错误编译期即暴露;
  • 终端全家桶:find / getOne / findPage / findTop / findRandom / count / exists,join 通道同样可用;
  • 严格 getOne:匹配多条直接抛异常(含匹配条数),不静默取第一条掩盖条件缺陷;
  • 全表更新守卫:无 where 条件的 update/delete 默认抛异常,显式 .matchAll() 才放行——从 API 层杜绝"忘写 where 的全表 UPDATE"事故;
  • 关联子查询:exists/notExists 用 OuterRef.of(外层字段) 类型安全地表达外层列引用,支持多层嵌套;
  • join 更新/删除:采用可移植的 EXISTS-over-derived 改写,不依赖 MySQL 专有语法,已在 mysql/postgresql/sqlserver/oracle/db2 五类真库验证;
  • 组合查询:CTE(含 WITH RECURSIVE 递归、PostgreSQL MATERIALIZED 物化 hint 自动按库保留/剔除)、@fast 分页下推派生表、UNION / UNION ALL,多个 lambdaQuery 组装成一条大 SQL,参数全局唯一不撞号;
  • 多数据源与分页优化:.dataSource(ds) 指定本次操作的数据源;findPage 支持 .pageOptimize(...) 复用 sqltoy 四层分页优化(并行 count + count 结果缓存);
  • 单表通道白嫖企业级能力:分库分表、缓存翻译、加解密脱敏、租户过滤在单表 lambda 查询上自动生效——这是"并入核心"相对外挂增强包的本质优势;
  • 6.0.9 细化:新增 likeExact 族(用户输入含 % 时按普通字符匹配,不被当通配符放大命中范围)、空 groupBy/having 跳过组装、setExpr 危险形态快速失败。

三、6.0.9:跨库不是"理论上支持",是真库跑出来的

6.0.9 的主题是"跨库实证修补"——每一个修正背后都是真库实测反证:

  • WITH xx AS MATERIALIZED 仅 PostgreSQL 系原生支持,其余库(mysql/sqlserver/oracle/db2 等)解析期自动剔除 hint,同一份 SQL 无需再按方言各写一个 sqlId(PG18/openGauss/金仓/瀚高/Vastbase 保留执行,mysql 9.3/tidb 8.5/mssql 2025 实测反证,8 库真库零跳过);
  • to_char/to_date 的裸 HH token 全库统一为 24 小时制(DB2/H2 真库实测原先按 12 小时输出,属静默错值);to_date 目标为 PG 系时自动改写 to_timestamp,规避时间部分被静默截断;
  • trim(from x) 省略剔除字符形态在 mysql 报 1064、oracle 报 ORA-30000,自动改写为等价 trim/ltrim/rtrim;
  • 分页 count 缓存键由 sqlId 升级为 sqlId@dialect,多数据源跨方言执行时 count 结果不再互相串用。

同版本的 GraalVM native 支持也值得一提:6.0.8 重写了 AOT 体系(修复 5.6 以来实体反射 hints 恒为空的缺陷,按配置动态注册资源与扩展类),showcase 项目 native 镜像实测启动 0.05 秒,CRUD、缓存翻译、lambda 全链路可用——云原生与信创场景的部署形态补齐了。

四、在线文档全面焕新:128 页、中英双语

文档站点(https://sagframe.github.io/sqltoy-docs/ )完成重整,从环境准备、快速上手到企业级特性共 128 页,中英双语全覆盖:

  • 新增 《Lambda 条件查询与更新》 专章:条件方法速查表、动态化惯用法、join/聚合/CTE/UNION 全示例,以及"安全边界与错误语义速查表"——每一处"会抛异常的场景"都写在明面上;
  • 新增 GraalVM Native Image 指引、6.0 升级指南(含 sqltoy-plus 逐项迁移对照表)与 版本更新日志索引;
  • 快速上手推荐 sqltoy-quickstart:clone 后 mvn test 即可运行,内置 H2 内存库,9 节课程从 CRUD 覆盖到真库跨库验证。

五、"完整覆盖"不是口号,是测试矩阵跑出来的

lambda 体系在核心仓库内配有 13 个专项测试类:从 lambda 元信息提取、SQL 组装文本锁定、H2 端到端执行、join 通道、组合查询(CTE/@fast/UNION)到多数据源与回归缺陷测试,其中 LambdaFullCoverageH2Test 以独立数据集对全部条件族、逻辑组合、动态化语法糖、查询终端、更新/删除安全边界做全功能覆盖验证;join 更新/删除与函数适配属发版多库门禁范围,经 mysql/postgresql/sqlserver/oracle/db2 等真实数据库回归。

至此,sqltoy 的能力面形成完整闭环:JPA 式对象操作(CRUD/级联/updateFetch 高并发台账)+ Lambda 条件体系(单表/join/聚合/CTE)+ SQL 模式(#[...] 动态段 + 缓存翻译 + 四层分页优化 + 行列转换/同比环比)+ 企业级特性(分库分表/多租户/脱敏加解密/数据权限)+ 25 种数据库方言 + NoSQL(ES/MongoDB)+ GraalVM native。简单业务一套链式写到底,复杂查询随时切 SQL 模式,两种风格共用同一个 LightDao。

六、升级成本:几乎为零

  • 普通用户:只改版本号为 6.0.9;
  • 原 sqltoy-plus 用户:移除增强包依赖,import 从 com.sagframe.sagacity.sqltoy.plus.* 换成 org.sagacity.sqltoy.lambda.*,对照文档迁移表逐项替换即可,无需任何 @Enable 配置;
  • JDK 8 存量项目:留在 5.6.96.jre8(jre8 最终版,持续可用)。

资源直达

资源 地址
在线文档 https://sagframe.github.io/sqltoy-docs/
Lambda 专章 https://sagframe.github.io/sqltoy-docs/#/query/lambda_query
GitHub https://github.com/sagframe/sagacity-sqltoy
Gitee https://gitee.com/sagacity/sagacity-sqltoy
入门演示 quickstart https://github.com/sagframe/sqltoy-quickstart
全功能演示 showcase https://github.com/sagframe/sqltoy-showcase

一套 SQL 跑遍 25 种数据库,一个 LightDao 用遍对象操作与 Lambda 条件。SqlToy 6.0.9,值得你花十分钟试试。