# sagacity-sqltoy **Repository Path**: packagejava/sagacity-sqltoy ## Basic Information - **Project Name**: sagacity-sqltoy - **Description**: Java真正智慧的ORM框架,融合JPA功能和最佳的sql编写及查询模式、独创的缓存翻译、最优化的分页、并提供无限层级分组汇总、同比环比、行列转换、树形排序汇总、sql自适配不同数据库、分库分表、多租户、数据加解密、脱敏以及面向复杂业务和大规模数据分析等痛点、难点问题项目实践经验分享的一站式解决方案! - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: 5.6 - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 197 - **Created**: 2025-06-10 - **Last Updated**: 2025-06-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README

maven

# WORD版详细文档(完整) ## 请见:docs/睿智平台SqlToy5.6 使用手册.doc ## [在线文档(持续完善中)](https://sagframe.github.io/sqltoy-docs/) ## [xml中sql完整配置](https://github.com/sagframe/sqltoy-online-doc/blob/master/docs/sqltoy/search.md) # [github地址](https://github.com/sagframe/sagacity-sqltoy) # [sqltoy Lambda](https://gitee.com/gzghde/sqltoy-plus) # [sqltoy管理系统脚手架](https://github.com/CoCoTeaNet/sra-admin) # [sqltoy idea 插件](https://github.com/imyuyu/sqltoy-idea-plugin) # 范例演示项目 ## 快速集成演示项目 * https://gitee.com/sagacity/sqltoy-helloworld * 阅读其readme.md学习 ## 快速上手项目 * https://gitee.com/sagacity/sqltoy-quickstart * 阅读其readme.md学习 ## POJO和DTO 严格分层演示项目 * https://gitee.com/sagacity/sqltoy-strict ## sharding分库分表演示 * https://gitee.com/sagacity/sqltoy-showcase/tree/master/trunk/sqltoy-sharding ## dynamic-datasource多数据源范例 * https://gitee.com/sagacity/sqltoy-showcase/tree/master/trunk/sqltoy-dynamic-datasource ## nosql演示(mongodb和elasticsearch) * https://gitee.com/sagacity/sqltoy-showcase/tree/master/trunk/sqltoy-nosql ## sqltoy基于xml配置演示 * https://gitee.com/sagacity/sqltoy-showcase/tree/master/trunk/sqltoy-showcase # QQ 交流群:531812227 # 最新版本 * 5.6.48 LTS (jdk17+/springboot3.x)/5.6.48.jre8 发版日期: 2025-6-10 (jre8版本EOL:2025-12-31) ```xml com.sagframe sagacity-sqltoy-spring-starter 5.6.48 ``` # 1. 前言 ## 1.1 sqltoy-orm是什么 sqltoy-orm是比JPA+MyBatis更加贴合项目的orm框架,具有jpa式的对象CRUD的同时具有比myBatis(plus)更直观简洁性能强大的查询功能。 ### JPA部分 * 类似JPA的对象化CRUD、对象级联加载和新增、更新 * 支持通过POJO生成DDL以及直接向数据库创建表 * 强化update操作,提供弹性字段修改能力,不同于hibernate先load后修改,而是一次数据库交互完成修改,确保了高并发场景下数据的准确性 * 改进了级联修改,提供了先删除或者先置无效,再覆盖的操作选项 * 增加了updateFetch、updateSaveFetch功能,强化针对强事务高并发场景的处理,类似库存台账、资金台账,实现一次数据库交互,完成锁查询、不存在则插入、存在则修改,并返回修改后的结果 * 增加了树结构封装,便于统一不同数据库树型结构数据的递归查询 * 支持分库分表、支持多种主键策略(额外支持基于redis的产生特定规则的业务主键)、加密存储、数据版本校验 * 提供了公共属性赋值(创建人、修改人、创建时间、修改时间、租户)、扩展类型处理等 * 提供了多租户统一过滤和赋值、提供了数据权限参数带入和越权校验 ### 查询部分 * 极为直观的sql编写方式,便于从客户端<-->代码 双向快速迁移,便于后期变更维护 * 支持缓存翻译、反向缓存匹配key代替like模糊查询 * 提供了跨数据库支持能力:不同数据库的函数自动转换适配,多方言sql根据实际环境自动匹配、多数据库同步测试,大幅提升了产品化能力 * 提供了取top记录、随机记录等特殊场景的查询功能 * 提供了最强大的分页查询机制:1)自动优化count语句;2)提供基于缓存的分页优化,避免每次都执行count查询;3)提供了独具特色的快速分页;4)提供了并行分页 * 提供了分库分表能力 * 提供了在管理类项目中极为价值的:分组汇总计算、行列转换(行转列、列转行)、同比环比、树形排序、树形汇总 相关算法自然集成 * 提供了基于查询的层次化数据结构封装 * 提供了大量辅助功能:数据脱敏、格式化、条件参数预处理等 ### 支持多种数据库 * 常规的mysql、oracle、db2、postgresql、 sqlserver、dm、kingbase、sqlite、h2、 oceanBase、polardb、gaussdb、tidb、oscar(神通)、瀚高、mogdb、vastbase、stardb、TDSQL * 支持分布式olap数据库: clickhouse、doris、StarRocks、greenplum、impala(kudu) * 支持elasticsearch、mongodb * 支持所有在jdbc协议上基于sql的查询和修改 # 2. 快速特点说明 ## 2.1 对象操作跟jpa类似并有针对性加强(包括级联) * 通过quickvo工具从数据库生成对应的POJO,注入sqltoy自带的LightDao完成全部操作 ```java //三步曲:1、quickvo生成pojo,2、完成yml配置;3、service中注入dao(无需自定义各种dao) @Autowired LightDao lightDao; StaffInfoVO staffInfo = new StaffInfoVO(); //保存 lightDao.save(staffInfo); //删除 lightDao.delete(new StaffInfoVO("S2007")); //public Long update(Serializable entity, String... forceUpdateProps); // 这里对photo 属性进行强制修改,其他为null自动会跳过 lightDao.update(staffInfo, "photo"); //只更新指定字段 lightDao.update().updateFields("name","status").one(entity); lightDao.update().updateFields("name","status").many(entities); //深度修改,不管是否null全部字段修改 lightDao.updateDeeply(staffInfo); //批量保存或修改 lightDao.saveOrUpdateAll(staffList); //批量保存 lightDao.saveAll(staffList); //并行保存 lightDao.save().parallelConfig(ParallelConfig.create().groupSize(5000).maxThreads(10)).many(entities) ............... lightDao.loadByIds(StaffInfoVO.class,"S2007") //唯一性验证 lightDao.isUnique(staffInfo, "staffCode"); ``` ## 2.2 支持代码中对象查询 * sqltoy 中统一的规则是代码中可以直接传sql也可以是对应xml文件中的sqlId ```java /** * @todo 通过对象传参数,简化paramName[],paramValue[] 模式传参 * @param * @param sqlOrNamedSql 可以是具体sql也可以是对应xml中的sqlId * @param entity 通过对象传参数,并按对象类型返回结果 */ public List find(final String sqlOrNamedSql, final T entity); ``` * 基于对象单表查询,并带缓存翻译 ```java public Page findStaff(Page pageModel, StaffInfoVO staffInfoVO) { // sql可以直接在代码中编写,复杂sql建议在xml中定义 // 单表entity查询场景下sql字段可以写成java类的属性名称 return findPageEntity(pageModel, StaffInfoVO.class, EntityQuery.create() .where("#[staffName like :staffName]#[and createTime>=:beginDate]#[and createTime<=:endDate]") .values(staffInfoVO)); } ``` * 对象式查询后修改或删除 ```java //演示代码中非直接sql模式设置条件模式进行记录修改 public Long updateByQuery() { return lightDao.updateByQuery(StaffInfoVO.class, EntityUpdate.create().set("createBy", "S0001") .where("staffName like ?").values("张")); } //代码中非直接sql模式设置条件模式进行记录删除 lightDao.deleteByQuery(StaffInfoVO.class, EntityQuery.create().where("status=?").values(0)); ``` ## 2.3 极致朴素的sql编写方式 * sqltoy 的写法(一眼就看明白sql的本意,后面变更调整也非常便捷,copy到数据库客户端里稍作调整即可执行) * sqltoy条件组织原理很简单: 如 #[order_id=:orderId] 等于if(:orderId<>null) sql.append(order_id=:orderId);#[]内只要有一个参数为null即剔除 * 支持多层嵌套:如 #[and t.order_id=:orderId #[and t.order_type=:orderType]] * 条件判断保留#[@if(:param>=xx ||:param<=xx1) sql语句] 这种@if()高度灵活模式,为特殊复杂场景下提供便利 ```xml //1、 条件值处理跟具体sql分离 //2、 将条件值前置通过filters 定义的通用方法加工规整(大多数是不需要额外处理的) =:beginAndEndDate[0]] #[and t.TRANS_DATE<:beginAndEndDate[1]] #[and (t.TECH_GROUP,t.PROD_GROUP) in (:techGroups,:prodGroups)] #[order by @value(:orderField) @value(:orderWay)] ]]> ``` ![alt](./docs/images/sqltoy-sql.png) * 同等功能mybatis写法 ```xml ``` ## 2.4 天然防止sql注入,执行过程: * 假设sql语句如下 ```sql select * from sqltoy_device_order_info t where #[t.ORGAN_ID in (:authedOrganIds)] #[and t.TRANS_DATE>=:beginDate] #[and t.TRANS_DATE<:endDate] ``` * java调用过程 ```java lightDao.find(sql, MapKit.keys("authedOrganIds","beginDate", "endDate").values(authedOrganIdAry,beginDate,null), DeviceOrderInfoVO.class); ``` * 最终执行的sql是这样的: ```sql select * from sqltoy_device_order_info t where t.ORDER_ID=? and t.ORGAN_ID in (?,?,?) and t.TRANS_DATE>=? ``` * 然后通过: pst.set(index,value) 设置条件值 ## 2.5 最为极致的分页 ### 2.5.1 分页特点说明 * 1、快速分页:@fast() 实现先取单页数据然后再关联查询,极大提升速度。 * 2、分页优化器:page-optimize 让分页查询由两次变成1.3~1.5次(用缓存实现相同查询条件的总记录数量在一定周期内无需重复查询) * 3、sqltoy的分页取总记录的过程不是简单的select count(1) from (原始sql);而是智能判断是否变成:select count(1) from 'from后语句', 并自动剔除最外层的order by * 4、sqltoy支持并行查询:parallel="true",同时查询总记录数和单页数据,大幅提升性能 * 5、在极特殊情况下sqltoy分页考虑是最优化的,如:with t1 as (),t2 as @fast(select * from table1) select * from xxx 这种复杂查询的分页的处理,sqltoy的count查询会是:with t1 as () select count(1) from table1, 如果是:with t1 as @fast(select * from table1) select * from t1 ,count sql 就是:select count(1) from table1 ### 2.5.2 分页sql示例 ```xml ``` ### 2.5.3 分页java代码调用 ```java /** * 基于对象传参数模式 */ public void findPageByEntity() { StaffInfoVO staffVO = new StaffInfoVO(); // 作为查询条件传参数 staffVO.setStaffName("陈"); // 使用了分页优化器 // 第一次调用:执行count 和 取记录两次查询 // 第二次调用:在特定时效范围内count将从缓存获取,只会执行取单页记录查询 Page result = lightDao.findPage(new Page(), "sqltoy_fastPage", staffVO); } ``` ## 2.6 极为巧妙的缓存翻译,将多表关联查询尽量变成单表 * 1、 通过缓存翻译: 将代码转化为名称,避免关联查询,极大简化sql并提升查询效率 * 2、 通过缓存名称模糊匹配: 获取精准的编码作为条件,避免关联like 模糊查询 ```java //支持对象属性注解模式进行缓存翻译 @Translate(cacheName = "dictKeyName", cacheType = "DEVICE_TYPE", keyField = "deviceType") private String deviceTypeName; @Translate(cacheName = "staffIdName", keyField = "staffId") private String staffName; ``` ```xml ``` ## 2.7 并行查询 * 接口规范 ```java // parallQuery 面向查询(不要用于事务操作过程中),sqltoy提供强大的方法,但是否恰当使用需要使用者做合理的判断 /** * @TODO 并行查询并返回一维List,有几个查询List中就包含几个结果对象,paramNames和paramValues是全部sql的条件参数的合集 * @param parallQueryList * @param paramNames * @param paramValues */ public List> parallQuery(List parallQueryList, String[] paramNames, Object[] paramValues); ``` * 使用范例 ```java //定义参数 String[] paramNames = new String[] { "userId", "defaultRoles", "deployId", "authObjType" }; Object[] paramValues = new Object[] { userId, defaultRoles, GlobalConstants.DEPLOY_ID, SagacityConstants.TempAuthObjType.GROUP }; // 使用并行查询同时执行2个sql,条件参数是2个查询的合集 List> list = super.parallQuery( Arrays.asList( ParallQuery.create().sql("webframe_searchAllModuleMenus").resultType(TreeModel.class), ParallQuery.create().sql("webframe_searchAllUserReports").resultType(TreeModel.class)), paramNames, paramValues); ``` ## 2.8 跨数据库支持 * 1、提供类似hibernate性质的对象操作,自动生成相应数据库的方言。 * 2、提供了常用的:分页、取top、取随机记录等查询,避免了各自不同数据库不同的写法。 * 3、提供了树形结构表的标准钻取查询方式,代替以往的递归查询,一种方式适配所有数据库。 * 4、sqltoy提供了大量基于算法的辅助实现,较大程度上用算法代替了以往的sql,实现了跨数据库 * 5、sqltoy提供了函数替换功能,比如可以让oracle的语句在mysql或sqlserver上执行(sql加载时将函数替换成了mysql的函数),较大程度上实现了代码的产品化。 default:SubStr\Trim\Instr\Concat\Nvl 函数;可以参见org.sagacity.sqltoy.plugins.function.Nvl 代码实现 ```properties # 开启sqltoy默认的函数自适配转换函数 spring.sqltoy.functionConverts=default # 如在mysql场景下同时测试其他类型数据库,验证sql适配不同数据库,主要用于产品化软件 spring.sqltoy.redoDataSources[0]=pgdb # 也可以自定义函数来实现替换Nvl # spring.sqltoy.functionConverts=default,com.yourpackage.Nvl # 启用框架自带Nvl、Instr # spring.sqltoy.functionConverts=Nvl,Instr # 启用自定义Nvl、Instr # spring.sqltoy.functionConverts=com.yourpackage.Nvl,com.yourpackage.Instr ``` * 6、通过sqlId+dialect模式,可针对特定数据库写sql,sqltoy根据数据库类型获取实际执行sql,顺序为: dialect_sqlId->sqlId_dialect->sqlId, 如数据库为mysql,调用sqlId:sqltoy_showcase,则实际执行:sqltoy_showcase_mysql ```xml ``` ## 2.9 提供行列转换、分组汇总、同比环比、树型排序汇总等 * 水果销售记录表 品类|销售月份|销售笔数|销售数量(吨)|销售金额(万元) ----|-------|-------|----------|------------ 苹果|2019年5月|12 | 2000|2400 苹果|2019年4月|11 | 1900|2600 苹果|2019年3月|13 | 2000|2500 香蕉|2019年5月|10 | 2000|2000 香蕉|2019年4月|12 | 2400|2700 香蕉|2019年3月|13 | 2300|2700 ### 2.9.1 行转列(列转行也支持) ```xml ``` * 效果
品类 2019年3月 2019年4月 2019年5月
笔数数量总金额 笔数数量总金额 笔数数量总金额
香蕉 13 2300 2700 12 2400 2700 10 2000 2000
苹果 13 2000 2500 11 1900 2600 12 2000 2400
### 2.9.2 分组汇总、求平均(可任意层级) ```xml ``` * 效果 品类|销售月份|销售笔数|销售数量(吨)|销售金额(万元) ----|-------|-------|----------|------------ 总计| | 71 | 12600 |14900 小计| | 36 | 5900 | 7500 苹果|2019年5月|12 | 2000|2400 苹果|2019年4月|11 | 1900|2600 苹果|2019年3月|13 | 2000|2500 小计| | 35 | 6700|7400 香蕉|2019年5月|10 | 2000|2000 香蕉|2019年4月|12 | 2400|2700 香蕉|2019年3月|13 | 2300|2700 ### 2.9.3 先行转列再环比计算 ```xml ``` * 效果
品类 2019年3月 2019年4月 2019年5月
笔数数量比上月总金额比上月 笔数数量比上月总金额比上月 笔数数量比上月总金额比上月
香蕉 13 2300 2700 12 2400 4.30% 2700 0.00% 10 2000 -16.70% 2000 -26.00%
苹果 13 2000 2500 11 1900 -5.10% 2600 4.00% 12 2000 5.20% 2400 -7.70%
### 2.9.4 树排序汇总 ```xml ``` * 效果
地区 归属地区 销售量
上海 中国 300
    松江 上海     120
    杨浦 上海     116
    浦东 上海     64
