可以节省大量时间,所有的CRUD代码可以自动化完成
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CRUD,性能基本无损耗,直接面向对象操作
- 强大的 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
- 创建数据库 mybatis_plus
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)
);
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');
- 创建一个springboot工程 导入依赖
mysql mysql-connector-java org.projectlombok lombok com.baomidou mybatis-plus-boot-starter 3.0.5
注:尽量不要mybatis-plus和mybatis同时导入
- 连接数据库
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3305/mybatis_plus?serverTimezone=UTC&useUnicode=true&useSSL=true&characterEncoding=utf-8
username: root
password: root
-
传统方式:pojo-dao-service-controller
-
mybatis-plus:pojo-mapper-使用
pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
mapper
使用mybatis-plus后,只需写一个UserMapper接口,继承BaseMapper接口,此时CRUD已经编写完毕,是因为BaseMapper已经存在了一些常用的CRUD方法,但是如果没有自己需要的,就可以在UserMapper接口中编写自定义的
@Repository public interface UserMapper extends BaseMapper{ }
@SpringBootApplication
@MapperScan("com.hua.dao")
public class MyBatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MyBatisPlusApplication.class, args);
}
}
test
@SpringBootTest
class MyBatisPlusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void contextLoads() {
List userList = userMapper.selectList(null);
for (User user : userList) {
System.out.println(user);
}
}
}
2. 配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3. CRUD扩展
3.1 插入操作
// 测试插入语句
@Test
public void testInsert(){
User user = new User();
user.setName("小明");
user.setAge(12);
user.setEmail("123@163.com");
int insert = userMapper.insert(user);
System.out.println(insert);
System.out.println(user);
// 此时并没有设置ID,观察日志看到会自动生成一个long的数据进行插入,这个数是通过雪花算法得到的
}
主键生成策略
uuid、自增id、雪花算法、redis、zookeeper
雪花算法:是Twitter开源的分布式ID生成算法,结果是一个Long类型的ID;核心思想就是使用41bit作为毫秒数,10bit作为机器的id,12bit作为毫秒内的流水号,最后还有一个符号位,永远是0。
AUTO(0), // 主键自增 NONE(1), // 不设置主键 INPUT(2), // 手动输入 需要自己写id ID_WORKER(3), // 默认的全局id UUID(4), // 全局唯一id ID_WORKER_STR(5); // ID_WORKER的字符串表示
默认策略:全局唯一id @TableId(type = IdType.ID_WORKER) 在主键添加注解
主键自增:@TableId(type = IdType.AUTO) 并且数据库主键设置为自增 缺一不可
3.2 更新操作@Test
public void testUpdate(){
User user = new User();
user.setId(1536171948541259778L);
user.setName("小红"); // 如果实体类字段中整型用的是int 就会赋以初始值0,就算此时没有改动年龄,也会修改年龄为0,所以实体类最好用Integer,初始值为空,这样就不会添加age字段
userMapper.updateById(user);
}
@Test
public void testUpdate(){
User user = new User();
user.setId(1536171948541259778L);
user.setName("小红");
user.setAge(15);
userMapper.updateById(user);
}
可以看到根据修改的字段不同,自动拼接sql语句,实现动态sql的功能
自动填充数据库表中的创建时间和修改时间,这些操作希望是自动化完成,不需要手动编写。
Alibaba的开发手册规定所有的表都必须要有:gmt_create、gmt_modified,而且需要自动化
方式一:数据库级别修改(不建议使用)
- 在表中增加字段create_time,update_time
- 实体类同步
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
private Date createTime;
private Date updateTime;
}
- 再次执行更新语句
方式二:代码级别
-
删除数据库的默认值和和根据当前时间戳更新操作
-
实体类的字段属性增加注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long 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;
}
- 编写处理器处理注解
package com.hua.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component // 一定要注册到容器中
@Slf4j // 使用日志输出
public class MyMetaObjectHandler implements MetaObjectHandler {
// 插入时的填充策略
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill...");
this.setFieldValByName("createTime",new Date(),metaObject);
this.setFieldValByName("updateTime",new Date(),metaObject);
}
// 更新时的填充策略
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill...");
this.setFieldValByName("updateTime",new Date(),metaObject);
}
}
可以看到修改成功,但是有一问题,测试时间是下午的16:40多,但是更新的时间却是早上八点多,出现这一问题的原因是配置文件中的时区写的是UTC,需要改为 GMT%2B8
3.3 乐观锁乐观锁:十分乐观,认为不会出现问题,无论干什么都不会上锁,如果出现问题就再次更新值测试;
悲观锁:十分悲观,认为都会出现问题,无论干什么都会上锁再去操作;
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果 version 不对,就更新失败
# 乐观锁先获取版本号 version = 1 update user set name = "张三",version = version + 1 where id = 2 and version = 1
测试
- 给数据库中增加version字段
- 实体类加对应字段
@Version //代表这是一个乐观锁 private Integer version;
- 注册组件
@Configuration // 配置类
@EnableTransactionManagement
@MapperScan("com.hua.dao") // 此时就可以在此处扫描包了
public class MybatisPlusConfig {
// 注册乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
- 测试
// 测试乐观锁成功
@Test
public void testVersion1(){
// 查询用户信息
User user = userMapper.selectById(1L);
// 修改用户信息
user.setName("哈哈哈哈哈哈哈");
user.setEmail("4451544511@163.com");
// 执行更新操作
int i = userMapper.updateById(user);
}
// 测试乐观锁失败
@Test
public void testVersion2(){
// 线程1
User user1 = userMapper.selectById(1L);
user1.setName("线程一");
user1.setEmail("4451544511@163.com");
// 模拟另一个线程执行插队操作
User user2 = userMapper.selectById(1L);
user2.setName("线程二");
user2.setEmail("4451544511@163.com");
int i2 = userMapper.updateById(user2);
int i1 = userMapper.updateById(user1); // 如果没有乐观锁就会覆盖插队线程的值
}
3.4 查询操作
// 通过id查询单个用户
@Test
public void testSelectByID(){
User user = userMapper.selectById(1L);
System.out.println(user);
}
// 通过id批量查询用户
//SELECT id,name,age,email,create_time,update_time,version FROM user WHERe id IN ( ? , ? , ? )
@Test
public void testSelectByBatchID(){
List userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
for (User user : userList) {
System.out.println(user);
}
}
// 条件查询 map map的内容自动拼接sql
//SELECT id,name,age,email,create_time,update_time,version FROM user WHERe name = ? AND age = ?
@Test
public void testSelectByBatchIDs(){
HashMap map = new HashMap<>();
map.put("name","小红");
map.put("age",15);
List userList = userMapper.selectByMap(map);
for (User user : userList) {
System.out.println(user);
}
}
3.5 分页查询
-
limit分页
-
第三方插件
-
MP内置了分页插件
-
配置拦截器组件
// 注册分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
- 直接使用Page对象
// 测试分页查询
@Test
public void testPage(){
Page userPage = new Page<>(2,5);
userMapper.selectPage(userPage,null);
List userList = userPage.getRecords();
for (User user : userList) {
System.out.println(user);
}
}
3.6 删除操作
// 测试删除
// 通过id删除
@Test
public void testDeleteByID(){
int i = userMapper.deleteById(8L);
}
// 通过id批量删除
// DELETE FROM user WHERe id IN ( ? , ? , ? )
@Test
public void testDeleteByBatchIDs(){
int i = userMapper.deleteBatchIds(Arrays.asList(9, 10, 11));
}
// 条件删除 map
// DELETE FROM user WHERe name = ? AND age = ?
@Test
public void testDeleteByMap(){
HashMap map = new HashMap<>();
map.put("name","线程二");
map.put("age","18");
userMapper.deleteByMap(map);
}
3.7 逻辑删除
物理删除:数据库中删除
逻辑删除:在数据库没有被移除,而是通过变量来失效
- 增加一个deleted字段
- 实体类中增加属性
@TableLogic // 代表这是一个逻辑删除 private Integer deleted;
- 配置
// 注册逻辑删除插件
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
在配置文件中配置
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-value: 1 # 真正删除为字段为1
logic-not-delete-value: 0 # 逻辑删除字段为0
- 测试
性能分析插件就是当遇到一些慢sql时,当执行时间超过规定值就是停止运行的插件。
作用:输出每条SQL语句及其执行时间
- 导入插件
// 注册性能分析器插件
@Bean
@Profile({"dev","test"}) // 只有在开发环境和测试环境下开启,设置当前环境为开发环境
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor interceptor = new PerformanceInterceptor();
interceptor.setMaxTime(1);//这只sql执行的最大时间,单位为毫秒
interceptor.setFormat(true); //设置sql的格式化输出
return interceptor;
}
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis-plus?serverTimezone=GMT%2B8&useUnicode=true&useSSL=true&characterEncoding=utf-8
username: root
password: root
profiles:
active: dev # 设置当前环境为开发环境
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
- 执行SQL进行测试
@Test
void contextLoads() {
List userList = userMapper.selectList(null);
for (User user : userList) {
System.out.println(user);
}
}
5. 条件查询器Wrapper
- 测试一
// 查询name不为空,邮箱不为空,年龄大于20的用户
@Test
public void test1(){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.isNotNull("name").isNotNull("email").gt("age",20);
List userList = userMapper.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
// SELECT id,... FROM user WHERe deleted=0 AND name IS NOT NULL AND email IS NOT NULL AND age >= ?
- 测试二
@Test
// 查询名字等于小红果 只查询一个结果
public void test2(){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("name","小红果");
User user = userMapper.selectOne(wrapper);
System.out.println(user);
}
// SELECT id,... FROM user WHERe deleted=0 AND name = ?
- 测试三
// 查询年龄在20-28的用户和用户数
@Test
public void test3(){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.between("age",20,28);
List userList = userMapper.selectList(wrapper);
Integer integer = userMapper.selectCount(wrapper);
System.out.println("查询总数为:"+integer);
for (User user : userList) {
System.out.println(user);
}
}
// SELECT id,... FROM user WHERe deleted=0 AND age BETWEEN ? AND ?
// SELECT COUNT(1) FROM user WHERe deleted=0 AND age BETWEEN ? AND ?
- 测试四
// 模糊查询
@Test
// SELECT id,... FROM user WHERe deleted=0 AND name NOT LIKE ? AND email LIKE ?
// %m%(String), t%(String) 不包含m的,以t开头的
public void test4(){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper
.notLike("name","m")
.likeRight("email","t");
List
- 测试五
// 子查询
@Test
// SELECT id,... FROM user WHERe deleted=0 AND id IN (select id from user where id<3)
public void test5(){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.inSql("id","select id from user where id<3");
List
- 测试六
// 排序
@Test
// SELECT id,... FROM user WHERe deleted=0 ORDER BY id DESC
public void test6(){
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
List userList = userMapper.selectList(wrapper);
for (User user : userList) {
System.out.println(user);
}
}
。。。。。。
6. 代码自动生成器- 导入mybatis-plus依赖
- 连接数据库
- 导入mp配置
package com.hua;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import java.util.ArrayList;
// 代码自动生成器
public class test {
public static void main(String[] args) {
// 需要构建一个代码自动生成器对象
AutoGenerator mpg = new AutoGenerator();
// 配置策略
// 1. 全局配置
GlobalConfig config = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
config.setOutputDir(projectPath+"src/main/java");
config.setAuthor("陈一");
config.setOpen(false);
config.setFileOverride(false);//是否覆盖
config.setServiceName("%sService"); // 去Service的I前缀
config.setIdType(IdType.ID_WORKER);
config.setDateType(DateType.ONLY_DATE);
config.setSwagger2(true);
mpg.setGlobalConfig(config);
// 2. 设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUrl("jdbc:mysql://localhost:3306/mybatis-plus?serverTimezone=GMT%2B8&useUnicode=true&useSSL=true&characterEncoding=utf-8");
dsc.setUsername("root");
dsc.setPassword("root");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 3. 包的配置
PackageConfig packageConfig = new PackageConfig();
packageConfig.setModuleName("blog");
packageConfig.setParent("com.hua");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
packageConfig.setController("controller");
mpg.setPackageInfo(packageConfig);
// 4. 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user");//设置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);// 下划线转驼峰
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true);// 自动lombok
strategy.setLogicDeleteFieldName("deleted");//逻辑删除列名
// 自动填充实现
TableFill gmtCreate = new TableFill("gmt_create", FieldFill.INSERT);
TableFill gmtUpdate = new TableFill("gmt_update", FieldFill.INSERT_UPDATE);
ArrayList tableFills = new ArrayList<>();
tableFills.add(gmtCreate);
tableFills.add(gmtUpdate);
strategy.setTableFillList(tableFills);
// 乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true); //localhost:8080/hello_id_2
mpg.setStrategy(strategy);
mpg.execute();
}
}



