一、简介
1.1 特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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.2 通用的MP模板
1.2.1 pom依赖
<!--SpringBoot整合mybatis-plus--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter-test</artifactId><version>3.4.3.4</version></dependency><!-- mybatisPlus 代码生成器 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.3.1.tmp</version></dependency><!-- mybatisPlus Velocity 模版引擎 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.2</version></dependency><!-- mybatisPlus Freemarker 模版引擎 --><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.29</version></dependency><!--连接mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.24</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency>
1.2.2MyBatisPlusConfig
@Configurationpublic class MyBatisPlusConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();//注册乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());//分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));return interceptor;}}
1.2.3application.yml
#数据库spring:datasource:username: rootpassword: qingdriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncodeing=utf-8mybatis-plus:configuration:#日志log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:# 全局逻辑删除的实体字段名(,配置后可以不用在实体类字段上使用@TableLogic注解)logic-delete-field: isDellogic-delete-value: 1 # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
1.2.4 MyMetaObjectHandler
字段填充
@Slf4j//必须使用@Component注解将handler注入到ioc容器中@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler {//插入@Overridepublic void insertFill(MetaObject metaObject) {log.info("start insert fill ....");//要填充的字段 填充值 metaObject//注意 这里的第二个属性使用new Date() 则实体类对属性值必须是Date属性 不然会报错 如:private Date createTime;this.setFieldValByName("createTime",new Date(),metaObject);this.setFieldValByName("updateTime",new Date(),metaObject);}//更新@Overridepublic void updateFill(MetaObject metaObject) {log.info("start update fill ....");this.setFieldValByName("updateTime",new Date(),metaObject);}}
1.2.5 代码生成器
public static void main(String[] args) {// 需要构建一个 代码自动生成器 对象AutoGenerator mpg = new AutoGenerator();// 配置策略// 1、全局配置GlobalConfig gcf = new GlobalConfig();String projectPath = System.getProperty("user.dir");gcf.setOutputDir(projectPath + "/src/main/java");gcf.setAuthor("卿帆");gcf.setOpen(false);// 是否覆盖gcf.setFileOverride(false);// 去Service的I前缀gcf.setServiceName("%sService");//设置主键策略 自增gcf.setIdType(IdType.AUTO);//日期类型gcf.setDateType(DateType.ONLY_DATE);//配置swagger文档gcf.setSwagger2(true);mpg.setGlobalConfig(gcf);//2、设置数据源DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus? useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");dsc.setDriverName("com.mysql.cj.jdbc.Driver");dsc.setUsername("root");dsc.setPassword("qing");dsc.setDbType(DbType.MYSQL);mpg.setDataSource(dsc);//3、包的配置PackageConfig pc = new PackageConfig();//只需要改实体类名字 和包名 还有 数据库配置即可//pc.setModuleName("");//父级包名pc.setParent("com.qing");pc.setEntity("entity");pc.setMapper("mapper");pc.setService("service");pc.setController("controller");mpg.setPackageInfo(pc);//4、策略配置StrategyConfig strategy = new StrategyConfig();//要转换的数据表 可同时配置多个strategy.setInclude("user");// 设置要映射的表名strategy.setNaming(NamingStrategy.underline_to_camel);strategy.setColumnNaming(NamingStrategy.underline_to_camel);// 自动lombok;strategy.setEntityLombokModel(true);//配置逻辑删除字段 与yml配置文件对应strategy.setLogicDeleteFieldName("isDel");// 自动填充配置//创建时间TableFill gmtCreate = new TableFill("create_time", FieldFill.INSERT);//修改时间TableFill gmtModified = new TableFill("update_time", FieldFill.INSERT_UPDATE);ArrayList<TableFill> tableFills = new ArrayList<>();tableFills.add(gmtCreate);tableFills.add(gmtModified);strategy.setTableFillList(tableFills);// 乐观锁strategy.setVersionFieldName("version");//驼峰命名strategy.setRestControllerStyle(true);// localhost:8080/hello_id_2strategy.setControllerMappingHyphenStyle(true);mpg.setStrategy(strategy);mpg.execute(); //执行}
二、快速入门
2.1 建立User表 导入数据
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));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');
2.2 初始化一个SpringBoot项目
1、导入依赖
导入mybatis-plus依赖后就不用导入mybatis依赖了
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3.4</version></dependency><!--连接mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.24</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.6</version></dependency>
2、配置文件
spring:datasource:username: rootpassword: qingdriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncodeing=utf-8#mybatis内置日志mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3、传统配置(不推荐使用)
controller+service+mapper+mapper.xml
4、plus配置
User实体类
@Data@AllArgsConstructor@NoArgsConstructorpublic class User {private Integer id;private String name;private Integer age;private String email;}
UserMapper接口:
@Mapper//使用mybatis-plus 只需要在mapper接口这里继承BaseMapper 所有基础的CRUD就不需要在编写xml文件了public interface UserMapper extends BaseMapper<User> {}
可以看到BaseMapper接口中给我们封装了所有的CRUD代码,方法接口如下图:
使用MapperScan注解扫描包下的所有接口:
@SpringBootApplication@MapperScan("com.qing.mapper")public class MybatisPlusApplication {public static void main(String[] args) {SpringApplication.run(MybatisPlusApplication.class, args);}}
5、测试
@SpringBootTestclass MybatisPlusApplicationTests {@ResourceUserMapper userMapper;@Testvoid contextLoads() {User user = userMapper.selectById(1);System.out.println(user);// selectList() 方法的参数为 MP 内置的条件封装器 Wrapper,不填写就是无任何条件List<User> userList = userMapper.selectList(null);userList.forEach(System.out::println);}}
三、CRUD扩展
3.1 插入
调用封装的inset方法插入一个实体,当不插入id时,会自动生成一个long型的id,这个id还可能为负。这就是分布式系统的主键策略:雪花算法
@Testvoid testInsert() {User user = new User();user.setAge(20);user.setName("卿帆");user.setEmail("2259");userMapper.insert(user);}
3.2 主键生成策略
在User实体类中的id字段上添加注解@TableId
public class User {@TableId(type = IdType.*)private Integer id;}
1、IdType.ASSIGN_ID
雪花算法:保证id全球唯一
SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图:

● 1位,不用。二进制中最高位为1的都是负数,但是我们生成的id一般都使用整数,所以这个最高位固定是0
● 41位,用来记录时间戳(毫秒)。
○ 41位可以表示个数字,
○ 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 ,减1是因为可表示的数值范围是从0开始算的,而不是1。
○ 也就是说41位可以表示个毫秒的值,转化成单位年则是
%20%2F%20(1000%20%2060%20%2060%20%2024%20%20365)%20%3D%2069#card=math&code=%282%5E%7B41%7D-1%29%20%2F%20%281000%20%2A%2060%20%2A%2060%20%2A%2024%20%2A%20365%29%20%3D%2069&id=vuC0U)年
● 10位,用来记录工作机器id。
○ 可以部署在个节点,包括 5位datacenterId 和 5位workerId
○ 5位(bit)可以表示的最大正整数是,即可以用0、1、2、3、….31这32个数字,来表示不同的datecenterId或workerId
● 12位,序列号,用来记录同毫秒内产生的不同id。
○ 12位(bit)可以表示的最大正整数是,即可以用0、1、2、3、….4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号
SnowFlake可以保证:
● 所有生成的id按时间趋势递增
● 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)
2、 IdType.AUTO
主键自增策略,数据库中的id字段也必须设置为自动递增
3、其余策略
public enum IdType {AUTO(0), //自增NONE(1), //未设置主键INPUT(2), //手动输入id 必须自己set注入ASSIGN_ID(3), //默认全局唯一(全球唯一) 雪花算法ASSIGN_UUID(4); //全局唯一uuid}
3.3 更新
@Testvoid update(){User user=new User();user.setId(10);user.setEmail("22594797");userMapper.updateById(user);}
@Testvoid update2(){User user=new User();user.setId(10);user.setAge(3);user.setEmail("22594797");userMapper.updateById(user);}
自动拼接动态sql语句!!
3.4 自动填充
每个表中都应该带有更新时间和创建时间!这个操作可以自动化实现,方式有直接配置数据库和在代码中自动填充
3.4.1 数据库操作(不建议)
首先在数据库表中增加create_time和update_time字段,并需要勾选根据当前时间戳更新
User实体类中也添加对应的字段
private String createTime;private String updateTime;
再次更新表中数据时就会自动填充!
3.4.2 代码操作
(1)在User实体类中的字段上添加 @TableField注解
public class User {@TableId(type = IdType.AUTO)private Integer id;private String name;private Integer age;private String email;//插入时填充@TableField(fill = FieldFill.INSERT)private Date createTime;//插入-更新时填充@TableField(fill = FieldFill.INSERT_UPDATE)private Date updateTime;}
@TableField注解属性:
public enum FieldFill {/*** 默认不处理*/DEFAULT,/*** 插入时填充字段*/INSERT,/*** 更新时填充字段*/UPDATE,/*** 插入和更新时填充字段*/INSERT_UPDATE}
(2)创建一个MyMetaObjectHandler处理器,设置自动填充属性
@Slf4j//必须使用@Component注解将handler注入到ioc容器中@Componentpublic class MyMetaObjectHandler implements MetaObjectHandler {//插入@Overridepublic void insertFill(MetaObject metaObject) {log.info("start insert fill ....");//要填充的字段 填充值 metaObject//注意 这里的第二个属性使用new Date() 则实体类对属性值必须是Date属性 不然会报错 如:private Date createTime;this.setFieldValByName("createTime",new Date(),metaObject);this.setFieldValByName("updateTime",new Date(),metaObject);}//更新@Overridepublic void updateFill(MetaObject metaObject) {log.info("start update fill ....");this.setFieldValByName("updateTime",new Date(),metaObject);}}
3.5 乐观锁
实现方式
乐观锁实现方式:很乐观,认为任何操作都不会出现问题,出现问题时才会报错
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
会出现问题:在单线程下不会出现问题,但在多线程中,如果有一个线程A正在执行修改操作,他校验的version是1,但此时来了一个线程B。且抢先执行,修改数据后version变成了2,那么此时线程A的修改就会停止!!,最终数据只是线程B修改后的数据!!
(1)在数据库表中增加字段version,初始化为1,并在实体类中同步添加属性version
public class User {//乐观锁注解@Versionprivate Integer version;}
(2)配置MyBatisPlusConfig组件
@Configurationpublic class MyBatisPlusConfig {//注册乐观锁插件@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}}
(3)单线程测试
修改用户信息
@Testvoid testLock() {User user = userMapper.selectById(1);user.setName("qf");userMapper.updateById(user);}
修改时是带上了version字段的
数据库中的version版本号也会加一
(4)模拟多线程
@Testvoid testLock() {//模拟线程A修改数据User user = userMapper.selectById(1);user.setName("qf");//模拟模拟线程B修改数据,且抢先执行User user2=userMapper.selectById(1);user.setName("qf2");userMapper.updateById(user2);//线程A在线程B之后执行userMapper.updateById(user);}
可以看到,数据库中的数据为线程B提交的数据,A线程执行修改失败!
说明:
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下
newVersion = oldVersion + 1 newVersion会回写到entity中- 仅支持
updateById(id)与update(entity, wrapper)方法 - 在
**update(entity, wrapper)**方法下,**wrapper**不能复用!!!
3.6 查询
3.6.1 普通查询
@Testvoid testSelect(){//批量查询List<User> userList = userMapper.selectBatchIds(Arrays.asList(1, 2,10,15));userList.forEach(System.out::println);//条件查询 map拼接Map<String,Object> map=new HashMap<>();map.put("name","qf2");map.put("age",18);List<User> userList1 = userMapper.selectByMap(map);userList1.forEach(System.out::println);}
3.6.2分页查询
使用MP内置的分页插件
配置拦截器:
@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();//分页插件interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));return interceptor;}
测试:
@Testvoid testPage() {//当前页 页面大小Page<User> page = new Page<>(2, 5);//查询Page<User> userPage = userMapper.selectPage(page, null);//将page中的所有数据转存到实体集合中,方便返回给前端List<User> userList = userPage.getRecords();userList.forEach(System.out::println);}
3.7 删除
3.7.1 直接删除(不推荐)
直接删除数据库中的数据
@Testvoid testDel() {//删除Map<String, Object> map = new HashMap<>();map.put("age",18);//根据id删除userMapper.deleteById(100);//批量删除userMapper.deleteBatchIds(Arrays.asList(10, 20, 30));//通过map条件删除userMapper.deleteByMap(map);}
3.7.2 逻辑删除
在数据库中添加is_del逻辑删除字段,并在实体类中添加对应的isDel属性,添加 @TableLogic注解;默认使用驼峰映射
public class User {//逻辑删除@TableLogicprivate Integer isDel;}
配置yml
mybatis-plus:global-config:db-config:# 全局逻辑删除的实体字段名(,配置后可以不用在实体类字段上使用@TableLogic注解)logic-delete-field: isDel #这里的isDel需和实体类中的isDel对应logic-delete-value: 1 # 逻辑已删除值(默认为 1)logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
测试
@Testvoid testDel2() {/*配置了逻辑删除字段后,就无法在通过实体类来修改删除字段的值User user = new User();user.setId(1);user.setIsDel(1);userMapper.updateById(user);*///直接调用deleteById方法就行userMapper.deleteById(1);System.out.println(userMapper.selectById(1));}
四、条件构造器
@Testvoid testWrapper1(){QueryWrapper<User> wrapper = new QueryWrapper<>();//查询姓名不为空,年龄大于18的所有用户wrapper.isNotNull("name").ge("age",18);List<User> userList = userMapper.selectList(wrapper);//查询name等于qf的指定用户wrapper.eq("name","qf");userMapper.selectOne(wrapper);//查询age等于20的所有用户wrapper.eq("age",20);List<User> userList2 = userMapper.selectList(wrapper);//查询age在20-30之间的用户wrapper.between("age",20,30);//模糊查询 查询name中不包含e 且邮箱是以t开头的所有用户wrapper.notLike("name","a")//likeRight是以**开头,对右边迷糊查询,likeLeft则以**结尾对左边模糊查询.likeRight("email","t");List<User> userList = userMapper.selectList(wrapper);//查询id为1 2 3中任意一个的用户列表wrapper.in("id",Arrays.asList(1,2,3));List<User> userList = userMapper.selectList(wrapper);//groupBy orderByAsc orderByDesc having or and}
五、自动生成器(重要)
- dao、pojo、service、controller都自动编写完成!
- AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、
- Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
- 只需要改实体类名字 和包名 还有 数据库配置即可
pom依赖:
注:这是旧版生成器,mybatis-plus-generator包的版本必须是3.5.0以下的,如果使用3.5.0以上的则需要使用新版生成器
<!-- mybatisPlus 代码生成器 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.3.1.tmp</version></dependency><!-- mybatisPlus Velocity 模版引擎 --><dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.2</version></dependency><!-- mybatisPlus Freemarker 模版引擎 --><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.29</version></dependency>
方法:
public static void main(String[] args) {// 需要构建一个 代码自动生成器 对象AutoGenerator mpg = new AutoGenerator();// 配置策略// 1、全局配置GlobalConfig gcf = new GlobalConfig();String projectPath = System.getProperty("user.dir");gcf.setOutputDir(projectPath + "/src/main/java");gcf.setAuthor("卿帆");gcf.setOpen(false);// 是否覆盖gcf.setFileOverride(false);// 去Service的I前缀gcf.setServiceName("%sService");//设置主键策略 自增gcf.setIdType(IdType.AUTO);//日期类型gcf.setDateType(DateType.ONLY_DATE);//配置swagger文档gcf.setSwagger2(true);mpg.setGlobalConfig(gcf);//2、设置数据源DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus? useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");dsc.setDriverName("com.mysql.cj.jdbc.Driver");dsc.setUsername("root");dsc.setPassword("qing");dsc.setDbType(DbType.MYSQL);mpg.setDataSource(dsc);//3、包的配置PackageConfig pc = new PackageConfig();//只需要改实体类名字 和包名 还有 数据库配置即可//pc.setModuleName("");//父级包名pc.setParent("com.qing");pc.setEntity("entity");pc.setMapper("mapper");pc.setService("service");pc.setController("controller");mpg.setPackageInfo(pc);//4、策略配置StrategyConfig strategy = new StrategyConfig();//要转换的数据表 可同时配置多个strategy.setInclude("user");// 设置要映射的表名strategy.setNaming(NamingStrategy.underline_to_camel);strategy.setColumnNaming(NamingStrategy.underline_to_camel);// 自动lombok;strategy.setEntityLombokModel(true);//配置逻辑删除字段strategy.setLogicDeleteFieldName("isDel");// 自动填充配置//创建时间TableFill gmtCreate = new TableFill("create_time", FieldFill.INSERT);//修改时间TableFill gmtModified = new TableFill("update_time", FieldFill.INSERT_UPDATE);ArrayList<TableFill> tableFills = new ArrayList<>();tableFills.add(gmtCreate);tableFills.add(gmtModified);strategy.setTableFillList(tableFills);// 乐观锁strategy.setVersionFieldName("version");//驼峰命名strategy.setRestControllerStyle(true);// localhost:8080/hello_id_2strategy.setControllerMappingHyphenStyle(true);mpg.setStrategy(strategy);mpg.execute(); //执行}