江苏 中国 270
    南京 江苏     110
    苏州 江苏     90
    无锡 江苏     70
## 2.10 分库分表 ### 2.10.1 查询分库分表(分库和分表策略可以同时使用) ```xml sql参见quickstart项目:com/sqltoy/quickstart/sqltoy-quickstart.sql.xml 文件 =:beginDate] #[and t.log_date<=:endDate] ]]> =:beginDate #[and t.trans_date<=:endDate] ]]> ``` ### 2.10.2 操作分库分表(vo对象由quickvo工具自动根据数据库生成,且自定义的注解不会被覆盖) @Sharding 在对象上通过注解来实现分库分表的策略配置 参见:com.sqltoy.quickstart.ShardingSearchTest 进行演示 ```java package com.sqltoy.showcase.vo; import java.time.LocalDate; import java.time.LocalDateTime; import org.sagacity.sqltoy.config.annotation.Sharding; import org.sagacity.sqltoy.config.annotation.SqlToyEntity; import org.sagacity.sqltoy.config.annotation.Strategy; import com.sagframe.sqltoy.showcase.vo.base.AbstractUserLogVO; /* * db则是分库策略配置,table 则是分表策略配置,可以同时配置也可以独立配置 * 策略name要跟spring中的bean定义name一致,fields表示要以对象的哪几个字段值作为判断依据,可以一个或多个字段 * maxConcurrents:可选配置,表示最大并行数 maxWaitSeconds:可选配置,表示最大等待秒数 */ @Sharding(db = @Strategy(name = "hashBalanceDBSharding", fields = { "userId" }), // table = @Strategy(name = "hashBalanceSharding", fields = {"userId" }), maxConcurrents = 10, maxWaitSeconds = 1800) @SqlToyEntity public class UserLogVO extends AbstractUserLogVO { private static final long serialVersionUID = 1296922598783858512L; /** default constructor */ public UserLogVO() { super(); } } ``` # 3.集成说明 ## 3.1 参见trunk 下面的quickstart,并阅读readme.md进行上手 ```java package com.sqltoy.quickstart; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import org.springframework.transaction.annotation.EnableTransactionManagement; /** * * @project sqltoy-quickstart * @description quickstart 主程序入口 * @author zhongxuchen * @version v1.0, Date:2020年7月17日 * @modify 2020年7月17日,修改说明 */ @SpringBootApplication @ComponentScan(basePackages = { "com.sqltoy.config", "com.sqltoy.quickstart" }) @EnableTransactionManagement public class SqlToyApplication { /** * @param args */ public static void main(String[] args) { SpringApplication.run(SqlToyApplication.class, args); } } ``` ## 3.2 application.properties sqltoy部分配置 ```properties # sqltoy config spring.sqltoy.sqlResourcesDir=classpath:com/sqltoy/quickstart spring.sqltoy.translateConfig=classpath:sqltoy-translate.xml spring.sqltoy.debug=true #spring.sqltoy.reservedWords=status,sex_type #dataSourceSelector: org.sagacity.sqltoy.plugins.datasource.impl.DefaultDataSourceSelector #spring.sqltoy.defaultDataSource=dataSource # 提供统一公共字段赋值(源码参见quickstart) spring.sqltoy.unifyFieldsHandler=com.sqltoy.plugins.SqlToyUnifyFieldsHandler #spring.sqltoy.printSqlTimeoutMillis=200000 ``` ## 3.3 缓存翻译的配置文件sqltoy-translate.xml ```xml =:lastUpdateTime ]]> =:lastUpdateTime ]]> =:lastUpdateTime ]]> ``` * 实际业务开发使用,直接利用SqlToyCRUDService 就可以进行常规的操作,避免简单的对象操作自己写service, 另外针对复杂逻辑则自己写service直接通过调用sqltoy提供的:LightDao完成数据库交互操作! ```java @RunWith(SpringRunner.class) @SpringBootTest(classes = SqlToyApplication.class) public class CrudCaseServiceTest { @Autowired private SqlToyCRUDService sqlToyCRUDService; /** * 创建一条员工记录 */ @Test public void saveStaffInfo() { StaffInfoVO staffInfo = new StaffInfoVO(); staffInfo.setStaffId("S190715005"); staffInfo.setStaffCode("S190715005"); staffInfo.setStaffName("测试员工4"); staffInfo.setSexType("M"); staffInfo.setEmail("test3@aliyun.com"); staffInfo.setEntryDate(LocalDate.now()); staffInfo.setStatus(1); staffInfo.setOrganId("C0001"); staffInfo.setPhoto(FileUtil.readAsBytes("classpath:/mock/staff_photo.jpg")); staffInfo.setCountry("86"); sqlToyCRUDService.save(staffInfo); } } ``` # 4. sqltoy关键代码说明 ## 4.1 sqltoy-orm 主要分以下几个部分: - SqlToyDaoSupport:提供给开发者Dao继承的基本Dao,集成了所有对数据库操作的方法。 - LightDao:提供给开发者快捷使用的Dao,让开发者只关注写Service业务逻辑代码,在service中直接调用lightDao - DialectFactory:数据库方言工厂类,sqltoy根据当前连接的方言调用不同数据库的实现封装。 - SqlToyContext:sqltoy上下文配置,是整个框架的核心配置和交换区,spring配置主要是配置sqltoyContext。 - EntityManager:封装于SqlToyContext,用于托管POJO对象,建立对象跟数据库表的关系。sqltoy通过SqlToyEntity注解扫描加载对象。 - ScriptLoader:sql配置文件加载解析器,封装于SqlToyContext中。sql文件严格按照*.sql.xml规则命名。 - TranslateManager:缓存翻译管理器,用于加载缓存翻译的xml配置文件和缓存实现类,sqltoy提供了接口并提供了默认基于ehcache的本地缓存实现,这样效率是最高的,而redis这种分布式缓存IO开销太大,缓存翻译是一个高频度的调用,一般会缓存注入员工、机构、数据字典、产品品类、地区等相对变化不频繁的稳定数据。 - ShardingStragety:分库分表策略管理器,4.x版本之后策略管理器并不需要显式定义,只有通过spring定义,sqltoy会在使用时动态管理。 ## 4.2 快速阅读理解sqltoy: - 从LightDao作为入口,了解sqltoy提供的所有功能 - SqlToyDaoSupport 是LightDao具体功能实现。 - 从DialectFactory会进入不同数据库方言的实现入口。可以跟踪看到具体数据库的实现逻辑。你会看到oracle、mysql等分页、取随机记录、快速分页的封装等。 - EntityManager:你会找到如何扫描POJO并构造成模型,知道通过POJO操作数据库实质会变成相应的sql进行交互。 - ParallelUtils:对象分库分表并行执行器,通过这个类你会看到分库分表批量操作时如何将集合分组到不同的库不同的表并进行并行调度的。 - SqlToyContext:sqltoy配置的上下文,通过这个类可以看到sqltoy全貌。 - PageOptimizeUtils:可以看到分页优化默认实现原理。