MyBatis-Plus

简介

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

代码托管:Gitee | Github

我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。

特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 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、研究依赖如何配置
3、代码如何编写
4、提高扩展技术能力!

快速开始

1、创建数据库 mybatis_plus
2、创建user表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)
);
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、编写项目,初始化项目!使用SpringBoot初始化!
4、导入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

使用 mybatis-plus 可以节省我们大量的代码,尽量不要同时导入 mybatis 和 mybatis-plus
5、配置数据库连接

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
datasource:
username: root
password: ****
url: jdbc:mysql://129.204.240.134:3306/mybatis_plus
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
dbcp2:
initial-size: 5
max-total: 20
min-idle: 5
pool-prepared-statements: true
max-open-prepared-statements: 20

6、准备实体类和mapper

  • pojo
1
2
3
4
5
6
7
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
  • mapper接口
1
2
3
4
5
// 在对应的Mapper上面继承基本的类 BaseMapper
@Repository // 代表持久层
public interface UserMapper extends BaseMapper<User> {
  // 所有的CRUD操作都已经编写完成了,你不需要像以前的配置一大堆文件了!
}

注意点:在主启动类上去扫描我们的mapper包下的所有接口,@MapperScan("com.atomsk.mapper")

7、测试类中测试

1
2
3
4
5
6
7
8
9
10
11
@SpringBootTest
class mybatis-plusApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
System.out.println(("----- selectAll method test ------"));
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
}

日志配置

1
2
3
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

CRUD扩展

插入操作

1
2
3
4
5
6
7
8
9
@Test
public void testInsert(){
User user = new User();
user.setName("atomsk");
user.setAge(22);
user.setEmail("atomsk@126.com");
int result = userMapper.insert(user);//结果为受影响行数
System.out.println(user);//会自动回填id
}

主键生成策略

默认为 ID_WORKER 全局的唯一id

分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html

雪花算法

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。可以保证几乎全球唯一!

配置为 AUTO 主键自增

  1. 在实体类id上增加 @TableId(type = IdType.AUTO),或者将全局配置mybatis-plus.global-config.db-config.id-type设置为 AUTO
  2. 数据库主键字段一定要勾选上自增选项!

IdType源码

1
2
3
4
5
6
7
public enum IdType {
AUTO(0),
NONE(1),
INPUT(2),
ASSIGN_ID(3),
ASSIGN_UUID(4)
}

更新操作

1
2
3
4
5
6
7
8
9
@Test
public void testUpdate(){
User user = new User();
user.setId(6L);
user.setName("范二林");
user.setEmail("fanerlin@85.com");
userMapper.updateById(user);
System.out.println(user);
}

可以看到日志,SQL是根据条件自动生成的

自动填充

阿里巴巴开发手册几乎所有的表都要配置上gmt_create、gmt_modified!而且需要自动化!

方式一:数据库级别

  1. 在表中新增字段 create_time、update_time,使用CURRENT_TIMESTAMP默认值,给update_time勾选更新选项
  1. 同步实体类,增加属性
1
2
private Date createTime;
private Date updateTime;
  1. 进行插入和更新操作后:

日志表明填充工作由数据库完成

方式二:代码级别

  1. 删除数据库字段的默认值和更新操作!
  1. 实体类属性上增加注解:
1
2
3
4
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

3. 编写处理器来处理这个注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Slf4j
@Component
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);
}
}
  1. 进行插入和更新操作后:

日志表面填充工作由MP完成

注意事项

  • 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段
  • 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component@Bean注入

乐观锁

乐观锁:故名思意十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题,再次更新值测试

悲观锁:故名思意十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!

乐观锁实现方式:

  • 取出记录时,获取当前 version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败
1
2
3
4
5
6
7
乐观锁:1、先查询,获得版本号 version = 1
-- A
update user set name = "kuangshen", version = version + 1
where id = 2 and version = 1
-- B 线程抢先完成,这个时候 version = 2,会导致 A 修改失败!
update user set name = "kuangshen", version = version + 1
where id = 2 and version = 1

使用MP的乐观锁插件

  1. 给数据库增加version字段,类型为int,默认值为1
  2. 同步实体类
1
2
@Version
private Integer version;
  1. 注册组件 optimisticLockerInterceptor
1
2
3
4
5
6
7
8
9
10
@MapperScan("com.atomsk.demo.mapper")
@EnableTransactionManagement
@Configuration
public class mybatis-plusConfig {
//乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor(){
return new OptimisticLockerInterceptor();
}
}
  1. 进行更新测试,日志输出:
1
2
3
==> Preparing: UPDATE user SET name=?, ... , version=? ... WHERE id=? AND version=? 
==> Parameters: atomsk(String), ... , 3(Integer), ... , 1(Long), 2(Integer)
<== Updates: 1

查询操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//测试批量查询
@Test
public void testSelectByBatchId() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}

//按条件查询之使用map
@Test
public void testSelectByBatchIds() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "atomsk");
map.put("age", 18);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}

分页查询

  1. 原始的limit进行分页
  2. pageHelper第三方插件
  3. MP内置的分页插件

如何使用

  1. 配置拦截器组件
