sqltoy-orm-4.16.1 发版,深度对比 mybatis!

2020年09月28日

致谢:

1、首先要特别感谢大家的积极反馈,提出了非常好的意见,比如通过sqlId组合dialect实现sql跨数据库、@loop循环组织sql 等等,很多东西虽然是极端场景,但充实了sqltoy面对极端场景的应对能力!

开源地址:

更新内容

1、增强产品跨库执行能力,支持sql id=dialect_id或id_dialect ,以id调用优先结合当前数据库dialect组合,如mysql则优先找mysql_id和id_mysql的sql,找不到则执行id对应的sql,从而在函数自适配的基础上进一步增强了跨库能力。
2、增加@loop(loopParam,loopContent,linkSign) 宏嵌入sql,便于极端场景下灵活组合动态sql

<sql id="qstart_loop_sql">
<value>
	<![CDATA[
	 select ORDER_ID
	        @loop(:fields,",:fields[i]")
	 from sqltoy_device_order t 
	 where #[t.ORDER_ID=:orderId]
		   #[@if(size(:staffIds)>0) and (@loop(:staffIds," t.STAFF_ID=':staffIds[i]' "," or ","1","100"))]
		   #[@blank(:startDates) 
		         and ( @loop(:startDates,
				             " t.TRANS_DATE between STR_TO_DATE(':startDates[i]','%Y-%m-%d') 
				               and STR_TO_DATE(':endDates[i]','%Y-%m-%d') " ,
				              " or "))]
	]]>
	</value>
</sql>

3、增强@if() 功能,提供获取size和判断其中包含某个值的功能,@if(size(:statusAry)>0) 和 @if(:statusAry include 1)
4、改进saveOrUpdate 功能,将全主键和无主键进行了区分处理,全主键返回继续做saveAllIgnoreExist操作
5、优化执行日志输出,以一个执行报告形式统一输出
6、进一步改进TranslateManager,简化二次扩展,便于开发者通过扩展实现近实时的缓存更新管理
7、增加树结构List排序,便于页面快速输出

为什么写sqltoy?

  • sqltoy是在2008年使用hibernate jpa的基础上发现了一个极致的动态sql组织模式(sqltoy的发明专利)

    可以看这篇文章: https://blog.csdn.net/iteye_2252/article/details/81683940

  • 2010年又巧妙的结合缓存,实现了缓存翻译,大幅简化了sql并提升了sql的性能(sqltoy的发明专利)
  • 2011~2015年发现了快速分页和分页优化,让特定场景下分页性能大幅提升(sqltoy的发明专利)
  • 2012年,sqltoy实现了类似于hibernate jpa的功能,当然在update、saveOrUpdate等很多方面做了大幅优化,规避了hibernate的缺陷,从而形成了完整的ORM功能体系。
  • 2013年:sqltoy作为sagacity-nebula星云报表框架的底层,为快速配置化在线化实现报表功能发挥了极大的作用。
  • 2015~2018年,sqltoy作为拉卡拉数据平台的底层,实现了分库分表、mongo、elasticsearch等支持,满足了日均千万级、总数据规模达30亿的数据ETL和数据OLAP诉求。
  • 2018~至今:sqltoy作为目前公司ERP、电商、CRM、数据平台的底层,可靠性和质量得到了长足的发展。

基于上述事实:

1、如果放弃sqltoy而使用mybatis等,面对复杂查询将失去很多便捷的手段,建立在其基础上的很多框架和产品就必须重新选择!

2、sqltoy的三个专利体现的功能mybatis和其他框架是无法拥有的,而这三点又对查询极为关键!

3、sqltoy具有良好的特性,非刻意而为,完全可以打造一个属于中国人的ORM框架,而ORM又极为底层和基础!

开始进入深度对比:

  • 平手点: sql热加载、分库分表、保存/批量保存、删除、依据主键加载 
  • 剩下的都比mybatis(plus)强

sqltoy的crud:sqltoy 是类似于jpa式的crud,是无需写sql的,sqltoy提供了SqlToyLazyDao\SqlToyCRUDService,开发者无需写dao。

