假设有两张表:一张商品表、一张订单表,具体表的字段如下:
现有如下需求:
需求 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 都修改一遍,加上这个字段。
需求 a:对于不同的实体类,即使查询的目的一致,仍然需要重复构造类似的 sql 语句,仅仅是表字段和表信息不同。 需求 b:对于相似的查询条件,针对某个单一场景必须构造不同的 sql,造成 sql 语句的大量冗余。 需求 c:将 dao 层所有涉及到新增字段的 sql 都需要修改一遍,这个过程比较繁琐且容易出错。
使用 mybatis-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,因此减少了大量的重复劳动。
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
本文针对 MyBatis-plus 中的核心功能:SQL 自动注入功能,进行流程分析及原理探究。
先从一个简单的 demo 入手,感受一下 MyBatis-plus 的便捷性。
源码中提供了一个基础接口,里面包含了基本的增删改查方法。
业务中根据具体实体对象,继承该抽象接口。
控制台显示:MyBatis-plus 最终为我们自动生成了 SQL 语句。根据上述操作分析:UserMapper 继承了 BaseMapper,拥有了 deleteById 的方法,但是 MyBatis-plus 是基于 mybatis 的增强版,关键在于最终仍然需要提供具体的 SQL 语句,来进行数据库操作。
下面就通过 debug 由上而下分析 mybatis-plus 是如何生成业务 sql 以及自动注入的。
mappedStatements:描述 sql 信息
如下图所示:mybatis 为我们生成了一个代理对象,里面包含了一些重要的属性。
具体如下:
userMapper——>mybatisMapperProxy——>sqlSession——>sqlSessionFactory
——>configuration——>mappedStatements——>mappedStatement——>sql 语句
至此我们可以发现每一个 SQL 语句对应一个 mappedStatement,mappedstatements 存储在 configuration 文件(configuration 是 mybatis 的全局配置文件,包含数据源、mapper、其他配置信息)中。
基于上面的分析,想要知道 SQL 语句什么时候拿到的,就是要找到 mappedStatement 被添加的位置。追踪到 AbstractMethod 的抽象方法中。
找到了 addMappedStatement()方法
而 BaseMapper 的所有方法(deleteById、delete、insert、select、update 等)都继承了该抽象方法。
根据 mapper 方法(deleteById)显然是调用 addDeleteMappedStatement 方法。
这里我们可以发现,源码中根据不同的方法继承 AbstractMethod 实现了不同的实现类,并且实现了 injectMappedStatement 方法,sqlSource 也是在这个地方被添加进配置文件。
继续研究 AbstractMethod 抽象类,inject 方法实现了自动注入 sql 的动作。
有上述源码可知,项目启动时,首先由默认注入器生成基础 CRUD 实现类对象,其次遍历实现类列表,依次注入各自的模板 SQL,最后将其添加至 mappedstatement。
上述方法中有两个关键的参数:SqlMethod、SqlSource;
继续研究源码发现:sqlMethod 本质上是一个枚举类,存储了两个关键的元素:
BaseMapper 中的方法名; 方法名对应的 sql 语句模板(即被<scripe>标签包裹的字符串)。
到这里我们基本了解了 mybaits-plus 实现 sql 自动生成的本质:根据不同的方法来提供一些通用的模板,项目启动后再加载进 mappedStatement。
此时 SqlSource 通过解析 SQL 模板、以及传入的表信息和主键信息构建出了 SQL 语句。
分析 initTableName()方法:获取表名信息源码中传入了实体类信息 clazz,其实就是通过实体上的 @TableName 注解拿到了表名;
我们在定义实体类的同时,指定了该实体类对应的表名。
分析 initTableFields()方法:
获取主键及其他字段信息
至此 tableInfo 的信息已经注入完成了。
在研究完解析 mapper 的核心过程之后,我们再简单看下 mapper 文件被添加到 configuration(mybatis 核心配置文件)的过程。
ISqlInjector:Sql 注入器
MybatisMapperAnnotationBuilder:mapper 解析器
MybatisMapperAnnotationBuilder 中的 parse 方法获取了 sqlInjector(Sql 注入器)来进行 SQL 注入。
Mybatis 添加 mapper 的固有流程:MybatisMapperRegistry
调用 MapperAnnotionBuilder 解析器进行解析
MybatisConfiguration.addMapper
MybatisXMLConfigBuilder.mapperElemnt
MybayisXMLConfigBuilder.parseConfiguration
添加 mapper 文件的过程分析到这里就完成了。
下面总结梳理了一下 mybatis-plus 解析 mapper 文件自动注入 sql 的主要流程。
1)实体类和数据库表通过自定义注解来完成一一映射。 2)对象属性和字段同样使用注解来一一对应(命名注意要相同)。 3)为了提高复用性使得具体的 mapper 继承通用的增删改查接口。 4)利用模板方法和对象属性值动态拼接 SQL。
MyBatis-plus 官方文档:https://mp.baomidou.com/
作者:vivo 互联网服务器团队-Li Lei
领取专属 10元无门槛券
私享最新 技术干货