乐趣区

关于mybatis:MybatisPlus的应用场景及注入SQL原理分析

一、背景

1.1 传统 Mybatis 的弊病

1.1.1 场景形容

假如有两张表:一张商品表、一张订单表,具体表的字段如下:

现有如下需要:

  • 别离依据 id 查问商品表和订单表所有信息
  • 依据领取状态和告诉状态查问订单表信息
  • 对订单表减少一个订单状态,依据订单状态查问订单信息

1.1.2 需要

需要 a : 依据 id 查问商品表:

​@Select(" SELECT p.id ,p.name ,p.picture ,p.type ,p.price, p.type, p.time
          FROM product p  where id = #{id} ")
List<Product> getProductsBYId(@Param("id") Integer id);

依据 id 查问订单表所有信息:

@Select(" SELECT o.id ,o.pay_no ,o.user_id ,o.product_id ,o.pay_price, o.num, o.pay_time, o.order_type, o.notif_type
          FROM order o  where id = #{id} ")
List<Order> getOrderBYId(@Param("id") Integer id);

需要 b :依据领取状态和告诉状态查问订单表信息

​@Select(" SELECT o.id ,o.pay_no ,o.user_id ,o.product_id ,o.pay_price, o.num, o.pay_time, o.order_type, o.notif_type
          FROM order o  where order_type= #{orderType} ")
List<Order> getOrderBYId(@Param("orderType") Integer orderType);
 
@Select(" SELECT o.id ,o.pay_no ,o.user_id ,o.product_id ,o.pay_price, o.num, o.pay_time, o.order_type, o.notif_type
          FROM order o  where notify_type= #{notifyType} ")
List<Order> getOrderBYId(@Param("notifyType") Integer notifyType);

需要 c :对订单表减少一个订单状态 status,依据订单状态查问订单信息。

传统 mybaits 须要三步:首先须要在订单表里加个字段,而后在订单的实体类增加这个属性,并且将所有 dao 层设计该状态的的查问 sql 都批改一遍,加上这个字段。

1.1.3 上述形式有什么问题呢?

需要 a:对于不同的实体类,即便查问的目标统一,依然须要反复结构相似的 sql 语句,仅仅是表字段和表信息不同。

需要 b:对于类似的查问条件,针对某个繁多场景必须结构不同的 sql,造成 sql 语句的大量冗余。

需要 c:将 dao 层所有波及到新增字段的 sql 都须要批改一遍,这个过程比拟繁琐且容易出错。

应用 mybatis-plus 就能够解决上述问题。

1.1.4 Myatis-plus 的解决方案

首先让 ProductMapper 和 OrderMapper 继承 BaseMapper 类:

public interface ProductMapper extends BaseMapper<Product> {
}
 
public interface OrderMapper extends BaseMapper<Order> {}

需要 a:

别离依据 id 查问商品表和订单表:因为 BaseMapper 中提供了 selectById 的办法,能够间接依据具体业务场景,传入指定的参数例如(id=1)即可。无需书写具体的 sql 语句,至于 sql 主动生成的原理将在上面介绍;

productMapper.selectById(1);
orderMapper.selectById(1);

需要 b:

此时利用 BaseMapper.selectList(Wapper queryWrapper)办法间接结构查问条件,例如查问领取状态为 2 和告诉状态为 1 的订单信息

orderMapper.selectList(new QueryWrapper<Order>().eq("orderType",2));
orderMapper.selectList(new QueryWrapper<Order>().eq("notifyType",1));
orderMapper.selectList(new QueryWrapper<Order>().eq("orderType",2));orderMapper.selectList(new QueryWrapper<Order>().eq("notifyType",1));

此时咱们能够发现:应用了 Mybatis-plus 当前,咱们更加聚焦于业务自身,对于上述类似的利用场景,无需结构雷同的 SQL,利用包装器间接传入查问条件。

需要 c:

前两步与传统 mybatis 统一,因为 MyBatis-plus 无需手动创立 SQL,因而缩小了大量的重复劳动。

1.2 MyBatis-Plus 的定位

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的加强工具,在 MyBatis 的根底上只做加强不做扭转,为简化开发、提高效率而生。

1.3 个性

  • 无侵入:只做加强不做扭转,引入它不会对现有工程产生影响,如丝般顺滑;
  • 损耗小:启动即会主动注入根本 CURD,性能根本无损耗,间接面向对象操作;
  • 弱小的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过大量配置即可实现单表大部分 CRUD 操作,更有弱小的条件结构器,满足各类应用需要;
  • 反对 Lambda 模式调用:通过 Lambda 表达式,不便的编写各类查问条件,无需再放心字段写错;
  • 反对主键主动生成:反对多达 4 种主键策略(内含分布式惟一 ID 生成器 – Sequence),可自在配置,完满解决主键问题;
  • 反对 ActiveRecord 模式:反对 ActiveRecord 模式调用,实体类只需继承 Model 类即可进行弱小的 CRUD 操作;
  • 反对自定义全局通用操作:反对全局通用办法注入(Write once, use anywhere);
  • 内置代码生成器:采纳代码或者 Maven 插件可疾速生成 Mapper、Model、Service、Controller 层代码,反对模板引擎,更有超多自定义配置等您来应用;
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关怀具体操作,配置好插件之后,写分页等同于一般 List 查问;
  • 分页插件反对多种数据库:反对 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库;
  • 内置性能剖析插件:可输入 Sql 语句以及其执行工夫,倡议开发测试时启用该性能,能疾速揪出慢查问;
  • 内置全局拦挡插件:提供全表 delete、update 操作智能剖析阻断,也可自定义拦挡规定,预防误操作。

1.4 原理解析

本文针对 MyBatis-plus 中的外围性能:SQL 主动注入性能,进行流程剖析及原理探索。

二、筹备工作

先从一个简略的 demo 动手,感受一下 MyBatis-plus 的便捷性。

2.1 根底接口 BaseMapper

源码中提供了一个根底接口,外面蕴含了根本的增删改查办法。

2.2 创立实体类对象

2.3 业务接口 UserMapper

业务中依据具体实体对象,继承该形象接口。

2.4 测试用例

控制台显示:MyBatis-plus 最终为咱们主动生成了 SQL 语句。根据上述操作剖析:UserMapper 继承了 BaseMapper,领有了 deleteById 的办法,然而 MyBatis-plus 是基于 mybatis 的增强版,关键在于最终依然须要提供具体的 SQL 语句,来进行数据库操作

上面就通过 debug 由上而下剖析 mybatis-plus 是如何生成业务 sql 以及主动注入的。

三、SQL 语句存储在哪里?

mappedStatements: 形容 sql 信息

如下图所示:mybatis 为咱们生成了一个代理对象,外面蕴含了一些重要的属性。

具体如下:

userMapper——>mybatisMapperProxy——>sqlSession——>sqlSessionFactory——>configuration——>mappedStatements——>mappedStatement——>sql 语句

至此咱们能够发现 每一个 SQL 语句对应一个 mappedStatement,mappedstatements 存储在 configuration 文件(configuration 是 mybatis 的全局配置文件,蕴含数据源、mapper、其余配置信息)中。

四、SQL 语句是什么时候注入的?

4.1 AbstractMethod.addMappedStatement

基于下面的剖析,想要晓得 SQL 语句什么时候拿到的,就是要找到 mappedStatement 被增加的地位。追踪到 AbstractMethod 的形象办法中。

找到了 addMappedStatement()办法

而 BaseMapper 的所有办法(deleteById、delete、insert、select、update 等)都继承了该形象办法。

依据 mapper 办法(deleteById)显然是调用 addDeleteMappedStatement 办法。

这里咱们能够发现,源码中依据不同的办法继承 AbstractMethod 实现了不同的实现类,并且实现了 injectMappedStatement 办法,sqlSource 也是在这个中央被增加进配置文件。

4.2 AbstractMethod.inject

持续钻研 AbstractMethod 抽象类,inject 办法实现了主动注入 sql 的动作。

有上述源码可知,我的项目启动时,首先由默认注入器生成根底 CRUD 实现类对象,其次遍历实现类列表,顺次注入各自的模板 SQL,最初将其增加至 mappedstatement。

五、SQL 语句是怎么生成的?

5.1 SQL 模板

上述办法中有两个要害的参数:SqlMethod、SqlSource;

持续钻研源码发现:sqlMethod 实质上是一个枚举类,存储了两个要害的元素:

BaseMapper 中的办法名;

办法名对应的 sql 语句模板(即被 <scripe> 标签包裹的字符串)。

到这里咱们根本理解了 mybaits-plus 实现 sql 主动生成的实质:依据不同的办法来提供一些通用的模板,我的项目启动后再加载进 mappedStatement。

5.2 SqlSource

此时 SqlSource 通过解析 SQL 模板、以及传入的表信息和主键信息构建出了 SQL 语句。

5.3 数据库表信息是如何获取的?

剖析 initTableName()办法:获取表名信息源码中传入了实体类信息 clazz,其实就是通过实体上的 @TableName 注解拿到了表名;

咱们在定义实体类的同时,指定了该实体类对应的表名。

剖析 initTableFields()办法:

获取主键及其他字段信息

至此 tableInfo 的信息曾经注入实现了。

在钻研完解析 mapper 的外围过程之后,咱们再简略看下 mapper 文件被增加到 configuration(mybatis 外围配置文件)的过程。

六、mapper 文件被增加的过程

ISqlInjector:Sql 注入器

MybatisMapperAnnotationBuilder:mapper 解析器

MybatisMapperAnnotationBuilder 中的 parse 办法获取了 sqlInjector(Sql 注入器)来进行 SQL 注入。

Mybatis 增加 mapper 的固有流程:MybatisMapperRegistry

调用 MapperAnnotionBuilder 解析器进行解析

MybatisConfiguration.addMapper

MybatisXMLConfigBuilder.mapperElemnt

MybayisXMLConfigBuilder.parseConfiguration

增加 mapper 文件的过程剖析到这里就实现了。

七、总结

7.1 流程梳理

上面总结梳理了一下 mybatis-plus 解析 mapper 文件主动注入 sql 的次要流程。

7.2 Mybatis-plus 的 ORM 的核心思想

1)实体类和数据库表通过自定义注解来实现一一映射。

2)对象属性和字段同样应用注解来一一对应(命名留神要雷同)。

3)为了进步复用性使得具体的 mapper 继承通用的增删改查接口。

4)利用模板办法和对象属性值动静拼接 SQL。

八、参考文档

MyBatis-plus 官网文档:https://mp.baomidou.com/

作者:vivo 互联网服务器团队 -Li Lei

退出移动版