我真的不想再用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下>,<必需求合作CDATA否则xml解析就失败,别说转义,我写那玩意在加转义你确认让我后续看得眼睛不要累死吗?美名其曰xml和代码分离便利保护,可是你再怎么便利修改了代码一样需求重启,而且由于代码写在xml里面导致动态条件得才干相对很弱。而且我也不知道mybatis为什么天生不支撑分页,需求分页插件来支撑,莫非一个3202年的orm了还需求这样吗,很难搞懂mybatis的作者莫非不写crud代码的吗?有些时分简练并不是偷闲的原因,当然也有或许是架构的问题导致的。

逻辑删去的功用我觉得稍微正常一点的企业一定都会有这个功用,可是由于运用了myabtis,由于手写sql,所以常常会忘掉往sql中增加逻辑删去字段,然后导致一些奇奇怪怪的bug需求排查,由于这些都是编译器无法体现的过错,由于他是字符串,由于mybatis把这个问题的原因指向了用户,这一点他很聪明,这个是用户的过错而不是结构的,可是结构要做的便是尽或许的将一些重复作业进行封装躲藏起来自动完成。

或许又会有一些用户会说所见即所得这样我才干知道他怎么执行了,可是现在哪个orm没有sql打印功用,哪个orm结构执行的sql和打印的sql是不一样的,不是所见即所得。整体而言我觉得mybatis充其量算是sqltemlate,比sqlhelper好的地便利是他是参数化避免sql注入。当然最主要的呀一点事莫非java程序员不需求修改表,不需求动表结构,不需求后期保护的吗仍是说java程序员写一个项目就换一个当地换岗,仍是说java程序员每个办法都有单元测验。我在转java后理解了一点,本来这便是你们经常说的java加班严峻,用这种结构加班不严峻就有鬼了。

为什么抛弃mybatis衍生结构

有幸在201几年再网上看到了mybatis-plus结构,这块结构一呈现就吸引了我,由于他在处理sql的方法上和.net的orm很相似,最少都是强类型,最少不需求java文件和xml文件跳来跳去,往常50%的代码也是能够经过结构的lambda表达式来完成,我个人比较排斥他的字符串形式的querywrapper,由于一门强类型言语缺少了强类型提示,在编写代码的时分会十分的奇怪。包含后期的重构,当然假如你的代码后续不需求你保护那么我觉得你用哪种方法都是ok的反正是一次性的,能出来成果就好了。

继续说mybatis-plus,由于作业的需求再2020年左右针对内部结构进行改造,而且让mybatis-plus支撑强类型group by,sum,min,max,any等api。

我真的不想再用mybatis和其衍生框架了选择自研亦是一种解脱
这个时分其实大部分状况下现已能够应对了,就这样用了1年左右这个结构,包含后续的update的increment,decrement

update table set column=column-1 where id=xxx and column>1

悉数运用lambda强类型语法,能够应对多数状况,可是针对join始终没有一个很好当地法。直到我遇到了mpj也便是mybatis-plus-join,可是这个结构也有问题,便是这个逻辑删去在join的子表上不收效,需求手动处理,假如收效那么在where上面,不知道现在怎么样了,其时我也是自行完成了让其呈现在join的on后边,可是由于完成是需求完成某个接口的,所以并没有pr代码. 首先界说一个接口