//保存对象
StaffInfoVO staffInfo = new StaffInfoVO();
staffInfo.setStaffId("S2007")
         .setStaffCode("S2007")
 		 .setPhoto(FileUtil.readAsBytes("classpath:/mock/staff_photo.jpg"))
         .setCountry("86");
sqlToyCRUDService.save(staffInfo);

//可以了解一下sqltoy的update,会自动忽视掉null属性,但可以通过forceUpdateProps来指定强制修改字段
//Long update(Serializable entity, String... forceUpdateProps);
StaffInfoVO staffInfo = new StaffInfoVO();
staffInfo.setStaffId("S2007");
staffInfo.setEmail("test07@139.com");
// 这里对照片进行强制修改
sqlToyCRUDService.update(staffInfo, "photo");


//唯一性验证
StaffInfoVO staffInfo = new StaffInfoVO();
staffInfo.setStaffId("S0006");
staffInfo.setStaffCode("S0006");
Boolean result = sqlToyCRUDService.isUnique(staffInfo, "staffCode");

//还有updateFetch、load(entity,lock)等等

代码中实现查询(纠正一下:sqltoy only xml、only sql的片面认识,sqltoy的参数是sql或者sqlId,jdk15文本块后可以将部分短sql写在代码中)

// @todo 通过对象传参数,简化paramName[],paramValue[] 模式传参
// @param <T>
// @param sqlOrNamedSql 可以是具体sql也可以是对应xml中的sqlId
// @param entity        通过对象传参数,并按对象类型返回结果
public <T extends Serializable> List<T> findBySql(final String sqlOrNamedSql, final T entity);



// 动态条件查询,基于对象传参,一个具有分页、缓存翻译、分页优化的代码比mybatis plus会复杂吗?
PaginationModel<StaffInfoVO> result = sqlToyLazyDao.findEntity(StaffInfoVO.class, new PaginationModel(),
		EntityQuery.create().where("#[status=:status] #[and staffName like :staffName]")
				.orderByDesc("ENTRY_DATE").values(new StaffInfoVO().setStaffName("陈"))
				// 设置rlike
				.filters(new ParamsFilter("staffName").rlike())
				// 设置缓存翻译
				.translates(new Translate("organIdName").setKeyColumn("organId").setColumn("organName"))
				// 设置分页优化
				.pageOptimize(new PageOptimize().aliveSeconds(120)));

// 在代码中实现单表查询
// 1、可指定特定字段
// 2、提供了分页和非分页两种
// 3、可以排序
// 4、可以进行缓存翻译
// 5、可以做分页优化
PaginationModel<StaffInfoVO> result = sqlToyLazyDao.findEntity(StaffInfoVO.class, new PaginationModel(),
	// 支持三种方式指定字段:
	// 1、用一个字符串写多个字段
	// EntityQuery.create().select("staffId,staffCode, staffName, organId,sexType")
	// 2、按数组形式提供字段
	// EntityQuery.create().select("staffId", "staffCode", "staffName",
	// "organId","sexType")
	// 3、采用链式模式提供字段
			EntityQuery.create().select(StaffInfoVO.select().staffId().staffCode().staffName().organId().sexType())
// 支持动态条件
.where("#[status=?] #[and staffName like ?]").orderByDesc("entryDate").values(1, "陈")
// 支持缓存翻译
.translates(new Translate("organIdName").setKeyColumn("organId").setColumn("organName"))
// 支持分页优化
.pageOptimize(new PageOptimize().aliveSeconds(120))
// 开关空白转null
.blankNotNull());

下面进入正题

  • 公共属性赋值
# 提供统一字段:createBy createTime updateBy updateTime 等字段补漏性(为空时)赋值(可选配置)
spring.sqltoy.unifyFieldsHandler=com.sqltoy.plugins.SqlToyUnifyFieldsHandler
  • 跨数据库支持:帮助实现一套代码多个数据库适用,函数自适配转换、优先执行dialect_id或id_dialect 对应的sql
# 提供跨数据库函数自适应转换,如mysql的函数在oracle下自动被替换
spring.sqltoy.functionConverts=default,com.xxxx.functions.GroupConcat
<sql id="qstart_cols_relative_case">
	<value>
	<![CDATA[
	select t.fruit_name,t.order_month,t.sale_count,t.sale_price,t.total_amt 
	from sqltoy_fruit_order t
	order by t.fruit_name ,t.order_month
	]]>
	</value>
