一.快速入门
(一)简介
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window) 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
(二)快速入门
1.准备数据库脚本
2.准备boot工程
(1)创建空项目
(2)创建maven模块
3.导入依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.0.5</version></parent><groupId>com.yan</groupId><artifactId>mybatis-plus-base-quick-01</artifactId><version>1.0-SNAPSHOT</version><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- 测试环境 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version></dependency><!-- 数据库相关配置启动器 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><!-- druid启动器的依赖 --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.18</version></dependency><!-- 驱动类--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version></dependency></dependencies><!-- SpringBoot应用打包插件--><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
4.配置文件和启动类
完善连接池配置:
通过源码分析,druid-spring-boot-3-starter目前最新版本是1.2.18,虽然适配了SpringBoot3,但缺少自动装配的配置文件,需要手动在resources目录下创建META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,文件内容如下!
com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure
连接池配置
resources/application.yml
# 连接池配置
spring:datasource:type: com.alibaba.druid.pool.DruidDataSourcedruid:url: jdbc:mysql:///day01username: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
启动类
@MapperScan("com.xx.mapper")
@SpringBootApplication
public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class,args);}}
5.编写实体类和Mapper接口
@Data
public class User {private Long id;private String name;private Integer age;private String email;
}
继承mybatis-plus提供的基础Mapper接口,自带crud方法!
public interface UserMapper extends BaseMapper<User> {//定义方法}
6.编写测试类
@SpringBootTest
public class SpringBootMybatisPlusTest {@Autowiredprivate UserMapper userMapper;@Testpublic void test() {List<User> list = userMapper.selectList(null);System.out.println("users:" + list);}}
二.核心功能
(一)基于Mapper接口的crud
通用 CRUD 封装BaseMapper接口,Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器! 内部包含常见的单表操作!
Insert方法
// 插入一条记录
// T 就是要插入的实体对象
// 默认主键生成策略为雪花算法(后面讲解)
int insert(T entity);
Delete方法
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);// 根据 ID 删除
int deleteById(Serializable id);// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
Update方法
// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);// 根据 ID 修改 主键属性必须值
int updateById(@Param(Constants.ENTITY) T entity);
实体类属性值为Null则不会修改,所以,实体类的属性必须是包装类型,如果不是,例如int类型,会赋初值为0,会将数据库的年龄修改为0
whereWrapper为null,表示全部修改
Select方法
// 根据 ID 查询
T selectById(Serializable id);// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
(二)自定义和多表映射
mybatis-plus: # mybatis-plus的配置# 默认位置 private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"}; mapper-locations: classpath:/mapper/*.xml
public interface UserMapper extends BaseMapper<User> {//正常自定义方法!//可以使用注解@Select或者mapper.xml实现List<User> queryAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace = 接口的全限定符 -->
<mapper namespace="com.xxx.mapper.UserMapper"><select id="queryAll" resultType="user" >select * from user</select>
</mapper>
(三)基于Service接口CRUD
1.接口继承IServce接口
public interface UserService extends IService<User> {
}
2.类继承ServiceImpl实现类
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{}
3.测试
@SpringBootTest
public class SpringBootMybatisPlusTest {@Autowiredprivate UserService userService;@Testpublic void test_save() {ArrayList<User> lists = new ArrayList<>();User user = new User(null, "张三三", 32, "22445432@qq.com");User user2 = new User(null, "李四四", 32, "3423423@qq.com");lists.add(user);lists.add(user2);userService.saveBatch(lists);}@Testpublic void test_saveOrUpdate() {User user = new User(null, "王五", 23, "234234@qq.com");//id有值则为修改,否则为更新boolean b = userService.saveOrUpdate(user);}@Testpublic void test_getOrList() {User byId = userService.getById(1);//返回单个对象List<User> list = userService.list(null);//查询全部}
}
(四)分页查询实现
1.导入分页插件
@SpringBootApplication
@MapperScan("com.yan.mapper")
public class Main {public static void main(String[] args) {SpringApplication.run(Main.class, args);}@Beanpublic MybatisPlusInterceptor plusInterceptor() {//mybaits-plus的插件集合[加入到这个集合中即可,分页插件.....]MybatisPlusInterceptor mybatisPlusInterceptor= new MybatisPlusInterceptor();//分页插件mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());return mybatisPlusInterceptor;}
}
2.非自定义的mapper方法使用分页
@SpringBootTest
public class SpringBootMybatisPlusTest {@Autowiredprivate UserMapper userMapper;@Testpublic void testPage(){Page<User> page = new Page<>(1,3);userMapper.selectPage(page,null);//结果 page最后也会被封装结果long current = page.getCurrent();long size = page.getSize();List<User> records = page.getRecords();long total = page.getTotal();}}
3.自定义的mapper方法使用分页
(1)定义接口
传入参数携带Ipage接口
返回结果为IPage
public interface UserMapper extends BaseMapper<User> {//定义一个根据年龄参数查询,并且分页的方法IPage<User> queryByAge(IPage<User> page,@Param("age") Integer age);
}
(2)接口实现
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yan.mapper.UserMapper">
<select id="queryByAge" resultType="com.yan.pojo.User">select *from userwhere age > #{age}
</select></mapper>
(3)测试
@Testpublic void testPage2(){Page<User> page = new Page<>(1,3);IPage<User> userIPage = userMapper.queryByAge(page, 4);}
(五)条件构造器的使用
1.基于QueryWrapper组装条件
(1)案例
查询用户名包含a,年龄在20--30之间,邮箱不为Null的,按年龄降序查询用户,如果年龄相同则按id升序排列
@Testpublic void test_01() {//查询用户名包含a,年龄在20--30之间,邮箱不为Null的,按年龄降序查询用户,如果年龄相同则按id升序排列QueryWrapper<User> queryWrapper= new QueryWrapper<>();queryWrapper.like("name", "a").between("age", 20, 30).isNotNull("email").orderByDesc("age").orderByAsc("id");userMapper.selectList(queryWrapper);}
删除email为空的用户
@Testpublic void test2() {//删除邮箱为空的QueryWrapper<User> queryWrapper= new QueryWrapper<>();queryWrapper.isNull("email");int delete = userMapper.delete(queryWrapper);}
将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
@Testpublic void test3() {QueryWrapper<User> queryWrapper= new QueryWrapper<>();queryWrapper.gt("age", 20).like("name", "a").or().isNull("email");User user = new User(null, "test", 88, "fdfsdf");userMapper.update(user, queryWrapper);}
(2)组装条件
(3)指定列映射
要给User对象设置无参构造器!
@Testpublic void test4() {QueryWrapper<User> queryWrapper= new QueryWrapper<>();queryWrapper.gt("id", 1).select("name", "age");List<User> users = userMapper.selectList(queryWrapper);System.out.println(users);}
(4)condition判断组织条件
public Children eq(boolean condition, R column, Object val) {return this.addCondition(condition, column, SqlKeyword.EQ, val); }
@Testpublic void test6() {//前端传入两个参数//name不为空,作为条件查询//age>18 作为条件,查询=String name = "";Integer age = 19;QueryWrapper<User> objectQueryWrapper = new QueryWrapper<>();objectQueryWrapper.eq(StringUtils.isNotBlank(name), "name",name).eq(age != null && age > 18, "age", age);userMapper.selectList(objectQueryWrapper);}
2.UpdateWrapper组装条件
使用updateWrapper可以随意设置列的值!!
使用queryWrapper + 实体类形式可以实现修改,但是无法将列值修改为null值!
将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
@SpringBootTest
public class MybatisPlusUpdateWrapper {@Autowiredprivate UserMapper userMapper;@Testpublic void test1() {UpdateWrapper<User> objectUpdateWrapper = new UpdateWrapper<>();objectUpdateWrapper.gt("age", 20).like("name", "a").or().isNotNull("email").set("email", null).set("age", 90);userMapper.update(null, objectUpdateWrapper);}
}
3.LambdaQueryWrapper组装条件
Java 8 支持以下几种方法引用的形式:
1.静态方法引用: 引用静态方法,语法为 `类名::静态方法名`。
2.实例方法引用: 引用实例方法,语法为 `实例对象::实例方法名`。
3.对象方法引用:引用特定对象的实例方法,语法为 `类名::实例方法名`。
4.构造函数引用: 引用构造函数,语法为 `类名::new`。
相比于 QueryWrapper,LambdaQueryWrapper 使用了实体类的属性引用(例如User::getName、User::getAge),而不是字符串来表示字段名,这提高了代码的可读性和可维护性
@SpringBootTest
public class LambdaQueryWrapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void test1() {//将年龄在20到100,并且用户名中包含a或者邮箱不为Null 的用户信息修改LambdaQueryWrapper<User> objectUpdateWrapper = new LambdaQueryWrapper<>();objectUpdateWrapper.between(User::getAge, 20,100).like(User::getName, "a").or().isNotNull(User::getEmail);User user = new User(null, "test1", 98, "eefe");userMapper.update(user, objectUpdateWrapper);}
}
4.LambdaUpdateWrapper
@SpringBootTest
public class LambdaQueryWrapperTest {@Autowiredprivate UserMapper userMapper;@Testpublic void test1() {//将年龄在20到100,并且用户名中包含a或者邮箱不为Null 的用户信息修改LambdaUpdateWrapper<User> objectUpdateWrapper = new LambdaUpdateWrapper<>();objectUpdateWrapper.between(User::getAge, 20,100).like(User::getName, "a").or().isNotNull(User::getEmail).set(User::getEmail, null).set(User::getAge, 91);userMapper.update(null, objectUpdateWrapper);}
}
5.核心注解使用
(1)@TableName注解
- 描述:表名注解,标识实体类对应的表
- 使用位置:实体类
特殊情况:如果表名和实体类名相同(忽略大小写)可以省略该注解!
其他解决方案:全局设置前缀
mybatis-plus: # mybatis-plus的配置global-config:db-config:table-prefix: sys_ # 表名前缀字符串
(2)@TableId 注解
- 描述:主键注解
- 使用位置:实体类主键字段
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 主键字段名 |
type | Enum | 否 | IdType.NONE | 指定主键类型 |
IdType属性可选值:
值 | 描述 |
---|---|
AUTO | 数据库 ID 自增 (mysql配置主键自增长) |
ASSIGN_ID(默认) | 分配 ID(主键类型为 Number(Long )或 String)(since 3.3.0),使用接口IdentifierGenerator 的方法nextId (默认实现类为DefaultIdentifierGenerator 雪花算法) |
@TableName("sys_user")
public class User {@TableId(value="主键列名",type=主键策略)private Long id;private String name;private Integer age;private String email;
}
mybatis-plus:configuration:# 配置MyBatis日志log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:# 配置MyBatis-Plus操作表的默认前缀table-prefix: t_# 配置MyBatis-Plus的主键策略id-type: auto
在以下场景下,添加`@TableId`注解是必要的:
1. 实体类的字段与数据库表的主键字段不同名:如果实体类中的字段与数据库表的主键字段不一致,需要使用`@TableId`注解来指定实体类中表示主键的字段。
2. 主键生成策略不是默认策略:如果需要使用除了默认主键生成策略以外的策略,也需要添加`@TableId`注解,并通过`value`属性指定生成策略。
雪花算法(Snowflake Algorithm)是一种用于生成唯一ID的算法。它由Twitter公司提出,用于解决分布式系统中生成全局唯一ID的需求。
在传统的自增ID生成方式中,使用单点数据库生成ID会成为系统的瓶颈,而雪花算法通过在分布式系统中生成唯一ID,避免了单点故障和性能瓶颈的问题。
(3)@TableField
描述:字段注解(非主键)
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value | String | 否 | "" | 数据库字段名 |
exist | boolean | 否 | true | 是否为数据库表字段 |
MyBatis-Plus会自动开启驼峰命名风格映射!!!
@TableName("sys_user")
public class User {@TableIdprivate Long id;@TableField("nickname")private String name;private Integer age;private String email;
}
三.高级拓展
(一)逻辑删除字段
1.概念
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
逻辑删除以后,没有真正的删除语句,删除改为修改语句!
2.实现
(1)数据库添加逻辑删除字段
数据库和实体类添加逻辑删除字段
ALTER TABLE USER ADD deleted INT DEFAULT 0 ; # int 类型 1 逻辑删除 0 未逻辑删除
(2)实体类添加逻辑删除属性
@Data
public class User {// @TableIdprivate Integer id;private String name;private Integer age;private String email;@TableLogic//逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 1 private Integer deleted;
}
(3) 指定逻辑删除字段和属性值
a.单一指定
@Data
public class User {// @TableIdprivate Integer id;private String name;private Integer age;private String email;@TableLogic//逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 1 private Integer deleted;
}
b.全局指定
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)
(4)测试
@Testpublic void test_02() {userMapper.deleteById(1);}
(二)乐观锁实现
1.思路
乐观锁:
乐观锁的基本思想是,认为并发冲突的概率较低,因此不需要提前加锁,而是在数据更新阶段进行冲突检测和处理。乐观锁的核心思想是"先修改,后校验"。在乐观锁的应用中,线程在读取共享资源时不会加锁,而是记录特定的版本信息。当线程准备更新资源时,会先检查该资源的版本信息是否与之前读取的版本信息一致,如果一致则执行更新操作,否则说明有其他线程修改了该资源,需要进行相应的冲突处理。乐观锁通过避免加锁操作,提高了系统的并发性能和吞吐量,但是在并发冲突较为频繁的情况下,乐观锁会导致较多的冲突处理和重试操作。
悲观锁:
悲观锁的基本思想是,在整个数据访问过程中,将共享资源锁定,以确保其他线程或进程不能同时访问和修改该资源。悲观锁的核心思想是"先保护,再修改"。在悲观锁的应用中,线程在访问共享资源之前会获取到锁,并在整个操作过程中保持锁的状态,阻塞其他线程的访问。只有当前线程完成操作后,才会释放锁,让其他线程继续操作资源。这种锁机制可以确保资源独占性和数据的一致性,但是在高并发环境下,悲观锁的效率相对较低。
2.解决方案
乐观锁
a.版本号/时间戳:为数据添加一个版本号或时间戳字段,每次更新数据时,比较当前版本号或时间戳与期望值是否一致,若一致则更新成功,否则表示数据已被修改,需要进行冲突处理。
b.CAS(Compare-and-Swap):使用原子操作比较当前值与旧值是否一致,若一致则进行更新操作,否则重新尝试。
c.无锁数据结构:采用无锁数据结构,如无锁队列、无锁哈希表等,通过使用原子操作实现并发安全。
悲观锁
a.锁机制:使用传统的锁机制,如互斥锁(Mutex Lock)或读写锁(Read-Write Lock)来保证对共享资源的独占访问。
b.数据库锁:在数据库层面使用行级锁或表级锁来控制并发访问。
c.信号量(Semaphore):使用信号量来限制对资源的并发访问。
3.使用mybatis-plus数据实现乐观锁
(1) 添加版本号更新插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;
}
(2)乐观锁字段添加@Version注解
数据库也需要添加version字段
@Version
private Integer version;
ALTER TABLE USER ADD VERSION INT DEFAULT 1 ; # int 类型 乐观锁字段
(3)正常更新使用即可
@Testpublic void testQuick7(){//步骤1: 先查询,在更新 获取version数据//同时查询两条,但是version唯一,最后更新的失败User user = userMapper.selectById(5);User user1 = userMapper.selectById(5);user.setAge(20);user1.setAge(30);userMapper.updateById(user);//乐观锁生效,失败!userMapper.updateById(user1);}
(三)防止全表更新和删除实现
针对 update 和 delete 语句 作用: 阻止恶意的全表更新删除
添加防止全表更新和删除拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());return interceptor;
}
}
当发生全表删除和全表更新时,mybatis会自动阻止