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

微信扫一扫,分享到朋友圈

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

致谢:

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,不要僵化教条
  • 同比环比

微信扫一扫,分享到朋友圈

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

腾讯FPS大作 《使命召唤》手游终测定档:10月20日

上一篇

科学家可能已经找到有些狼蛛进化成亮蓝色的原因

下一篇

你也可能喜欢

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

长按储存图像,分享给朋友