Mybatis-Plus高级用法:最优化持久层开发
文章目录
前言
mybatis的相关功能理解及配置
springboot整合实现mybatis-plus
mybatis-plus使用参考文档:mybatis-plus使用手册
mybatis-plus支持的配置:使用配置 | MyBatis-Plus (baomidou.com)
对于接口方法、分页查询、条件构造器、核心注解、逻辑删除、乐观锁、防全表更新和删除、插件等等需要用到的时候都可以去mybatis-plus参考文档里面查找相关的实现,这里相关的介绍只是其中的一部分!!
一、Mybatis-Plus快速入门
1.1 简介
1、Mybatis-Plus:(简称 MP)是一个 Mybatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
mybatisplus官网:mybatis-plus官网
mybatis官网:MyBatis官网
Mybatis-Plus支持的数据库:
- MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb,informix,TDengine,redshift
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库,优炫数据库
2、Mybatis-Plus特点:
- 无侵入(只做增强不做改变)、损耗小(启动即会自动注入基本 CURD——直接面向对象操作)、支持主键自动生成、支持自定义全局通用操作(支持全局通用方法注入)、
- 自动生成单表 CRUD 操作:内置通用 Mapper(extends BaseMapper< 实体类类型 >)、通用 Service(xxxservice接口extends IService< 实体类类型 >,xxxserviceImpl extends ServiceImpl<对应的mapper接口, 对应实体类类型 >,同时还需要implements对应的service接口),有构造器。
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,支持自定义。
- 支持 Lambda 形式调用:通过 Lambda 表达式,便于编写各类查询条件,避免字段错写。(使用了实体类的属性引用(User: :getName),替代字符串标识字段名,提高可读性、可维护性)
- 内置多种插件:分页插件(支持多种数据库,提供对应的分页查询的方法)、性能分析插件(输出 SQL 语句以及其执行时间)、全局拦截插件(提供全表 delete/update操作智能分析阻断,也可自定义)
mybatis-plus总结:
自动生成单表的CRUD功能、提供丰富的条件拼接方式、全自动ORM类型持久层框架
1.2 Mybtais-Plus使用流程
使用springboot项目工程整合mybatis-plus的步骤和springboot的使用步骤一样,需要注意的地方就是:引入 mybatis-plus的starter 工程,并配置 mapper 扫描路径即可
回顾springboot工程项目:
导入依赖(父工程+启动器+依赖)→编写springboot配置文件(替代原来的配置类/配置文件ext)→编写启动引导类→实体类(使用了Lombok 简化代码)→三层架构的实现→启动项目(idea工具实现启动引导类/打包jar并运行)
- 创建数据库
- 导入依赖:继承父项目+注意这里使用的是启动器mybatis-plus-boot-starter+数据库+druid+驱动类(mysql、lombok)、测试环境spring-boot-starter-test,最后添加打包插件spring-boot-maven-plugin
由于要用到druid连接池,springboot3中缺少自动装配,因此这里需要完善连接池配置,完善方式见srpingboot3文章中的总结! - 配置文件application.yaml:配置自己的连接池信息
- 编写启动类:添加注解@SpringBootApplication,因为这里结合的是mybatis-plus因此需要再加上注解@MapperScan(“com.atguigu.mapper”)。
- 编写实体类:用lombok简化代码,即为实体类添加注解@Data,自动实现get、set方法
- 编写mapper接口:继承mybatis-plus提供的基础Mapper接口BaseMapper< 实体类类型 >,自带crud方法!这里会自动帮我们生成自带sql语句mapperxml文件
- 添加测试类:自动装配mapper接口,实现相关数据库操作
@SpringBootTest //springboot下测试环境注解,这样就无需创建ioc容器了,自动帮我们实现了!
public class SampleTest {
@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);
}
}
二、Mybatis-Plus核心功能### 2.1 基于Mapper/Service接口的CRUD(理解接口方法)
1、这里探究xxmapper接口extends的BaseMapper、xxservice接口extends的IService、xxServiceImpl extends的serviceImpl具体有哪些方法,以便使用。
BaseMapper定义的全部方法手册:BaseMapper手册
IService定义的全部方法手册:IService手册
BaseMapper接口 | IService 接口 / ServiceImpl | |
---|---|---|
说明 | 通用 CRUD 封装BaseMapper接口。 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器! | 通用 Service CRUD 封装IService 接口,进一步封装 CRUD。 对比Mapper接口CRUD区别:添加了批量方法、service层的方法自动添加事务 |
使用方式 | public interface UserMapper extends BaseMapper { } | public interface UserService extends IService { } @Service public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService{ } |
CRUD区别 | insert、delete、update、select | save/saveOrUpdate、remove、update、get/list/count |
问1:为什么xxxservice接口需要extends IService,在xxxserviceImpl中需要extends ServiceImpl?
答:因为IService中只实现了一半的默认方法,如果xxxserviceImpl没有extends ServiceImpl那么就需要自定义实现了另一半方法,因为ServiceImpl实现了另一半方法
2、两种接口的CRUD方法
BaseMapper接口 | IService 接口 / ServiceImpl | |
---|---|---|
插入 | insert插入:插入一条记录,雪花算法默认生成主键 | save保存:插入一条记录,策略插入、批量插入、插入或修改 |
删除 | delete删除:根据(一个/多个)ID值实现(批量)删除、根据 columnMap 条件、根据 entity 条件删除记录 | remove移除:根据(一个/多个)ID值实现(批量)删除、根据 columnMap 条件、根据queryWrapper 设置的条件删除记录 |
修改 | update更新/修改:据 ID 修改(主键属性必须有值)、根据 whereWrapper 条件,更新记录 | update更新/修改:据一个(多个) ID 值实现(批量)修改、根据 whereWrapper 条件,更新记录、根据 UpdateWrapper 条件,更新记录 需要设置sqlset |
查询 | select查询:根据(一个/多个)id值实现(批量)查询、根据columnMap条件、根据entity条件查询一条/全部记录、根据wrapper条件查询全部记录或只返回第一个字段的值 多了一个分页查询:根据 entity / Wrapper条件分页查询 | get/list查询:根据(一个/多个)id值实现(批量)查询、根据columnMap条件、根据 Wrapper,查询一条/全部记录,或者是列表、查询所有 |
查询记录数 | select查询:查询总记录数 | 查询:查询总记录数、根据wrapper条件查询总记录数 |
一些总结:插入、删除、更新/修改的方法返回值类型都是int
使用示例:首先在对应使用的地方注入xxmapper接口
//一般mapper接口是在service层注入,比如是针对实体类User的,说明涉及到的T是泛型,只实体类对象。
@Autowired
private UserMapper userMapper;
// insert, 形参T: 插入
User user = new User();
//......设置相关的插入值逻辑
int row = userMapper.insert(user);
insert插入:插入一条记录,雪花算法默认生成主键
save保存:插入一条记录,策略插入、批量插入、插入或修改
// T 就是要插入的实体对象
int insert(T entity);
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
delete删除:根据(一个/多个)ID值实现(批量)删除、根据 columnMap 条件、根据 entity 条件删除记录
remove移除:根据(一个/多个)ID值实现(批量)删除、根据 columnMap 条件、根据queryWrapper 设置的条件删除记录
// 根据 ID 删除
int deleteById(Serializable id);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 根据 queryWrapper 设置的条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
update更新/修改:据 ID 修改(主键属性必须有值)、根据 whereWrapper 条件,更新记录
update更新/修改:据一个(多个) ID 值实现(批量)修改、根据 whereWrapper 条件,更新记录、根据 UpdateWrapper 条件,更新记录 需要设置sqlset
// 根据 ID 修改 主键属性必须值
int updateById(@Param(Constants.ENTITY) T entity);
// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity,
@Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
select查询:根据(一个/多个)id值实现(批量)查询、根据columnMap条件、根据entity条件查询一条/全部记录、根据wrapper条件查询全部记录或只返回第一个字段的值、多了一个分页查询即根据 entity / Wrapper条件分页查询
get/list查询:根据(一个/多个)id值实现(批量)查询、根据columnMap条件、根据 Wrapper,查询一条/全部记录,或者是列表、查询所有
// 根据 ID 查询
T selectById(Serializable id);
// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 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);
// 根据 ID 查询
T getById(Serializable id);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询列表
List<T> list(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询所有
List<T> list();
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询全部记录
List<Object> listObjs();
select查询:查询总记录数
count查询:查询总记录数、根据wrapper条件查询总记录数
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
3、方法参数类型说明
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 |
Wrapper< T > | wrapper/updateWrapper/queryWrapper | 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)——理解为动态sql |
Collection< ? extends Serializable > | idList | 主键 ID 列表(不能为 null 以及 empty) |
Serializable | id | 主键 ID |
Map< String, Object > | columnMap | 表字段 map 对象 |
IPage< T > | page | 分页查询条件(可以为 RowBounds.DEFAULT) |
4、自定义映射
- springboot的配置文件application.yaml中修改mybatis-plus的默认mapperxml位置
- 在mapper接口(仍然可以extend BeaseMapper< 实体类类型 >)自定义接口方法,可以为其添加注解@Select或者进入下一步
- 基于mapper.xml实现sql语句:和原来使用mybatis方式一样
mybatis-plus的默认mapperxml位置
mybatis-plus: # mybatis-plus的配置
# 默认位置 private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"};
mapper-locations: classpath:/mapper/*.xml
自定义mapper方法
public interface UserMapper extends BaseMapper<User> {
//正常自定义方法!
//可以使用注解@Select或者mapper.xml实现
List<User> queryAll();
}
基于mapper.xml实现
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace = 接口的全限定符 -->
<mapper namespace="com.atguigu.mapper.UserMapper">
<select id="queryAll" resultType="user" >
select * from user
</select>
</mapper>
2.2 分页查询实现
mybatis提供插件pagehelper:配置文件中添加插件,使用即可
mapper< 实体类类型 >提供了分页查询的方法,只需要导入分页插件(springboot项目的启动类中),然后调用分页查询方法即可!分页查询方法也可以自定义。
1、使用mapper< 实体类类型 >提供的分页查询的方法
- 导入分页插件拦截器
- 使用分页查询
提供的分页方法:
// 根据 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);
拦截器:导入mybatis-plus提供的分页插件:在springboot项目的启动类中添加。
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
使用分页查询
@Test
public void testPageQuery(){
//设置分页参数
Page<User> page = new Page<>(1, 5);
userMapper.selectPage(page, null);
//获取分页数据
List<User> list = page.getRecords();
list.forEach(System.out::println);
System.out.println("records = " + list);
long current = page.getCurrent();
System.out.println("当前页:"+current);
System.out.println("每页显示的条数:"+page.getSize());
long total = page.getTotal();
System.out.println("总记录数:"+total);
long pages = page.getPages();
System.out.println("总页数:"+pages);
System.out.println("是否有上一页:"+page.hasPrevious());
System.out.println("是否有下一页:"+page.hasNext());
}
2、自定义的mapper方法使用分页:
- 导入依赖:同上
- mapper接口中自定义分页查询接口方法:要求传入参数携带Ipage接口、返回结果为IPage
- 接口实现
- 测试调用:类似上
自定义mapper#method使用分页
IPage<User> selectPageVo(IPage<?> page, Integer id);
// or (class MyPage extends Ipage<UserVo>{ private Integer state; })
MyPage selectPageVo(MyPage page);
// or
List<UserVo> selectPageVo(IPage<UserVo> page, Integer state);
<!-- 实现selectPageVo接口方法 -->
<select id="selectPageVo" resultType="xxx.xxx.xxx.User">
SELECT * FROM user WHERE id > #{id}
</select>
@Test
public void testQuick(){
IPage page = new Page(1,2);
userMapper.selectPageVo(page,2);
//page调用各种方法逻辑...
}
2.3 条件构造器使用(构造好作为mapper方法参数使用)
对于动态sql语句,mybatis-plus提供的实现,在mybatis中提供的sql标签用于实现。
使用说明:使用wrapper组装好条件作为参数放到userMapper继承的接口Mapper中的某个方法中调用即可!
1、条件构造器作用
MyBatis-Plus的条件构造器,提供了许多方法来支持各种条件操作符,并且可以通过链式调用来组合多个条件。
2、条件构造器实现原理及方式
使用方式及效果
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "John"); // 添加等于条件
queryWrapper.ne("age", 30); // 添加不等于条件
queryWrapper.like("email", "@gmail.com"); // 添加模糊匹配条件
等同于:
delete from user where name = "John" and age != 30
and email like "%@gmail.com%"
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);//组装好的条件作为参数放入
Wrapper : 条件构造抽象类,最顶端父类
QueryWrapper查询/删除/修改条件封装
UpdateWrapper : 修改条件封装,存在的意义可以随意设置列的值
AbstractLambdaWrapper : 使用Lambda 语法
LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
其中LambdaQueryWrapper、LambdaUpdateWrapper分别和QueryWrapper、UpdateWrapper 用法一致,只是做了一些增强!使用了实体类的属性引用(User: :getName),替代字符串标识字段名,提高可读性、可维护性
2.3.1 QueryWrapper / UpdateWrapper 组装条件
说明: QueryWrapper查询/删除/修改条件封装。UpdateWrapper只有一个修改,存在的意义是可以随意设置列的值!
组装查询/排序条件
- selectList(QueryWrapper< T >)
组装删除条件
- delete(QueryWrapper< T >)
修改:注意使用queryWrapper + 实体类形式可以实现修改,但是无法将列值修改为null值!但是updateWrapper可以随意设置列的值。
- update(T , QueryWrapper< T >)
指定列映射
- selectMaps(QueryWrapper< T >)
condition判断组织条件
总结:
- 条件默认是and。需要or的时候就.or()继续拼接
- 每个条件拼接方法都condition参数,这是一个比较运算,为true追加当前条件!比如eq(condition,列名,值)
使用说明:组装查询、排序、删除条件、(修改)and和or关键字使用【UpdateWrapper只有这个】、指定列映射、condition判断条件
@Autowired
private UseMapper userMapper;
@Test //组装查询条件
public void test01(){
//查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (username LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username", "a")
.between("age", 20, 30)
.isNotNull("email");
//组装好调用方法
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
@Test //组装排序条件
public void test02(){
//按年龄降序查询用户,如果年龄相同则按id升序排列
//SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper
.orderByDesc("age")
.orderByAsc("id");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
@Test //组装删除条件
public void test03(){
//删除email为空的用户
//DELETE FROM t_user WHERE (email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
//条件构造器也可以构建删除语句的条件
int result = userMapper.delete(queryWrapper);
System.out.println("受影响的行数:" + result);
}
@Test //and和or关键字使用(修改)
public void test04() {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//将年龄大于20并且用户名中包含有a或邮箱为null的用户信息修改
//UPDATE t_user SET age=?, email=? WHERE username LIKE ? AND age > ? OR email IS NULL)
queryWrapper
.like("username", "a")
.gt("age", 20)
.or()
.isNull("email");
User user = new User();
user.setAge(18);
user.setEmail("user@atguigu.com");
int result = userMapper.update(user, queryWrapper);
System.out.println("受影响的行数:" + result);
}
/*
*只有这一个是UpdateWrapper的修改条件组装
**/
@Test
public void testQuick2(){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
//将id = 3 的email设置为null, age = 18
updateWrapper.eq("id",3)
.set("email",null) // set 指定列和结果
.set("age",18);
//如果使用updateWrapper 实体对象写null即可!
int result = userMapper.update(null, updateWrapper);
System.out.println("result = " + result);
}
@Test //指定列映射
public void test05() {
//查询用户信息的username和age字段
//SELECT username,age FROM t_user
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("username", "age");
//selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值为null
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
@Test //condition判断条件
public void testQuick3(){
String name = "root";
int age = 18;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//判断条件拼接
//当name不为null拼接等于, age > 1 拼接等于判断
//方案1: 手动判断
if (!StringUtils.isEmpty(name)){
queryWrapper.eq("name",name);
}
if (age > 1){
queryWrapper.eq("age",age);
}
//方案2: 拼接condition判断
//每个条件拼接方法都condition参数,这是一个比较运算,为true追加当前条件!
//eq(condition,列名,值)
queryWrapper.eq(!StringUtils.isEmpty(name),"name",name)
.eq(age>1,"age",age);
}
2.3.2 基于 LambdaQueryWrapper / LambdaUpdateWrapper 组装条件
1、说明LambdaQueryWrapper、LambdaUpdateWrapper分别和QueryWrapper、UpdateWrapper 用法一致,只是做了一些增强!使用了实体类的属性引用(User: :getName),替代字符串标识字段名,提高可读性、可维护性
2、方法引用包括:静态、实例、对象、构造函数方法引用,语法:xxx : : 相关的方法名
3、与QueryWrapper及UpdateWrapper对比
queryWrapper.eq(!StringUtils.isEmpty(name),"name",name).eq(age>1,"age",age);
//调用实体类中的get方法
lambdaQueryWrapper.eq(!StringUtils.isEmpty(name), User::getName,name);
//将id = 3 的email设置为null, age = 18
updateWrapper.eq("id",3).set("email",null).set("age",18);
//使用lambdaUpdateWrapper
LambdaUpdateWrapper<User> updateWrapper1 = new LambdaUpdateWrapper<>();
updateWrapper1.eq(User::getId,3).set(User::getEmail,null).set(User::getAge,18);
2.4 核心注解使用(表名 != 实体类类名)
涉及到的注解有@TableName、@TableId、@TableFiled。
1、表名 != 实体类类名,主键列名 != 属性名。
当设置xxxMapper extends BaseMapper< User >后,此接口对应的方法会自动触发 user表的crud,但是当表名和实体类类名不一致时(如名 t_user → 实体类 User,忽略大小写),或者主键的列名和属性名不一致时,数据库的信息和实体类就不能实现映射了该怎么解决?
原因:因为默认情况下, 根据指定的<实体类>的名称对应数据库表名,属性名对应数据库的列名!
解决办法1:逐个设置:使用mybatis-plus提供的注解,对于表名以及字段名自定义映射关系;对于主键策略也可以在注解属性中设置
-
注解@TableName:注解表名,用于标识实体类对应的表。位置实体类
-
注解@TableId:注解主键,用于标记实体类主键字段以及指定主键生成策略。如@TableId(value=“主键字段名”,type=主键类型/生成策略),其中String value默认为"",Enum type默认[IdType](mybatis-plus/mybatis-plus-annotation/src/main/java/com/baomidou/mybatisplus/annotation/IdType.java at 3.0 · baomidou/mybatis-plus · GitHub).NONE,其中的可选值如下
-
@TableField:注解字段(非主键),如@TableId(value=“数据库字段名”, exist=“是否为数据库表字段”),其中String value默认为"",boolean exist默认为true。MyBatis-Plus会自动开启驼峰命名风格映射!
IdType值 描述 说明 AUTO 数据库 ID 自增 mysql需要配置主键自增长:对于表主键指定类型为数字,并且设置为auto_increment ASSIGN_ID(默认) 主键类型为 Number(Long )或 String,接值用Long
mybatis-plus3.3.0以后默认使用雪花算法分配 ID
接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法)雪花算法:随机生成一个long类型不重复的数字。用于解决分布式系统中生成全局唯一ID的需求。(逐渐类型为Long或String)
UUID:随机生成一个不重复的字符串
解决办法2:全局设置:对于表名可以全局设置前缀,可以参照mybatis-plus的基本配置使用配置 | MyBatis-Plus (baomidou.com);对于主键策略可以全局
全局设置前缀:在application.yaml配置文件中设置mybatis-plus
mybatis-plus: # mybatis-plus的配置
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
2、雪花算法是什么?
- 定义:雪花算法(Snowflake Algorithm)可以在分布式系统中生成全局唯一的ID,并保证ID的生成顺序性。广泛应用于分布式系统中,如微服务架构、分布式数据库、分布式锁等场景,以满足全局唯一标识的需求。
在传统的自增ID生成方式中,使用单点数据库生成ID会成为系统的瓶颈,而雪花算法通过在分布式系统中生成唯一ID,避免了单点故障和性能瓶颈的问题。 - 组成部分:生成的ID是一个64位的整数
- 时间戳:41位,精确到毫秒级,可以使用69年。
- 节点ID:10位,用于标识分布式系统中的不同节点。
- 序列号:12位,表示在同一毫秒内生成的不同ID的序号。
- 工作方式:
- 当前时间戳从某一固定的起始时间开始计算,可以用于计算ID的时间部分。
- 节点ID是分布式系统中每个节点的唯一标识,可以通过配置或自动分配的方式获得。
- 序列号用于记录在同一毫秒内生成的不同ID的序号,从0开始自增,最多支持4096个ID生成。
- 注意点:
- 雪花算法依赖于系统的时钟,需要确保系统时钟的准确性和单调性,否则可能会导致生成的ID不唯一或不符合预期的顺序。
- 雪花算法生成的数字,需要使用Long 或者 String类型主键!!
三、Mybatis-Plus高级扩展
3.1 逻辑删除实现
说明:数据库表添加逻辑删除字段deleted→设置一个逻辑删除字段→指定注解和配置,正常编写sql语句,删除、查询时无需考虑deleted字段,mybatis-plus会自动执行添加的
1、逻辑删除定义:指通过更改记录的状态或添加标记字段来模拟删除操作,从而保留了删除前的数据,便于后续的数据分析和恢复。
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段(如deleted)的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录(但是在mybatis-plus中通过sql语句查询查询的只是逻辑为删除状态的数据)
2、逻辑删除实现流程
- 在数据库表中添加逻辑删除字段:布尔、整数、枚举类型
- 在实体类中添加逻辑删除属性:逻辑未删除值默认0、逻辑已删除值默认1
- 指定逻辑删除字段和属性值:让mybatis-plus知道
- 单一指定:实体类中删除属性添加注解@TableLogic
- 全局指定:实体类中还需要相关删除属性,在application.yaml配置文件中配置mybatis-plus的相关属性
数据库表中添加逻辑删除字段
ALTER TABLE USER ADD deleted INT DEFAULT 0 ; # int 类型 1 逻辑删除 0 未逻辑删除
实体类添加逻辑删除属性——单一指定
@TableLogic
//逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 1
private Integer deleted;
实体类添加逻辑删除属性——全局指定
mybatis-plus:
global-config:
db-config:
# 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-field: deleted
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
3、逻辑删除实例
逻辑删除以后,没有真正的删除语句,删除改为修改语句!
当查询时,查询到的是逻辑未删除内容,mybatis-plus执行,自动添加删除字段值=逻辑未删除值
逻辑删除代码&实际效果,查询测试
@Test
public void testQuick5(){
//逻辑删除
userMapper.deleteById(5);
}
/**
* 在mybatis-plus中,真正运行的sql
*/
/**
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@5871a482] will not be managed by Spring
==> Preparing: UPDATE user SET deleted=1 WHERE id=? AND deleted=0
==> Parameters: 5(Integer)
<== Updates: 1
*/
//查询测试
@Test
public void testQuick6(){
//正常查询.默认查询非逻辑删除数据
userMapper.selectList(null);
}
//实际执行效果
//SELECT id,name,age,email,deleted FROM user WHERE deleted=0
3.2 乐观锁实现
1、乐观锁悲观锁定义及实现方案和技术
- 悲观锁:有锁,先保护在修改。具体技术:锁机制、数据库锁、信号量
- 乐观锁:没有锁,先修改后校验。具体技术:版本号/时间戳、CAS、无锁数据结构
乐观锁和悲观锁是在并发编程中用于处理并发访问和资源竞争的两种不同的锁机制,是两种解决并发数据问题的思路,不是具体技术。
2、版本号乐观锁技术的实现流程:正常使用,配置好以后,乐观锁的执行交给mybatis-plus去做
配置:添加版本号更新插件拦截器(启动类中加拦截器)→数据库表添加字段版本号字段version,实体类中添加属性version→为其添加注解@Version
使用资源流程:查询获取记录(获取当前version)→准备更新→检查版本号→一致执行更新,如果不一致说明就是失效数据,更新失败。注意:不是所有的方法都支持,仅支持两个updateById(id) 与 update(entity, wrapper)
- 取出记录时,获取当前 version
- 更新时,检查获取版本号是不是数据库当前最新版本号
- 如果是[证明没有人修改数据], 执行更新, set 数据更新 , version = version+ 1
- 如果 version 不对[证明有人已经修改了],我们现在的其他记录就是失效数据!就更新失败
添加版本号更新插件:启动类中添加拦截器,用于实现版本号更新插件。mybatis-plus会在更新的时候,每次自动对比版本号字段和曾倩版本号字段!
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
数据库添加version字段
ALTER TABLE USER ADD VERSION INT DEFAULT 1 ; # int 类型 乐观锁字段
实体类何种添加属性version:支持的数据类型int,Integer,long,Long,Date,Timestamp,LocalDateTime
支持的方法:支持 updateById(id) 与 update(entity, wrapper) 方法
@Version
private Integer version;
使用示例:乐观锁生效场景!导入插件→添加属性,注意属性的数据类型以及只支持两种方法
//演示乐观锁生效场景
@Test
public 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);
}
3.3 防止全表更新和删除实现
针对 update 和 delete 语句 作用: 阻止恶意的全表更新删除
- 添加防止全表更新和删除拦截器
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
- 测试全部更新或者删除
@Test
public void testQuick8(){
User user = new User();
user.setName("custom_name");
user.setEmail("xxx@mail.com");
//Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
//全局更新,报错
userService.saveOrUpdate(user,null);
}
四、Mybatis-Plus代码生成器(MybatisX插件)
4.1 Mybatis插件逆向工程
安装MybatisX插件→右侧DataBase先连接到数据库→选择对应的数据库→针对数据库中的表右键选择"MybatisX-Generator"
作用:可以快速生成实体类(注意deleted逻辑删除、version乐观锁配置没有弄好)、mapper接口、mapperxml、service、及其对应的继承都生成好了。
4.2 MybatisX快速代码生成
使用mybatisX插件,自动生成sql语句实现
MybatisX快速开发插件 | MyBatis-Plus (baomidou.com)
在mapper接口下选择相关的MybatisX插件的方法,会自动帮我们在mapperxml中自动生成相关的sql语句
五、总结
核心注解
注解名 | 用途 |
---|---|
@TableName | 表名 |
@TableId、 | 主键字段 |
@TableFiled | 非主键字段 |
@TableLogic | 逻辑删除中标注删除属性 |
@Version | 用于实现乐观锁,标注实体类中version属性,乐观锁版本号 |
插件使用:在启动类中导入相关插件,有些可能需要添加相关注解→正常使用即可!
插件名 | 用法 |
---|---|
分页插件 | 在启动类中导入分页插件→正常使用 |
版本号更新插件 | 在启动类中导入该插件→数据库中添加version字段及实体类中添加version属性→加上注解@Version→正常使用,mybatis-plus会自动执行 |
防止全表更新和删除插件 | 添加防止全表更新和删除拦截器 |
对于分页、版本号更新等等都需要进行一个拦截,因此只需要在springboot项目的启动类中配置相关插件拦截器(拦截器中导入相关的插件),再进行配置,即可实现相关的功能!