public interface ISoftDelete {
    Boolean getDeleted();
}
//其中join mapper是我自己的完成,主要仍是`WrapperFunction`的那段界说
  @Override
    public Scf4jBaseJoinLinq<T1,TR> on(WrapperFunction<MPJAbstractLambdaWrapper<T1, ?>> onFunction) {
        WrapperFunction<MPJAbstractLambdaWrapper<T1, ?>> join=  on->{
            MPJAbstractLambdaWrapper<T1, ?> 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,支撑entityinsertupdate可是不支撑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支撑 单表 多表 子查询 逻辑删去 多租户 差异更新 联级1对1 一对多 多对一 多对多 分库分表(支撑跨表查询分页等) 动态表名 数据库列高效加解密支撑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<Topic> 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
<== Total: 1
//根据条件查询id为3的集合
List<Topic> topics = easyQuery
                .queryable(Topic.class)
                .where(o->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)
<== Total: 1

多表

 Topic topic = easyQuery
                .queryable(Topic.class)
                //join 后边是双参数委托,参数次序表示join表次序,能够经过then函数切换
                .leftJoin(BlogEntity.class, (t, t1) -> 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)
<== Total: 1
List<BlogEntity> blogEntities = easyQuery
                .queryable(Topic.class)
                //join 后边是双参数委托,参数次序表示join表次序,能够经过then函数切换
                .innerJoin(BlogEntity.class, (t, t1) -> 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)
<== Total: 1

子查询


```java
//SELECT * FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?
 Queryable<BlogEntity> subQueryable = easyQuery.queryable(BlogEntity.class)
                .where(o -> o.eq(BlogEntity::getId, "1"));
List<Topic> 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)
<== Time Elapsed: 3(ms)
<== Total: 1
//SELECT t1.`id` FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?
Queryable<String> idQueryable = easyQuery.queryable(BlogEntity.class)
            .where(o -> o.eq(BlogEntity::getId, "123"))
            .select(String.class, o -> o.column(BlogEntity::getId));//假如子查询in string那么就需求select string,假如integer那么select要integer 两边需求一致
List<Topic> 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)
<== Time Elapsed: 2(ms)
<== Total: 0

自界说逻辑删去


//@Component //假如是spring
public class MyLogicDelStrategy extends AbstractLogicDeleteStrategy {
    /**
     * 允许datetime类型的属性
     */
    private final Set<Class<?>> allowTypes=new HashSet<>(Arrays.asList(LocalDateTime.class));
    @Override
    protected SQLExpression1<WherePredicate<Object>> getPredicateFilterExpression(LogicDeleteBuilder builder,String propertyName) {
        return o->o.isNull(propertyName);
    }
    @Override
    protected SQLExpression1<ColumnSetter<Object>> getDeletedSQLExpression(LogicDeleteBuilder builder, String propertyName) {
//        LocalDateTime now = LocalDateTime.now();
//        return o->o.set(propertyName,now);
        //上面的是过错用法,将now值获取后那么这个now便是个固定值而不是动态值
        return o->o.set(propertyName,LocalDateTime.now())
                .set("deletedUser",CurrentUserHelper.getUserId());
    }
    @Override
    public String getStrategy() {
        return "MyLogicDelStrategy";
    }
    @Override
    public Set<Class<?>> 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)
<== Total: 0

差异更新

  • 要注意是否敞开了追寻spring-boot下用@EasyQueryTrack注解即可敞开
  • 是否将当时目标增加到了追寻上下文 查询增加asTracking或许 手动将查询出来的目标进行easyQuery.addTracking(Object entity)
TrackManager trackManager = easyQuery.getRuntimeContext().getTrackManager();
try{
        trackManager.begin();
        Topic topic = easyQuery.queryable(Topic.class)
                .where(o -> 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)
<== Total: 1

相关查询

1对1

学生和学生地址

//数据库对像查询
           List<SchoolStudent> list1 = easyQuery.queryable(SchoolStudent.class)
                        .include(o -> o.one(SchoolStudent::getSchoolStudentAddress).asTracking().disableLogicDelete())
                        .toList();
//vo自界说列映射回来
List<SchoolStudentVO> 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<SchoolStudent> list1 = easyQuery.queryable(SchoolStudent.class)
                        .include(o -> o.one(SchoolStudent::getSchoolClass))
                        .toList();
//自界说列
 List<SchoolStudentVO> 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<SchoolStudentVO> list1 = easyQuery.queryable(SchoolStudent.class)
                        .include(o -> o.one(SchoolStudent::getSchoolClass))
                        .select(SchoolStudentVO.class,o->o
                                .columnAll()
                                .columnInclude(SchoolStudent::getSchoolClass,SchoolStudentVO::getSchoolClass)
                        )
                        .toList();

一对多

班级和学生

//数据库对像查询
 List<SchoolClass> list1 = easyQuery.queryable(SchoolClass.class)
                        .include(o -> o.many(SchoolClass::getSchoolStudents))
                        .toList();
//vo自界说列映射回来
       List<SchoolClassVO> list1 = easyQuery.queryable(SchoolClass.class)
                        .include(o -> o.many(SchoolClass::getSchoolStudents))
                        .select(SchoolClassVO.class,o->o.columnAll()
                                .columnIncludeMany(SchoolClass::getSchoolStudents,SchoolClassVO::getSchoolStudents))
                        .toList();

多对多

班级和教师

      List<SchoolClass> list2 = easyQuery.queryable(SchoolClass.class)
                .include(o -> o.many(SchoolClass::getSchoolTeachers,1))
                .toList();
  List<SchoolClassVO> list2 = easyQuery.queryable(SchoolClass.class)
                    .include(o -> o.many(SchoolClass::getSchoolTeachers))
                    .select(SchoolClassVO.class,o->o.columnAll()
                            .columnIncludeMany(SchoolClass::getSchoolTeachers,SchoolClassVO::getSchoolTeachers))
                    .toList();

动态报名

List<BlogEntity> 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)
<== Total: 0
List<BlogEntity> blogEntities = easyQuery.queryable(BlogEntity.class)
                .asTable(a->{
                    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)
<== Total: 0
List<BlogEntity> x_t_blog = easyQuery
                .queryable(Topic.class)
                .asTable(o -> "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)
<== Total: 0

最终

感谢各位看到最终,期望以后我的开源结构能够协助到您,假如您觉得有用能够点点star,这将对我是极大的鼓励

更多文档信息能够参阅git地址或许文档

文档地址 xuejm.gitee.io/easy-query-…

GITHUB地址 github.com/xuejmnet/ea…

GITEE地址 gitee.com/xuejm/easy-…