目录
一分钟了解Mybatis-plus
特性
学会Mybatis-plus,我们能简化哪些操作
快速入门
1. 引入依赖
2. 数据准备
3. 配置文件
4. Bean
5. Mapper
6. 启动类
7. 测试类
开启SQL日志
为什么UserMapper继承了BaseMapper就有了selectList方法?
SQL语句是什么时候拼接的?
一分钟了解Mybatis-plus
大家好,我是兔子老师。那么从今天开始呢,就由我来带领大家来了解一下Mybatis-plus这个框架。
为什么他叫Mybatis-plus呢,它和Mybatis又有什么关系?
https://baomidou.com/ 这是官网。
为简化而生,对Mybatis只做增强,不做改变。
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
Mybatis-plus的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
为什么要学习Mybatis-plus呢?说白了,为了偷懒。
怎么个偷懒法呢?我们来看一下特性。
特性
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
每一项都能大大地帮我们减轻开发的压力,用起来确实是无比丝滑。现在SPringBoot+MybatisPlus几乎已经是中小型公司开发项目的标配。
因此,我们学好MybatisPlus,不仅仅是学会了一门新的框架,对我们的就业也是有着极大的帮助。
学会Mybatis-plus,我们能简化哪些操作
1.通过继承BaseMapper,自动获取单表CRUD方法,是动态,不需要我们写具体的CRUD实现,也不需要XML配置。以后我们不需要用Mybatis逆向生成工具了,减少了一大堆XML,也不需要通用Mapper了。
2.自带分页插件,以后我们就不需要写PageHelper代码了,引入了MP,就自动拥有了分页功能。
3.全局拦截插件,能帮助我们设置一些字段的默认值,比如Create Time,updateTime等,还可以自己做一些特殊化的拦截配置,防止误操作。
4.强大的代码生成器,自动帮我们生成Ctroller,Service,Dao,怎一个爽子了得,进一步解放我们的双手。
5.QueryWrapper强大的条件控制器,即使是复杂的SQL也不再话下
快速入门
好了,接下来,我会带领大家来完成一个MP的快速入门。方便我们直观地感受一下,MybatisPlus是如何应用到实战中的。
新建springboot项目,版本2.6.13
1. 引入依赖
<!-- 引入Mybatis-plus依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1.tmp</version> </dependency>
2. 数据准备
其对应的数据库 Schema 脚本如下:
DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT(20) NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名', age INT(11) NULL DEFAULT NULL COMMENT '年龄', email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱', PRIMARY KEY (id) );
其对应的数据库 Data 脚本如下:
DELETE FROM user; INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com'), (4, 'Sandy', 21, 'test4@baomidou.com'), (5, 'Billie', 24, 'test5@baomidou.com');
3. 配置文件
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=root spring.datasource.password=root spring.datasource.url=jdbc:mysql://localhost:3306/mptest?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
我们只需要配置一下数据库连接,其他全都用默认的就行。
4. Bean
@Data @ToString public class User { private Long id; private String name; private Integer age; private String email; }
5. Mapper
@Mapper public interface UserMapper extends BaseMapper<User> { }
6. 启动类
@SpringBootApplication public class MpdemoApplication { public static void main(String[] args) { SpringApplication.run(MpdemoApplication.class, args); } }
默认就行,啥也不需要修改。
7. 测试类
@SpringBootTest @Slf4j class MpdemoApplicationTests { @Autowired UserMapper userMapper; @Test void searchUsers() { log.info("测试MP框架的查询功能"); List<User> users = userMapper.selectList(null); users.forEach(System.out::println); } }
结果:
思考两个问题?
1.为什么UserMapper继承了BaseMapper就有了selectList方法?
2.SQL语句是什么时候拼接的?
开启SQL日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
添加上述配置即可。
就有日志了。
为什么UserMapper继承了BaseMapper就有了selectList方法?
要回答第一个问题,让我们回过头来看看UserMapper
@Mapper public interface UserMapper extends BaseMapper<User> { }
这是一个接口,我们并没有写任何方法,那么只可能是在父类里面。
确实找到了方法,可是BaseMapper依然是个接口,实现逻辑究竟在什么地方呢?
我们试着思考一下,假如你是框架的设计者,会怎么来完成这个壮举?
对于User表来说,基本的CRUD无非是:
1.insert into user values (xxx)
2.delete from user where xxx
3.update user set column = value where xxx
4.select * from user
BaseMapper里面有增删改查方法,对于单表的CRUD操作,我们需要知道表名+字段名即可。
而UserMapper里面是有一个泛型的,对应User类。
@Data @ToString public class User { private Long id; private String name; private Integer age; private String email; }
而MabtisPlus也确实是这么做的,现在我们可以回答为什么UserMapper继承了BaseMapper就有了selectList方法?
大概方向就是框架根据Mapper获取到了对应的泛型类,然后通过反射从泛型类读取了表信息和字段信息,再通过动态代理给UserMapper做动态代理,注入一些列增删改查的方法。当你下次写userMapper.selectList(null);的时候,就会生成动态代理对象,执行对应的方法。
SQL语句是什么时候拼接的?
然后是第二个问题,SQL语句的拼接,这个很重要,为了知道MP框架具体是怎么做的,我们有必要深入源码看一下。
找到MP框架自动配置的地方。
点进去,找到这个方法:
往下拉,找到这里
// TODO 注入sql注入器 this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
ISqlInjector就是SQL注入的接口,它有唯一的实现类:AbstractSqlInjector
实现了inspectInject方法,即SQL自动注入
核心就在于:
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
m就是AbstractMethod集合,意思循环遍历方法对象,调用每一个方法对象的inject方法,去注入对应的SQL。这个SQL所需要的参数分别为:
builderAssistant: 一个工具类
mapperClass: Mapper类
ModelClass:Mapper的泛型类
tableInfo:表信息,也是从ModelClass而来
因此,我们之前的推测是完全正确的。
methodList在上面的代码中可以得到,如下图:
List<AbstractMethod> methodList = this.getMethodList(mapperClass);
点进getMethodList
/** * <p> * 获取 注入的方法 * </p> * * @param mapperClass 当前mapper * @return 注入的方法集合 * @since 3.1.2 add mapperClass */ public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass);
getMethodList是一个抽象方法,交给子类DefaultSqlInjector去实现的。
@Override public List<AbstractMethod> getMethodList(Class<?> mapperClass) { return Stream.of( new Insert(), new Delete(), new DeleteByMap(), new DeleteById(), new DeleteBatchByIds(), new Update(), new UpdateById(), new SelectById(), new SelectBatchByIds(), new SelectByMap(), new SelectOne(), new SelectCount(), new SelectMaps(), new SelectMapsPage(), new SelectObjs(), new SelectList(), new SelectPage() ).collect(toList()); }
看到这里,恍然大悟,原来BaseMapper里面的方法,都有一个类去专门实现的。AbstractMethod方法拥有17个子类。
因此,看了源码我们就知道,不管你是哪个Mapper,这行代码都将得到17个Method类的集合。
让我们把注意力再次放到循环注入自定义方法这块
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
点进inject方法
public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { this.configuration = builderAssistant.getConfiguration(); this.builderAssistant = builderAssistant; this.languageDriver = configuration.getDefaultScriptingLanguageInstance(); /* 注入自定义方法 */ injectMappedStatement(mapperClass, modelClass, tableInfo); }
这是AbstractMethod的inject方法,作用是注入自定义方法,然后最后一行的injectMappedStatement又是一个抽象方法,这是模板方法模式。
这才是真正注入SQL的方法,AbstractMethod的17个子类,各有各的实现
让我们不忘初心,就来看一个SelectList类的实现吧。
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { SqlMethod sqlMethod = SqlMethod.SELECT_LIST; String sql = String.format(sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(), sqlWhereEntityWrapper(true, tableInfo), sqlComment()); SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass); return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo); }
现在我们可以回答第二个问题了,SQL语句是什么时候拼接的?
以UserMapper为例,就是:
UserMapper -> User.class -> 获取17个方法类对象(SelectList等) -> 循环注入具体SQL