我真的不想再用mybatis和其衍生框架了选择自研亦是一种解脱
文档地址 xuejm.gitee.io/easy-query-…
GITHUB地址 github.com/xuejmnet/ea…
GITEE地址 gitee.com/xuejm/easy-…
为什么要用orm
众所邹知orm的出现让本来以sql实现的复杂繁琐功能大大简化,对于大部分程序员而言一个框架的出现是为了生产力的提升.。dbc定义了交互数据库的规范,任何数据库的操作都是只需要满足jdbc规范即可,而orm就是为了将jdbc的操作进行简化。我个人“有幸”体验过.net和java的两个大orm,只能说差距很大,当然语言上的一些特性也让java在实现orm上有着比较慢的进度,譬如泛型的出现,lambda的出现。
一个好的orm我觉得需要满足以下几点
- 强类型,如果不支持强类型那么和手写sql没有区别
- 能实现80%的纯手写sql的功能,好的orm需要覆盖业务常用功能
- 支持泛型,“如果一个orm连泛型都不支持那么就没有必要存在”这是一句现实但是又很残酷的结论,但是泛型会大大的减少开发人员的编写错误率
- 不应该依赖过多的组件,当然这并不是orm特有的,任何一个库其实依赖越少越不易出bug
其实说了这么多总结一下就是一个好的orm应该有ide的提示外加泛型约束帮助开发可以非常顺滑的把代码写下去,并且错误部分可以完全的在编译期间提现出来,运行时错误应该尽可能少的去避免。
为什么放弃mybatis
首先如果你用过其他语言的orm那么再用java的mybatis就像你用惯了java的stream然后去自行处理数据过滤,就像你习惯了kotlin的语法再回到java语法,很难受。这种难受不是自动挡到手动挡的差距,而且自动挡到手推车的差距。
xml
配置sql也不知道是哪个“小天才”想出来的,先不说写代码的时候java代码和xml代码跳来跳去,而且xml下>
,1
全部使用lambda强类型语法,可以应对多数情况,但是针对join始终没有一个很好地方法。直到我遇到了mpj
也就是mybatis-plus-join
,但是这个框架也有问题,就是这个逻辑删除在join的子表上不生效,需要手动处理,如果生效那么在where上面,不知道现在怎么样了,当时我也是自行实现了让其出现在join的on后面,但是因为实现是需要实现某个接口的,所以并没有pr代码.
首先定义一个接口
public interface ISoftDelete {
Boolean getDeleted();
}
//其中join mapper是我自己的实现,主要还是`WrapperFunction`的那段定义
@Override
public Scf4jBaseJoinLinq on(WrapperFunction onFunction) {
WrapperFunction join= on->{
MPJAbstractLambdaWrapper apply = onFunction.apply(on);
if(ISoftDelete.class.isAssignableFrom(joinClass)){
SFunction deleted = LambdaHelper.getFunctionField(joinClass, "deleted", Boolean.class);
apply.eq(deleted,false);
}
return apply;
};
joinMapper.setJoinOnFunction(query->{
query.innerJoin(joinClass,join);
});
return joinMapper;
}
虽然实现了join
但是还是有很多问题出现和bug。
- 比如不支持vo对象的返回,只能返回数据库对象自定义返回列,不然就是查询所有列
- 再比如如果你希望你的对象update的时候填充null到数据库,那么只能在entity字段上添加,这样就导致这个字段要么全部生效要么全部不生效.
- 批量插入不支持默认居然是foreach一个一个加,当然这也没关系,但是你真的想实现批处理需要自己编写很复杂的代码并且需要支持全字段。而不是null列不填充
MetaObjectHandler
,支持entity
的insert
和update
但是不支持lambdaUpdateWrapper
,有时候当前更新人和更新时间都是需要的,你也可以说数据库可以设置最后更新时间,但是最后修改人呢?- 非常复杂的动态表名,拜托大哥我只是想改一下表名,目前的解决方案就是try-finally每次用完都需要清理一下当前线程,因为tomcat会复用线程,通过threadlocal来实现,话说pagehelper应该也是这种方式实现的吧
当然其他还有很多问题导致最终我没办法忍受,选择了自研框架,当然我的框架自研是参考了一部分的freesql和sqlsuagr的api,并且还有java的beetsql的实现和部分方法。毕竟站在巨人的肩膀上才能看的更远,不要问我为什么不参考mybatis的,我觉得mybatis已经把简单问题复杂化了,如果需要看懂他的代码是一件很得不偿失的事情,最终我发现我的选择是正确的,我通过参考beetsql
的源码很快的清楚了java这边应该需要做的事情,为我编写后续框架节约了太多时间,这边也给beetsql
打个广告https://gitee.com/xiandafu/beetlsql
自研orm有哪些特点
easy-query
一款无任何依赖的java全新高性能orm支持 单表 多表 子查询 逻辑删除 多租户 差异更新 联级一对一 一对多 多对一 多对多 分库分表(支持跨表查询分页等) 动态表名 数据库列高效加解密支持like crud拦截器 原子更新 vo对象直接返回
文档地址 xuejm.gitee.io/easy-query-…
GITHUB地址 github.com/xuejmnet/ea…
GITEE地址 gitee.com/xuejm/easy-…
- 强类型,可以帮助团队在构建和查询数据的时候拥有id提示,并且易于后期维护。
- 泛型可以控制我们编写代码时候的一些低级错误,比如我只查询一张表,但是where语句里面可以使用不存在上下文的表作为条件,进一步限制和加强表达式
- easy-query提供了三种模式分别是lambda,property,apt proxy其中lambda表达式方便重构维护,property只是性能最好,apt proxy方便维护,但是重构需要一起重构apt文件
单表查询
//根据条件查询表中的第一条记录
List topics = easyQuery
.queryable(Topic.class)
.limit(1)
.toList();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t LIMIT 1
o.eq(Topic::getId,"3").eq(Topic::geName,"4")
.toList();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t WHERE t.`id` = ? AND t.`name` = ?
==> Parameters: 3(String),4(String)
t.eq(t1, Topic::getId, BlogEntity::getId))
.where(o -> o.eq(Topic::getId, "3"))
.firstOrNull();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t LEFT JOIN t_blog t1 ON t.`id` = t1.`id` WHERE t.`id` = ? LIMIT 1
==> Parameters: 3(String)
t.eq(t1, Topic::getId, BlogEntity::getId))
.where((t, t1) -> t1.isNotNull(BlogEntity::getTitle).then(t).eq(Topic::getId, "3"))
//join查询select必须要带对应的返回结果,可以是自定义dto也可以是实体对象,如果不带对象则返回t表主表数据
.select(BlogEntity.class, (t, t1) -> t1.columnAll())
.toList();
==> Preparing: SELECT t1.`id`,t1.`create_time`,t1.`update_time`,t1.`create_by`,t1.`update_by`,t1.`deleted`,t1.`title`,t1.`content`,t1.`url`,t1.`star`,t1.`publish_time`,t1.`score`,t1.`status`,t1.`order`,t1.`is_top`,t1.`top` FROM t_topic t INNER JOIN t_blog t1 ON t.`id` = t1.`id` WHERE t1.`title` IS NOT NULL AND t.`id` = ?
==> Parameters: 3(String)
o.eq(BlogEntity::getId, "1"));
List x = easyQuery
.queryable(Topic.class).where(o -> o.exists(subQueryable.where(q -> q.eq(o, BlogEntity::getId, Topic::getId)))).toList();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE EXISTS (SELECT 1 FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ? AND t1.`id` = t.`id`)
==> Parameters: false(Boolean),1(String)
o.column(BlogEntity::getId));//如果子查询in string那么就需要select string,如果integer那么select要integer 两边需要一致
List list = easyQuery
.queryable(Topic.class).where(o -> o.in(Topic::getId, idQueryable)).toList();
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE t.`id` IN (SELECT t1.`id` FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?)
==> Parameters: false(Boolean),123(String)
> allowedPropertyTypes() {
return allowTypes;
}
}
//为了测试防止数据被删掉,这边采用不存在的id
logicDelTopic.setId("11xx");
//测试当前人员
CurrentUserHelper.setUserId("easy-query");
long l = easyQuery.deletable(logicDelTopic).executeRows();
==> Preparing: UPDATE t_logic_del_topic_custom SET `deleted_at` = ?,`deleted_user` = ? WHERE `deleted_at` IS NULL AND `id` = ?
==> Parameters: 2023-04-01T23:15:13.944(LocalDateTime),easy-query(String),11xx(String)
o.eq(Topic::getId, "7")).asTracking().firstNotNull("未找到对应的数据");
String newTitle = "test123" + new Random().nextInt(100);
topic.setTitle(newTitle);
long l = easyQuery.updatable(topic).executeRows();
}finally {
trackManager.release();
}
==> Preparing: UPDATE t_topic SET `title` = ? WHERE `id` = ?
==> Parameters: test1239(String),7(String)
o.one(SchoolStudent::getSchoolStudentAddress).asTracking().disableLogicDelete())
.toList();
//vo自定义列映射返回
List list1 = easyQuery.queryable(SchoolStudent.class)
.include(o -> o.one(SchoolStudent::getSchoolStudentAddress).asTracking().disableLogicDelete())
.select(SchoolStudentVO.class,o->o.columnAll()
.columnInclude(SchoolStudent::getSchoolStudentAddress,SchoolStudentVO::getSchoolStudentAddress))
.toList();
多对一
学生和班级
//数据库对像查询
List list1 = easyQuery.queryable(SchoolStudent.class)
.include(o -> o.one(SchoolStudent::getSchoolClass))
.toList();
//自定义列
List list1 = easyQuery.queryable(SchoolStudent.class)
.include(o -> o.one(SchoolStudent::getSchoolClass))
.select(SchoolStudentVO.class,o->o
.columnAll()
.columnInclude(SchoolStudent::getSchoolClass,SchoolStudentVO::getSchoolClass,s->s.column(SchoolClassVO::getId))
)
.toList();
//vo自定义列映射返回
List list1 = easyQuery.queryable(SchoolStudent.class)
.include(o -> o.one(SchoolStudent::getSchoolClass))
.select(SchoolStudentVO.class,o->o
.columnAll()
.columnInclude(SchoolStudent::getSchoolClass,SchoolStudentVO::getSchoolClass)
)
.toList();
一对多
班级和学生
//数据库对像查询
List list1 = easyQuery.queryable(SchoolClass.class)
.include(o -> o.many(SchoolClass::getSchoolStudents))
.toList();
//vo自定义列映射返回
List list1 = easyQuery.queryable(SchoolClass.class)
.include(o -> o.many(SchoolClass::getSchoolStudents))
.select(SchoolClassVO.class,o->o.columnAll()
.columnIncludeMany(SchoolClass::getSchoolStudents,SchoolClassVO::getSchoolStudents))
.toList();
多对多
班级和老师
List list2 = easyQuery.queryable(SchoolClass.class)
.include(o -> o.many(SchoolClass::getSchoolTeachers,1))
.toList();
List list2 = easyQuery.queryable(SchoolClass.class)
.include(o -> o.many(SchoolClass::getSchoolTeachers))
.select(SchoolClassVO.class,o->o.columnAll()
.columnIncludeMany(SchoolClass::getSchoolTeachers,SchoolClassVO::getSchoolTeachers))
.toList();
动态报名
List blogEntities = easyQuery.queryable(BlogEntity.class)
.asTable(a -> "aa_bb_cc")
.where(o -> o.eq(BlogEntity::getId, "123")).toList();
==> Preparing: SELECT t.`id`,t.`create_time`,t.`update_time`,t.`create_by`,t.`update_by`,t.`deleted`,t.`title`,t.`content`,t.`url`,t.`star`,t.`publish_time`,t.`score`,t.`status`,t.`order`,t.`is_top`,t.`top` FROM aa_bb_cc t WHERE t.`deleted` = ? AND t.`id` = ?
==> Parameters: false(Boolean),123(String)
{
if("t_blog".equals(a)){
return "aa_bb_cc1";
}
return "xxx";
})
.where(o -> o.eq(BlogEntity::getId, "123")).toList();
==> Preparing: SELECT t.`id`,t.`create_time`,t.`update_time`,t.`create_by`,t.`update_by`,t.`deleted`,t.`title`,t.`content`,t.`url`,t.`star`,t.`publish_time`,t.`score`,t.`status`,t.`order`,t.`is_top`,t.`top` FROM aa_bb_cc1 t WHERE t.`deleted` = ? AND t.`id` = ?
==> Parameters: false(Boolean),123(String)
"t_topic_123")
.innerJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
.asTable("x_t_blog")
.where((t, t1) -> t1.isNotNull(BlogEntity::getTitle).then(t).eq(Topic::getId, "3"))
.select(BlogEntity.class, (t, t1) -> t1.columnAll()).toList();
==> Preparing: SELECT t1.`id`,t1.`create_time`,t1.`update_time`,t1.`create_by`,t1.`update_by`,t1.`deleted`,t1.`title`,t1.`content`,t1.`url`,t1.`star`,t1.`publish_time`,t1.`score`,t1.`status`,t1.`order`,t1.`is_top`,t1.`top` FROM t_topic_123 t INNER JOIN x_t_blog t1 ON t1.`deleted` = ? AND t.`id` = t1.`id` WHERE t1.`title` IS NOT NULL AND t.`id` = ?
==> Parameters: false(Boolean),3(String)