</sql>

<!-- 当目前数据库是mysql时会优先执行mysql_开头或_mysql结尾的sql -->
<sql id="mysql_qstart_cols_relative_case">
	<value>
	<![CDATA[
	select t.fruit_name,t.order_month,t.sale_count,t.sale_price,t.total_amt 
	from sqltoy_fruit_order t
	order by t.fruit_name ,t.order_month
	]]>
	</value>
</sql>
  • 极致的动态查询(适用于代码中直接写的sql):请思考跟mybatis的对比,同时思考后期的可维护性!

    这是当初坚持写sqltoy的根本原因,因为其它一切对等的情况下,面对项目查询较多较复杂时,sqltoy的优势无与伦比!

sqltoy的机制,#[]类似于if(null)判断,同时提供了极为灵活的可能
1、简单的:#[and t.status=:status]
2、任意为null剔除:#[and t.status=:status and t.name like :name]
3、支持嵌套:#[and t.status=:status #[and t.name like :name]]
<sql id="show_case">
<filters>
   <!-- 参数statusAry只要包含-1(代表全部)则将statusAry设置为null不参与条件检索 -->
   <eq params="statusAry" value="-1" />
   <!-- 首要条件:当订单号不为空,排除其他条件(除授权机构)  -->
   <primary param="orderId" excludes="authedOrganIds"/>
</filters>
<value><![CDATA[
	select 	*
	from sqltoy_device_order_info t 
	where #[t.status in (:statusAry)]
		  #[and t.ORDER_ID=:orderId]
		  #[and t.ORGAN_ID in (:authedOrganIds)]
		  #[and t.STAFF_ID in (:staffIds)]
		  #[and t.TRANS_DATE>=:beginDate]
		  #[and t.TRANS_DATE<:endDate]    
	]]></value>
</sql>

同样功能mybatis的实现!如果再复杂点的呢?

<select id="show_case" resultMap="BaseResultMap">
 select *
 from sqltoy_device_order_info t 
 <where>
     <if test="statusAry!=null">
	and t.status in
	<foreach collection="status" item="statusAry" separator="," open="(" close=")">  
            #{status}  
 	</foreach>  
    </if>
    <if test="orderId!=null">
	and t.ORDER_ID=#{orderId}
    </if>
    <if test="authedOrganIds!=null">
	and t.ORGAN_ID in
	<foreach collection="authedOrganIds" item="order_id" separator="," open="(" close=")">  
            #{order_id}  
 	</foreach>  
    </if>
    <if test="staffIds!=null">
	and t.STAFF_ID in
	<foreach collection="staffIds" item="staff_id" separator="," open="(" close=")">  
            #{staff_id}  
 	</foreach>  
    </if>
    <if test="beginDate!=null">
	and t.TRANS_DATE>=#{beginDate}
    </if>
    <if test="endDate!=null">
	and t.TRANS_DATE<#{endDate}
    </if>
</where>
</select>

以下面的查询条件来说,mybatis如何应对?写出来的sql还能看吗?后期还能维护吗?

  • 缓存翻译(关于缓存定义和更新机制这里篇幅原因不深入介绍),sqltoy的专利!

  • 极致分页优化(sqltoy的专利)

  • 并行查询(同时执行提升效率)
// 使用并行查询同时执行2个sql,条件参数是2个查询的合集
String[] paramNames = new String[] { "userId", "defaultRoles", "deployId", "authObjType" };
Object[] paramValues = new Object[] { userId, defaultRoles, DEPLOY_ID,GROUP };

List<QueryResult<TreeModel>> list = super.parallQuery(
		Arrays.asList(
            ParallQuery.create().sql("webframe_searchAllModuleMenus").resultType(TreeModel.class),	
			ParallQuery.create().sql("webframe_searchAllUserReports").resultType(TreeModel.class)
),paramNames, paramValues);
  • 数据旋转:用算法来减少复杂大sql

  • 无限极分组统计(含汇总求平均),算法配置简单又跨数据库,谁说sqltoy喜欢强调sql的?该算法就算法,该sql就sql,不要僵化教条

  • 同比环比

