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
6
7"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
11value = "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
7
8"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
12
13class 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
xxxxxxxxxx
161
2public 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删除数据
xxxxxxxxxx
91
2public void test_deleteById(){
3
4 //id是long类型所以在后面加L
5 int result = userMapper.deleteById(1554390450146725889L);
6
7 System.out.printf("result:"+result);
8
9}
根据map集合提供的条件删除数据
xxxxxxxxxx
131
2public 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()
函数
xxxxxxxxxx
101
2public 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修改用户数据
xxxxxxxxxx
121
2public 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来查询用户信息
xxxxxxxxxx
71
2public void testSelect(){
3
4 User user = userMapper.selectById(1L);
5 System.out.println(user);
6
7}
根据多个id来查询用户信息
xxxxxxxxxx
81
2public 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集合中的内容查询
xxxxxxxxxx
131
2public 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都会被当作映射文件
xxxxxxxxxx
11mybatis-plus.mapper-locations=文件路径
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分 Mapper
层避免混淆
MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑
我们通常会选择创建一个Service接口来继承mybatisPlus的IService接口
xxxxxxxxxx
71package 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提供的实现类即可
注意需要传入两个泛型,一个是当前类的父接口以及需要被操作的实体类
xxxxxxxxxx
91package 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接口查询总记录数
xxxxxxxxxx
81
2public void testGetCount(){
3
4 int count = userService.count();
5
6 System.out.println("总记录数:"+count);
7
8}
底层通过baseMapper中的单个添加的sql语句循环来实现的
xxxxxxxxxx
211
2public 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
xxxxxxxxxx
161
2public void test01(){
3
4 //用户名包含a
5 //密码不为空
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}
xxxxxxxxxx
131
2public 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
xxxxxxxxxx
131
2public 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是修改条件
xxxxxxxxxx
181
2public 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表达式
xxxxxxxxxx
171
2public 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}
xxxxxxxxxx
141
2public 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}
xxxxxxxxxx
131
2public 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}
xxxxxxxxxx
171
2public 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语句判断较为麻烦
xxxxxxxxxx
261
2public 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}
与上面的代码实现相同,但是更加简洁
xxxxxxxxxx
161
2public 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会自动去匹配对应的字段
xxxxxxxxxx
161
2public 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核心配置类
xxxxxxxxxx
161
2"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}
测试分页
xxxxxxxxxx
111
2void 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对象必须在参数第一个位置
xxxxxxxxxx
71/**
2 * 通过id查询用户信息然后分页
3 * @param page MyBatis-Plus所提供的分页对象,必须在参数第一个位置
4 * @param id
5 * @return
6 */
7Page<User> selectPageVo( ("page") Page<User> page, Integer id);
测试
xxxxxxxxxx
101
2void 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
xxxxxxxxxx
11SELECT id,`name`,price,`version` FROM product WHERE id=1
更新时,version + 1,如果where语句中的version版本不对,则更新失败
xxxxxxxxxx
11UPDATE product SET price=price+50, `version`=`version` + 1 WHERE id=1 AND `version`=1
修改实体类
为version字段添加@Version注解 标识乐观锁版本号字段
xxxxxxxxxx
111package com.atguigu.mybatisplus.entity;
2import com.baomidou.mybatisplus.annotation.Version;
3import lombok.Data;
4
5public class Product {
6private Long id;
7private String name;
8private Integer price;
9
10private Integer version;
11}
在spring核心配置类中的MybatisPlusInterceptor拦截器中配置
在之前的分页插件之后继续配置
xxxxxxxxxx
101
2public MybatisPlusInterceptor mybatisPlusInterceptor(){
3MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
4//添加分页插件
5interceptor.addInnerInterceptor(new
6PaginationInnerInterceptor(DbType.MYSQL));
7//添加乐观锁插件
8interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
9return interceptor;
10}
因为乐观锁版本号改变而导致的操作失败如何解决?
xxxxxxxxxx
131//假设版本号改变操作失败,返回result为0
2int 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}
需要在配置文件中设置扫描通用枚举包
需要传入通用枚举类型送在包
xxxxxxxxxx
11mybatis-plus.type-enums-package=com.os467.enums
设置性别枚举类型
@EnumValue注解 将此注解标注的枚举类型属性值存入数据库中
221package com.os467.enums;
2
3import com.baomidou.mybatisplus.annotation.EnumValue;
4import lombok.Getter;
5
6
7public 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
测试
91
2public 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}