Mybatis-Plus (简称MP)是 MyBatis 的一个增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生
MyBatis-Plus提供了通用的mapper和service,可以在不编写任何SQL语句的情况下,快速的实现对单表的CRUD、批量、逻辑删除、分页等操作
润物无声
只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
效率至上
只需简单配置,即可快速进行单表CRUD操作,从而节省大量时间
功能丰富
代码生成、自动分页、逻辑删除、自动填充等功能一应俱全
任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库

1、扫描实体(ScanEntity)
第一步会先扫描当前所操作的实体类,扫描器会通过反射机制对实体类中的属性进行抽取
2、分析表与实体类之间的关系
分析表与实体类的关系,实体类中的属性与字段之间的关系
3、分析我们当前所调用的方法
根据我们当前所调用的方法生成对应的sql语句,将生成的sql语句注入到mybatis的容器中实现最终的功能
Lombok项目是一个java库,它可以自动插入到编辑器和构建工具中,增强java的性能,不需要再写getter、setter或equals方法,只要有一个注解,你的类就有一个功能齐全的构建器、自动记录变量等等
@NoArgsConstructor注解
生成字节码时自动为实体类生成无参构造
@AllArgsConstructor注解
自动生成全参构造
@Data
整合了Getter、Setter、ToString、EqualsAndHashCode、RequiredArgsConstructor注解
@Getter
快速构建Getter方法
@Setter
快速构建Setter方法
@ToString
快速将当前对象转换成字符串类型,便于log
@EqualsAndHashCode
快速进行相等判断
@TableName注解
创建实体时我们使用的主键是Long类型,数据库表主键字段是BigInt类型
是因为mybatisPlus使用的主键id是用雪花算法生成的
在mybatisPlus执行查询的时候指定实体匹配的表名
161package com.os467.pojo;2
3import com.baomidou.mybatisplus.annotation.TableName;4import lombok.*;5
67("tb_user")8public class User {9
10 private Long id;11
12 private String username;13
14 private String password;15
16}使用全局配置
设置实体类所对应表的统一前缀
11mybatis-plus.global-config.db-config.table-prefix=tb_
@TableId注解
设置实体类中的指定属性对应的字段为主键id
注解中的value属性可以设置当前属性所对应的表中的字段名
type属性
指定主键的生成方法,默认使用雪花算法IdType.ASSIGN_ID
如果要指定自动递增,首先表中的主键也必须是自动递增的
需要把type属性指定为IdType.AUTO
11(value = "id",type = IdType.AUTO)设置统一的主键生成策略
11mybatis-plus.global-config.db-config.id-type=auto
背景
数据库分表
单表数据拆分有两种方式:垂直分表和水平分表
垂直分表
垂直分表适合将表中某些不常用且占了大量空间的列拆分出去
水平分表
主键自增
优点:可以随着数据的增加平滑地扩充新的表
例如,现在的用户是 100 万,如果增加到 1000 万, 只需要增加新的表就可以了,原有的数据不需要动
取模
优点:表分布比较均匀
缺点:扩充新的表很麻烦,所有数据都要重分布
雪花算法
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性
长度共64bit(一个long型)
优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高
@TableField注解
指定属性所对应的字段名,value值为指定的表中的字段名
@TableLogic注解
此字段需要有默认值
比如未删除状态字段值默认值为0
delete语句会变成修改操作,会将删除状态字段数值改变为1
下次执行查询就无法查询到删除状态字段数值为1的数据
MyBatis-Plus中的基本增删改查在内置的BaseMapper类中都已得到了实现,我们可以直接使用
我们在让Mapper接口继承BaseMapper类的时候需要提供一个实体类的泛型
81package com.os467.mapper;2
3import com.baomidou.mybatisplus.core.mapper.BaseMapper;4import com.os467.pojo.User;5
6public interface UserMapper extends BaseMapper<User> {7
8}
在springboot启动类中开启映射包扫描
如果基于springboot开发,我们需要指定mapper接口所在的包以及mapper映射文件所在的路径
@MapperScan 用于扫描指定包下的mapper接口
将mapper包下由mybatis动态生成的接口代理类交由spring容器管理
151package com.os467.mybatisplus;2
3import org.mybatis.spring.annotation.MapperScan;4import org.springframework.boot.SpringApplication;5import org.springframework.boot.autoconfigure.SpringBootApplication;6
78("com.os467.mapper")9public class MybatisplusApplication {10
11 public static void main(String[] args) {12 SpringApplication.run(MybatisplusApplication.class, args);13 }14
15}
测试单元,测试是否能查询到数据
表和实体的名字需要相同才能被匹配到
关于Autowired报错,其实是idea编译器在检测时以为接口实例还没被创建出来,在运行时是不会报错的
291package com.os467.mybatisplus;2
3import com.os467.mapper.UserMapper;4import com.os467.pojo.User;5import org.junit.jupiter.api.Test;6import org.springframework.beans.factory.annotation.Autowired;7import org.springframework.boot.test.context.SpringBootTest;8
9
10import java.util.List;11
1213class MybatisplusApplicationTests {14
15 16 private UserMapper userMapper;17
18 19 void test01() {20
21 //通过条件构造器查询一个list集合,如果没有条件,则传入null为参数22 List<User> userList = userMapper.selectList(null);23
24 userList.forEach(System.out::println);25
26 }27
28}29
mybatis中内置的显示sql日志
11mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
xxxxxxxxxx1612public void test_insert(){3
4 User user = new User();5
6 user.setUsername("Tom001");7 user.setPassword("123456");8
9 int result = userMapper.insert(user);10
11 System.out.println("result"+result);12
13 System.out.println("uid:"+user.getId());14
15
16}
根据id删除数据
xxxxxxxxxx912public void test_deleteById(){3
4 //id是long类型所以在后面加L5 int result = userMapper.deleteById(1554390450146725889L);6
7 System.out.printf("result:"+result);8
9}
根据map集合提供的条件删除数据
xxxxxxxxxx1312public void test_deleteById(){3
4 Map<String,Object> map = new HashMap<>();5
6 map.put("username","张三");7 map.put("password",123);8
9 int result = userMapper.deleteByMap(map);10
11 System.out.println("result:"+result);12
13}
根据多个id条件删除数据
deleteBatchIds()这个方法使用的是sql中的in()函数
xxxxxxxxxx1012public void test_deleteById(){3
4 List<Long> longs = Arrays.asList(1L, 2L, 3L);5
6 int result = userMapper.deleteBatchIds(longs);7
8 System.out.println("result:"+result);9
10}
根据id修改用户数据
xxxxxxxxxx1212public void testUpdate(){3
4 User user = new User();5 user.setId(2L);6 user.setUsername("小明");7 user.setPassword("1234567");8
9 int result = userMapper.updateById(user);10
11 System.out.println("result:"+result);12}
通过id来查询用户信息
xxxxxxxxxx712public void testSelect(){3
4 User user = userMapper.selectById(1L);5 System.out.println(user);6
7}根据多个id来查询用户信息
xxxxxxxxxx812public void testSelect(){3
4 List<Long> longs = Arrays.asList(1L, 2L, 3L);5 List<User> users = userMapper.selectBatchIds(longs);6 users.forEach(System.out::println);7
8}根据Map集合中的内容查询
xxxxxxxxxx1312public void testSelect(){3
4 Map<String,Object> map = new HashMap<>();5
6 Object put = map.put("username", "张三");7 Object password = map.put("password", "123");8
9 List<User> users = userMapper.selectByMap(map);10
11 users.forEach(System.out::println);12
13}
需要创建mapper文件
不配置,默认是类路径下的mapper文件,此文件下的所有xml都会被当作映射文件
xxxxxxxxxx11mybatis-plus.mapper-locations=文件路径
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆
MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑
我们通常会选择创建一个Service接口来继承mybatisPlus的IService接口
xxxxxxxxxx71package com.os467.service;2
3import com.baomidou.mybatisplus.extension.service.IService;4import com.os467.pojo.User;5
6public interface UserService extends IService<User> {7}
我们在使用自己的service实现类的时候想要使用IService接口中的方法,但是有些方法我们又不想去重写,因此我们只需要继承一个IService提供的实现类即可
注意需要传入两个泛型,一个是当前类的父接口以及需要被操作的实体类
xxxxxxxxxx91package com.os467.service.impl;2
3import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;4import com.os467.mapper.UserMapper;5import com.os467.pojo.User;6import com.os467.service.UserService;7
8public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {9}
service接口查询总记录数
xxxxxxxxxx812public void testGetCount(){3
4 int count = userService.count();5
6 System.out.println("总记录数:"+count);7
8}
底层通过baseMapper中的单个添加的sql语句循环来实现的
xxxxxxxxxx2112public void insertMore(){3
4 List<User> list = new ArrayList<>();5
6 for (int i = 0; i <= 10; i++) {7
8 User user = new User();9
10 user.setUsername("os"+i);11 user.setPassword("os467"+i);12
13 list.add(user);14
15 }16
17 boolean b = userService.saveBatch(list);18
19 System.out.println(b);20
21}
Wrapper : 条件构造抽象类,最顶端父类
AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
QueryWrapper : 查询条件封装
UpdateWrapper : Update 条件封装
AbstractLambdaWrapper : 使用Lambda 语法
删除用的是QueryWrapper
xxxxxxxxxx1612public void test01(){3
4 //用户名包含a5 //密码不为空6 QueryWrapper<User> queryWrapper = new QueryWrapper<>();7
8 queryWrapper.like("username","小")9 .between("id",1,10)10 .isNotNull("password");11
12 List<User> users = userMapper.selectList(queryWrapper);13
14 users.forEach(System.out::println);15
16}
xxxxxxxxxx1312public void test02(){3
4 //查询用户信息按照id降序排序5 QueryWrapper<User> queryWrapper = new QueryWrapper<>();6
7 queryWrapper.orderByDesc("id");8
9 List<User> users = userMapper.selectList(queryWrapper);10
11 users.forEach(System.out::println);12
13}
因为设置了逻辑删除,此测试实际上是将删除状态从0变为了1
xxxxxxxxxx1312public void test03(){3
4 //删除名字包含o的5 QueryWrapper<User> queryWrapper = new QueryWrapper<>();6
7 queryWrapper.like("username","o");8
9 int result = userMapper.delete(queryWrapper);10
11 System.out.println("result:"+result);12
13}
第一个user对象是存放需要修改的信息,第二个wrapper是修改条件
xxxxxxxxxx1812public void test04(){3
4 //id大于1并且密码为123或名字包含"小"5 QueryWrapper<User> queryWrapper = new QueryWrapper<>();6
7 queryWrapper.gt("id",1).eq("username","123").8 or().like("username","小");9
10 User user = new User();11 user.setPassword("123");12
13 int result = userMapper.update(user, queryWrapper);14
15 System.out.println("result:"+result);16
17
18}
在and方法中使用lambda表达式
xxxxxxxxxx1712public void test05(){3
4 //名字包含"小"(id大于1或密码为123)5 //lambda中的条件优先执行6 QueryWrapper<User> queryWrapper = new QueryWrapper<>();7
8 queryWrapper.like("username","小").9 and(i->i.gt("id",1).or().eq("password","123"));10
11 User user = new User();12 user.setPassword("1234");13 int result = userMapper.update(user, queryWrapper);14
15 System.out.println("result:"+result);16
17}
xxxxxxxxxx1412public void test06(){3 4 //查询用户的用户名,年龄,邮箱信息5 QueryWrapper<User> queryWrapper = new QueryWrapper<>();6 7 //指定需要查询的字段8 queryWrapper.select("username","password");9
10 List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);11 12 maps.forEach(System.out::println);13
14}
xxxxxxxxxx1312public void test07(){3
4 //查询id小于100的用户信息,用子查询5 QueryWrapper<User> queryWrapper = new QueryWrapper<>();6
7 queryWrapper.inSql("id","select id from tb_user where id < 100");8
9 List<User> users = userMapper.selectList(queryWrapper);10
11 users.forEach(System.out::println);12
13}
xxxxxxxxxx1712public void test08(){3
4 //名字包含"小"(id大于1或密码为1234)5 UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();6
7 updateWrapper.like("username","小").8 and(i->i.gt("id",1).or().eq("password","1234"));9
10 //设置需要修改的字段11 updateWrapper.set("username","小黑");12
13 int result = userMapper.update(null, updateWrapper);14
15 System.out.println("result:"+result);16
17}
此方法需要用if语句判断较为麻烦
xxxxxxxxxx2612public void test09(){3
4 String username = "";5 String password = "123";6
7 QueryWrapper<User> queryWrapper = new QueryWrapper<>();8
9 //mybatis-plus内置字符串工具类,判断某个字符串不为空字符串,不为null,不为空白符10 if (StringUtils.isNotBlank(username)){11
12 queryWrapper.like("username","小");13
14 }15
16 if (password != null){17
18 queryWrapper.eq("password","1234");19
20 }21
22 List<User> users = userMapper.selectList(queryWrapper);23
24 users.forEach(System.out::println);25
26}
与上面的代码实现相同,但是更加简洁
xxxxxxxxxx1612public void test10(){3
4 String username = "";5 String password = "123";6
7 QueryWrapper<User> queryWrapper = new QueryWrapper<>();8
9 queryWrapper.like(StringUtils.isNotBlank(username),"username","小").10 eq(password != null,"password","1234");11
12 List<User> users = userMapper.selectList(queryWrapper);13
14 users.forEach(System.out::println);15
16}
能够防止字段名写错,此方法使用函数式接口,mybatis-plus会自动去匹配对应的字段
xxxxxxxxxx1612public void test11(){3
4 String username = "";5 String password = "123";6
7 LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();8
9 queryWrapper.like(StringUtils.isNotBlank(username),User::getUsername,username).10 eq(password != null,User::getPassword,password);11
12 List<User> users = userMapper.selectList(queryWrapper);13
14 users.forEach(System.out::println);15
16}
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
需要创建一个spring核心配置类
xxxxxxxxxx1612("com.os467.mapper")3public class MyBatisPlusConfig {4
5 6 public MybatisPlusInterceptor mybatisPlusInterceptor(){7
8 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();9
10 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));11
12 return interceptor;13
14 }15
16}
测试分页
xxxxxxxxxx1112void test01() {3
4 Page<User> page = new Page<>(2,3);5
6 userMapper.selectPage(page,null);7
8 System.out.println(page.getRecords());9 System.out.println(page.getPages());10
11}
自定义查询分页
接口中定义方法
@Param注解 为参数命名,通过OGNL表达式获取
Page对象必须在参数第一个位置
xxxxxxxxxx71/**2 * 通过id查询用户信息然后分页3 * @param page MyBatis-Plus所提供的分页对象,必须在参数第一个位置4 * @param id5 * @return6 */7Page<User> selectPageVo(("page") Page<User> page, Integer id);
测试
xxxxxxxxxx1012void test02() {3
4 Page<User> page = new Page<>(2,3);5
6 userMapper.selectPageVo(page, 3);7
8 System.out.println(page);9 10}
一件商品,成本价是80元,售价是100元。
老板先是通知小李,说你去把商品价格增加50元。小 李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太 高,可能会影响销量。又通知小王,你把商品价格降低30元。
此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王 也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据 库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就 完全被小王的覆盖了。
现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1万多。
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库
如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。
数据库中添加version字段
取出记录时,获取当前version
xxxxxxxxxx11SELECT id,`name`,price,`version` FROM product WHERE id=1更新时,version + 1,如果where语句中的version版本不对,则更新失败
xxxxxxxxxx11UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1
修改实体类
为version字段添加@Version注解 标识乐观锁版本号字段
xxxxxxxxxx111package com.atguigu.mybatisplus.entity;2import com.baomidou.mybatisplus.annotation.Version;3import lombok.Data;45public class Product {6private Long id;7private String name;8private Integer price;910private Integer version;11}
在spring核心配置类中的MybatisPlusInterceptor拦截器中配置
在之前的分页插件之后继续配置
xxxxxxxxxx1012public MybatisPlusInterceptor mybatisPlusInterceptor(){3MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();4//添加分页插件5interceptor.addInnerInterceptor(new6PaginationInnerInterceptor(DbType.MYSQL));7//添加乐观锁插件8interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());9return interceptor;10}
因为乐观锁版本号改变而导致的操作失败如何解决?
xxxxxxxxxx131//假设版本号改变操作失败,返回result为02int result = productMapper.updateById(1);3
4//判断影响记录条数5if(result == 0){6 7 //操作失败,重试,查询查询获取到版本号8 Product productNew = productMapper.selectById(1);9 10 productNew.setPrice(productNew.getPrice()-30);11 12 productMapper.updateById(productNew);13}
需要在配置文件中设置扫描通用枚举包
需要传入通用枚举类型送在包
xxxxxxxxxx11mybatis-plus.type-enums-package=com.os467.enums设置性别枚举类型
@EnumValue注解 将此注解标注的枚举类型属性值存入数据库中
221package com.os467.enums;2
3import com.baomidou.mybatisplus.annotation.EnumValue;4import lombok.Getter;5
67public enum SexEnum {8
9 MALE(1,"男"),10 FEMALE(2,"女");11
12 13 private Integer sex;14 private String sexName;15
16 SexEnum(Integer sex, String sexName) {17 this.sex = sex;18 this.sexName = sexName;19 }20
21}22
测试
912public void test(){3 User user = new User();4 user.setId(1L);5 user.setSex(SexEnum.FEMALE);6 int result = userMapper.updateById(user);7
8 System.out.println("result:"+result);9}