展开阅读全文
17 收藏
分享
加载中
精彩评论
发明专利最好附上专利号,大家也可以学习一下
2020-09-29 03:20
3
举报
已经更新了,日志效果不错,更加完善了!建议大家可以深入了解一下,个人的感受就是项目越有挑战越感受到sqltoy的价值!
2020-09-28 17:36
1
举报
深入浅出的一个ORM框架。
2020-09-28 17:25
1
举报
不是这么理解的,sqltoy还真不是照轮子,其实起源比较早,等了这么久发现mybatis(plus)还是无法满足我们现实的需求,不得不发力,以前从来没有提sqltoy的专利,是希望大家发展起来我们也享受一个现成的,发现这些框架都跟联想thinkpad电脑一样,永远的可以跑马的大边框!总是在小学生知识范围内转(crud,对象式查询等),而不知道这些不是痛点,如何面对复杂查询诉求和大规模数据查询才是痛点!

所以sqltoy决定要发力在crud的基础上解决查询问题!
2020-09-28 17:00
1
举报
点赞,动态sql很好用,mybatis的sql确实繁琐~~~
2020-09-28 16:39
1
举报
最新评论 (32)
好恶心
2020-10-14 10:03
0
回复
举报
可以说具体点,哪里说错了,是动态查询不如mybatis和plus?还是缓存翻译不如它,还是分页优化不如它?

讨论技术要强调铁一般的事实!

sqltoy欢迎任何形式的有依据的pk,技术人就需要这种态度!

你不会是看到sql中的@if吧,好好看清楚内容,sqltoy的@if是在极端场景下才有!如果每个条件都加@if 还有脸开源!不是跟mybatis一样了!
2020-10-14 10:54
0
回复
举报
为啥过滤掉null值更新?null也是一个有意义的值啊,万一就想设置为null咋办?
2020-09-29 09:05
0
回复
举报
有deeply更新也有可以指定强制更新字段
2020-09-29 09:07
0
回复
举报
建议还是换个名字,更简单更单一的名字
2020-09-29 08:47
0
回复
举报
关注,还是期待入apache孵化
2020-09-29 04:50
0
回复
举报
发明专利最好附上专利号,大家也可以学习一下
2020-09-29 03:20
3
回复
举报
CN107463662A 可以查询一下
2020-09-29 06:29
0
回复
举报
其实我感觉java最牛批的orm框架是hibernate
2020-09-28 19:22
0
回复
举报
hibernate的对象操作确实很不错,这点不要否定,唯一就是复杂查询欠缺,所以补强这方面其实才是重点
2020-09-28 19:23
0
回复
举报
n+1问题有方案吗
2020-09-28 19:18
0
回复
举报
怎么讲?1对n,级联吗?可以参见quickstart范例,就有级联范例
2020-09-28 19:20
0
回复
举报
好的,我去看看文档
2020-09-28 19:21
0
回复
举报
就是hibernate 关联查询 会发送多次sql查询
2020-09-28 19:23
0
回复
举报
sqltoy也是在使用hibernate基础上发展起来的,所以hibernate的增删改等不足在sqltoy里面已经规避!
2020-09-28 19:25
0
回复
举报
所以会不会多次?
2020-10-14 10:20
0
回复
举报
一次主,一次子
2020-10-14 10:38
0
回复
举报
我提一个能跟spring boot整合需求吧
2020-09-28 19:16
0
回复
举报
参见quickstart演示项目,就是基于springboot的,有boot-starter
2020-09-28 19:17
0
回复
举报
牛批,现在java开源框架,不跟spring boot整合,都感觉没有学了
2020-09-28 19:19
0
回复
举报
已经更新了,日志效果不错,更加完善了!建议大家可以深入了解一下,个人的感受就是项目越有挑战越感受到sqltoy的价值!
2020-09-28 17:36
1
回复
举报
深入浅出的一个ORM框架。
2020-09-28 17:25
1
回复
举报
更多评论
32 评论
17 收藏
分享
返回顶部
顶部