1
2
3
4
5
//分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
  1. 直接使用Page对象即可
1
2
3
4
5
6
7
@Test
public void testPage(){
Page<User> page = new Page<>(1, 3);
userMapper.selectPage(page, null);
page.getRecords().forEach(System.out::println);
System.out.println(page.getTotal());
}

删除操作

1
2
3
4
5
6
7
8
9
10
11
12
13
//通过id批量删除
@Test
public void testDeleteBatchId(){
userMapper.deleteBatchIds(Arrays.asList(1, 2, 3));

}
//通过map删除
@Test
public void testDeleteMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name", "atomsk");
userMapper.deleteByMap(map);
}

逻辑删除

物理删除:从数据库中直接移除

逻辑删除:在数据库中没有被移除,而是通过一个变量来让他失效,deleted=0 -> deleted=1

使用

  1. 在数据库中增加一个deleted字段,类型为int,默认值为0

  2. 增加配置:

1
2
3
4
5
6
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  1. 实体类字段上增加注解:

    1
    2
    @TableLogic
    private Integer deleted;
  2. 测试

条件构造器

详情请看:https://mp.baomidou.com/guide/wrapper.html

AbstractWrapper

多个方法间默认用AND拼接,使用OR时要显式调用

例: eq("id",1).or().eq("name","老王")—>id = 1 or name = '老王'

  1. allEq(详见官网)

  2. in、notIn、between、notBetween,isNull、isNotNull:用法略

  3. inSql、notInSql:子查询,也可以和in、notIn一样使用

值比较

eq ne gt ge lt le
等于 = 不等于 <> 大于 > 大于等于 >= 小于 < 小于等于 <=

以eq为例子:

1
2
3
4
5
6
7
8
9
10
11
eq(R column, Object val)
eq(boolean condition, R column, Object val)

@Test
void contextLoads() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper
.isNotNull("name")
.eq("age",22);
userMapper.selectList(wrapper).forEach(System.out::println);
}

模糊查询

like notLike likeLeft likeRight
LIKE ‘%值%’ NOT LIKE ‘%值%’ LIKE ‘%值’ LIKE ‘值%’

例子:

1
2
3
4
5
6
7
8
9
10
11
@Test
void test4(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 左 %t 和右 t%
wrapper
.notLike("name","a")
.likeRight("email","t");

List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}

mybatis日志:

1
2
==>  Preparing: SELECT id,name,age,... FROM user WHERE deleted=0 AND (name NOT LIKE ? AND email LIKE ?) 
==> Parameters: %a%(String), t%(String)

排序

orderBy orderByAsc orderByDesc
ORDER BY 字段, … ORDER BY 字段, … ASC ORDER BY 字段, … DESC

orderBy,第一个true表示加入sql,第二个true表示按升序排列

1
orderBy(boolean condition, boolean isAsc, R... columns)

例: orderBy(true, true, "id", "name")—>order by id ASC,name ASC

orderByAsc和orderByDesc用法一样,以orderByAsc为例

1
2
orderByAsc(R... columns)
orderByAsc(boolean condition, R... columns)
  • 排序:ORDER BY 字段, … ASC
  • 例: orderByAsc("id", "name")—>order by id ASC,name ASC

groupBy和Having

1
2
groupBy(R... columns)
groupBy(boolean condition, R... columns)
  • 分组:GROUP BY 字段, …
  • 例: groupBy("id", "name")—>group by id,name
1
2
having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
  • HAVING ( sql语句 )
  • 例: having("sum(age) > 10")—>having sum(age) > 10
  • 例: having("sum(age) > {0}", 11)—>having sum(age) > 11

QueryWrapper

说明:

继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件

select

1
2
3
4
5
6
7
8
@Test
void testQuerryWrapper(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id,name,age");
//或者 wrapper.select("id","name","age");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}

代码生成器

自动生成pojo、mapper、service、controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class MPCodeGenerator {

//读取控制台内容
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new mybatis-plusException("请输入正确的" + tip + "!");
}

public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();

// 1.全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/mybatis-plus-code-generator/src/main/java");
gc.setAuthor("atomsk");
gc.setOpen(false);
gc.setFileOverride(false);//是否覆盖
gc.setServiceName("%sService");//去Service的I前缀
gc.setIdType(IdType.AUTO);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(true); //实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);

// 2.数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://129.204.240.134:3306/mybatis_plus");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("****");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);

// 3.包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模块名"));
pc.setParent("com.atomsk");
mpg.setPackageInfo(pc);

// 4.策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude(scanner("表名,多个则用英文逗号分割").split(","));//设置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true); //自动lombok

strategy.setVersionFieldName("version");//乐观锁
strategy.setLogicDeleteFieldName("deleted");//逻辑删除
//自动填充配置
TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(createTime);
tableFills.add(updateTime);
strategy.setTableFillList(tableFills);

strategy.setRestControllerStyle(true);//生成@RestController
strategy.setControllerMappingHyphenStyle(true);//生成@RequestMapping("/模块名/表名")
mpg.setStrategy(strategy);

mpg.execute();
}
}

生成的项目结构: