MyBatisPlus

简介

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

框架结构

image-20220330213819094

入门案例

相关starter依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

实体类

1
2
3
4
5
6
7
8
9
10
11
@Data
@AllArgsConstructor
public class User {
private Long id;

private String name;

private Integer age;

private String email;
}

mapper接口

1
2
3
@Repository
public interface UserMapper extends BaseMapper<User> {
}

需要application添加上@MapperScan(“com.wht.mapper”)注解扫描mapper接口/或者用@Mapper注解

入门测试

1
2
3
4
5
6
7
8
@Autowired
private UserMapper userMapper;

@Test
public void testSelectList(){
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}

BaseMapper的具体功能介绍

添加功能

1
2
3
4
5
6
7
8
9
@Test
public void testInsert(){
User user = new User(null,"张三",23,"1369281736@qq.com");

int res = userMapper.insert(user);

System.out.println("res:"+res);
System.out.println("id:"+user.getId());
}

删除功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void testDelete(){
//根据主键id删除
int res = userMapper.deleteById(1509495136467660801L);
System.out.println("res:"+res);
}

@Test
public void testDelete(){
//根据map条件删除
Map<String,Object> map = new HashMap<>();
map.put("name", "张三");
int res = userMapper.deleteByMap(map);
System.out.println("res:"+ res);
}

@Test
public void testDelete(){
//批量删除
List<Long> list = Arrays.asList(1L, 2L);
int res = userMapper.deleteBatchIds(list);
System.out.println("res:"+ res);
}

修改功能

1
2
3
4
5
6
7
8
9
@Test
public void testUpdate(){
User user = new User();
user.setId(4L);
user.setName("李四");
//根据id修改
int res = userMapper.updateById(user);
System.out.println("res:"+ res);
}

查询功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void testSelect(){
//根据单一id查询
User user = userMapper.selectById(4L);
System.out.println(user);
//多个id查询
List<User> list = userMapper.selectBatchIds(Arrays.asList(3L, 4L));
list.forEach(System.out::println);
//根据条件map查询
HashMap<String, Object> map = new HashMap<>();
map.put("name", "李四");
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}

自定义功能

完全同MyBatis:

  • 在Mapper接口中定义方法
  • 在项目resources.mapper文件夹下创建对应的XXXmapper.xml
  • 编写对应sql

通用Service接口

说明:

  • 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行remove 删除list 查询集合 page 分页 前缀命名方式区分 Mapper层避免混淆,

  • 泛型 T 为任意实体对象

  • 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService继承 Mybatis-Plus提供的基类

  • 对象 Wrapper 为 条件构造器

简单使用

为了方便以后自定义需要自行定义一个接口和一个实现类来继承MyBatisPlus提供的接口和实现类

  • service接口
1
2
public interface UserService extends IService<User> {
}
  • service实现类(需要加入容器)
1
2
3
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
  • 进行测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@SpringBootTest
public class MyBatisServiceTest {

@Autowired
private UserService userService;
//获取总记录数
@Test
public void testGetCount(){
long count = userService.count();
System.out.println("总记录数:"+count);
}
//批量插入
@Test
public void testBathAdd(){
List<User> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
User user = new User();
user.setName("李"+i);
user.setAge(20+i);
list.add(user);
}
userService.saveBatch(list);
}
}

MyBatisPlus提供的注解

  • 设置实体类所对应的表名

    • @TableName(“数据库的真实表名”)

    • 或者使用全局配置

      1
      2
      3
      global-config:
      db-config:
      table-prefix: t_
  • 因为MyBatisPlus默认以id为主键,所以当主键名不为id时需要使用注解来指定

    • @TableId
      • 属性:
        • value = “数据库主键名”
        • type = IdType. AUTO/IdType. ASSIGN_ID (自增/雪花算法)
  • 设置实体类属性与数据库字段名的对应

    • @TableField(“字段名”)
  • 设置数据的逻辑删除

    • 首先在表中设置一个逻辑删除判断字段
      • 并在实体类添加该属性
      • 再使用@TableLogic字段
  • 当实体类使用通用枚举属性时

    • 配置枚举类

    • 在相应的枚举类里的存数据库的字段上添加@EnumValue

    • 设置全局配置: type-enums-package: com.wht.enums

MyBatisPlus条件构造器

  • Wrapper:条件构造抽象类
    • AbostractWrapper:用于查询条件封装,生成sql的where条件
      • QueryWrapper:查询条件封装
      • UpdateWrapper:更新条件封装
      • AbstractLambdaWrapper:使用Lambda语法
        • LambdaQueryWrapper:用于Lambda语法使用的查询条件构造器
        • LambdaUpdateWrapper:用于Lambda语法使用的更新条件构造器

组装查询条件

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
@Autowired
private UserMapper userMapper;

@Test
public void test01(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//用户username包含a年龄在20~30之间并且email不为空
wrapper.like("name", "a")
.between("age", 20, 30)
.isNotNull("email");
List<User> list = userMapper.selectList(wrapper);
list.forEach(System.out::println);
}

@Test
public void test06() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//只查询设置的字段
wrapper.select("name", "age", "email");
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}

@Test
public void test07() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//使用子查询查询id小于等于100的用户信息
wrapper.inSql("id", "select id from user where id <= 100");
List<User> list = userMapper.selectList(wrapper);
list.forEach(System.out::println);
}

@Test
public void test09() {
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
QueryWrapper<User> wrapper = new QueryWrapper<>();
if(StringUtils.isNotBlank(username)){
//isNotBlank判断某个字符是否为空串不为null不为空白符
wrapper.like("name", username);
}
if(ageBegin != null){
wrapper.ge("age", ageBegin);
}
if(ageEnd != null){
wrapper.le("age", ageEnd);
}
List<User> list = userMapper.selectList(wrapper);
list.forEach(System.out::println);
}

@Test
public void test10() {
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like(StringUtils.isBlank(username), "name", username)
.ge(ageBegin != null, "age", ageBegin)
.le(ageEnd != null, "age",ageEnd);
List<User> list = userMapper.selectList(wrapper);
list.forEach(System.out::println);
}

排序条件

1
2
3
4
5
6
7
8
@Test
public void test02(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//按照年龄降序排序若年龄相同按照id升序排序
wrapper.orderByDesc("age").orderByAsc("id");
List<User> list = userMapper.selectList(wrapper);
list.forEach(System.out::println);
}

删除条件

1
2
3
4
5
6
7
8
@Test
public void test03(){
//删除邮箱为null的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.isNull("email");
int i = userMapper.delete(wrapper);
System.out.println("res:"+i);
}

修改条件

  • 使用QueryWrapper
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
@Test
public void test04(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//将年龄大于20且用户名包含有a或者邮箱为null的用户信息进行修改
wrapper.gt("age", 20)
.like("name", "a")
.or()
.isNull("email");
User user = new User();
user.setName("张四");
int i = userMapper.update(user, wrapper);
System.out.println("res:"+i);
}

@Test
public void test05(){
QueryWrapper<User> wrapper = new QueryWrapper<>();
//将用户名包含有a并且(年龄大于20且或者邮箱为null)的用户信息进行修改
wrapper.like("name", "a")
.and(i -> i.gt("age", 20).or().isNull("email"));
User user = new User();
user.setName("张四");
int i = userMapper.update(user, wrapper);
System.out.println("res:"+i);
}
  • 使用UpdateWrapper
1
2
3
4
5
6
7
8
9
10
@Test
public void test08() {
//将用户名包含有a并且(年龄大于20且或者邮箱为null)的用户信息进行修改
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.like("name", "a")
.and(i -> i.gt("age", 20).or().isNull("email"));;
wrapper.set("name", "大王").set("email", "1369281736@qq.com");
int i = userMapper.update(null, wrapper);
System.out.println("res:"+i);
}

Lambda条件构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void test11() {
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isBlank(username), User::getName, username)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null, User::getAge,ageEnd);
List<User> list = userMapper.selectList(wrapper);
list.forEach(System.out::println);
}

@Test
public void test12() {
//将用户名包含有a并且(年龄大于20且或者邮箱为null)的用户信息进行修改
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.like(User::getName, "a")
.and(i -> i.gt(User::getAge, 20).or().isNull(User::getEmail));;
wrapper.set(User::getName, "大王").set(User::getEmail, "1369281736@qq.com");
int i = userMapper.update(null, wrapper);
System.out.println("res:"+i);
}

MyBatisPlus分页插件

  • 配置分页插件拦截器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration
    @MapperScan("com.wht.mapper")
    public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    return interceptor;
    }
    }
  • 测试分页

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Autowired
    private UserMapper userMapper;

    @Test
    public void test01(){
    //创建分页对象 使用构造器(当前页码,分页大小)
    Page<User> page = new Page<>(1,3);
    //查询返回page的分页对象
    Page<User> userPage = userMapper.selectPage(page, null);
    System.out.println(page);
    }
  • 自定义功能分页

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //mapper接口中的方法
    Page<User> selectPageVo(@Param("page") Page<User> page,@Param("age") Integer age);
    //测试
    @Test
    public void test02(){
    Page<User> page = new Page<>(1,3);
    Page<User> userPage = userMapper.selectPageVo(page, 15);
    System.out.println(userPage.getRecords());
    }

MyBatisPlus乐观锁插件

数据库由于并发操作的问题引起的数据查询结果不一致的问题:

解决方法:

  • 悲观锁:当一个用户对数据库操作时会直接加锁,另一个用户只能阻塞等待
  • 乐观锁:认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了

MyBatisPlus乐观锁的实现:

1
2
3
4
5
6
//首先数据库和实体类需要多一个版本号的字段
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号'
@Version //标识版本号字段
private Integer version;
//在配置类中添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

多数据源配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
spring:
# 配置数据源信息
datasource:
dynamic:
# 设置默认的数据源或者数据源组,默认值即为master
primary: master
# 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-
8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-
8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456

并在service上使用@DS(“数据源”)