前言:
这一篇的标题是Mybatis-plus笔记,不会再去分析Mybatis-plus中的每一个方法,就单纯的从下面四个方面介绍一下
1:介绍一下概念(依赖,基本的使用)
2:Mybatis-plus的条件构造器
3:Mybatis-plus的扩展功能:分页插件,乐观锁,悲观锁,禁止全局删除或更新
4:Mybatis-plus - x插件
Mybatis-plus介绍:
Mybatis-plus的官网:
简介 | MyBatis-Plus (baomidou.com)
MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
从我自己的角度来说,Mybatsi-plus简化了开发,我在写苍穹外卖的时候,用的只是Mybatis,每次写crud都需要自己去写sql,还需要在.xml文件中利用标签之类,其实已经蛮方便的了,不过Mybatis-plus更快。
依赖:
<!-- mybatis-plus启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
Mybatis-plus的条件构造器:
MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。Wrapper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句,从而提高开发效率并减少 SQL 注入的风险。
简单看一下这个继承关系,
最常用的就是最底层的实现类:QueryWrapper和LambdaQueryWrapper
先贴一个官网的条件构造器:条件构造器 | MyBatis-Plus (baomidou.com)。
直接上案例:
在查询之前注入一下:
@Autowired
private UserMapper userMapper;
@Resource
private TeamMapper teamMapper;
@Resource
private UserService userService;
1://查询用户名包含test,创建时间在2024-06-07和2024-07-01之间,并且邮箱不为null的用户信息
@Test
public void test1() throws ParseException {
//查询用户名包含test,创建时间在2024-06-07和2024-07-01之间,并且邮箱不为null的用户信息
// 定义日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 转换字符串为 Date
Date startDate = sdf.parse("2024-06-07");
Date endDate = sdf.parse("2024-07-01");
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("username","test");
queryWrapper.between("createTime",startDate,endDate);
queryWrapper.isNotNull("email");
final List<User> userList = userMapper.selectList(queryWrapper);
for (User user : userList) {
System.out.println(user.getUserAccount());
}
}
2://按createTime降序查询用户,如果年龄相同则按id升序排列
@Test
public void test2() throws ParseException {
//按createTime降序查询用户,如果年龄相同则按id升序排列
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("createTime");
queryWrapper.orderByAsc("id");
final List<User> userList = userMapper.selectList(queryWrapper);
for (User user : userList) {
System.out.println(user.getUserAccount());
}
3://删除avatarUrl为空的用户
@Test
public void test3() throws ParseException {
//删除avatarUrl为空的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("avatarUrl");
final List<User> userList = userMapper.selectList(queryWrapper);
final int i = userMapper.deleteBatchIds(userList);
System.out.println(i);
}
4:将createTime小于2024-07-01并且用户名中包含有test或邮箱为null的用户信息修改
@Test
public void test4() throws ParseException {
//将createTime小于2024-07-01并且用户名中包含有test或邮箱为null的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// 定义日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 转换字符串为 Date
Date endDate = sdf.parse("2024-07-01");
queryWrapper.lt("createTime",endDate)
.like("username","test")
.or()
.isNull("email");
final List<User> userList = userMapper.selectList(queryWrapper);
for (User user : userList) {
System.out.println(user.getId());
}
}
5:查询用户信息的username和userAccount字段
@Test
public void test5() throws ParseException {
//查询用户信息的username和userAccount字段
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("username","userAccount");
final List<User> userList = userMapper.selectList(queryWrapper);
for (User user : userList) {
System.out.println(user.getUsername());
}
}
这里有个点:就是你查询了这两个字段,出来的结果其它的字段都为null
6://将createTime小于2024-07-01并且用户名中包含有test或邮箱为null的用户信息修改
@Test
public void test6() throws ParseException {
//将createTime小于2024-07-01并且用户名中包含有test或邮箱为null的用户信息修改
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// 定义日期格式
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 转换字符串为 Date
Date endDate = sdf.parse("2024-07-01");
updateWrapper.lt("createTime",endDate);
updateWrapper.like("username","test").or().isNull("email");
final User newuser = new User();
newuser.setEmail("123456@qq.com");
final int update = userMapper.update(newuser, updateWrapper);
System.out.println(update);
//updateWrapper和queryWrapper查询条件的封装规则是一样的,
//只不过,update是修改,你需要指定当你查出符合你要求的数据需要修改成什么,封装一个实体即可
}
7://用户名中包含"test",按照创建时间升序排序,输出10条数据
@Test
public void test7() throws ParseException {
//用户名中包含"test",按照创建时间升序排序,输出10条数据
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(User::getUsername,"test")
.orderByDesc(User::getCreateTime)
.last("limit 10");
final List<User> userList = userMapper.selectList(lambdaQueryWrapper);
for (User user : userList) {
System.out.println(user.getId());
}
}
这里之后就用到了Lambda表达式的QueryWrapper
用这个Lambda表达式的好处就是可以直接映射数据库的字段:
我们之前用QueryWrapper,比如用eq拼接,第一个参数需要输入字符串,并且得是字段名,这就有可能输错,
如果用了这个Lambda表达式的QueryWrapper就可以通过反射来获取字段的get方法。
8://输入一段字符串,这段字符串同时在name和desc中出现
@Test
public void test8() throws ParseException {
//输入一段字符串,这段字符串同时在name和desc中出现
String searchText = "测试";
LambdaQueryWrapper<Team> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(Team::getName,searchText).or().like(Team::getDescription,searchText);
final List<Team> teamList = teamMapper.selectList(lambdaQueryWrapper);
for (Team team : teamList) {
System.out.println(team.getId());
}
}
9:/* userAccount 为ljh126,并且username包含字段test userAccoutn 为ljh127 */
这个案例是我照这个官网的一个案例来模仿的:
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.and(i -> i.and(j -> j.eq(User::getName, "李白").eq(User::getStatus, "alive"))
.and(j -> j.eq(User::getName, "杜甫").eq(User::getStatus, "alive")));
官网是这样写的
SELECT * FROM user WHERE ((name = '李白' AND status = 'alive') AND (name = '杜甫' AND status = 'alive'))
我们一看到这个sql也能知道这个查询的条件
@Test
public void test9() throws ParseException {
/*
userAccount 为ljh126,并且username包含字段test
userAccoutn 为ljh127
*/
String searchText = "test";
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.and(qw->qw.or(i->i.eq(User::getUserAccount,"ljh126").like(User::getUsername,searchText))
.or(i->i.eq(User::getUserAccount,"ljh127")));
final List<User> teamList = userMapper.selectList(lambdaQueryWrapper);
for (User user : teamList) {
System.out.println(user.getId());
}
}
Mybatis-plus的扩展功能:
一:分页查询:
分页查询的实现说起来很简单,就是一个后置的拦截器:
1:需要来一个Mybatis的配置拦截器:
@Configuration
@MapperScan("com.usercenter.usercenterproject.mapper")
public class MybatisPlusConfig {
/**
* 新增分页拦截器,并设置数据库类型为mysql
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));;
return interceptor;
}
}
2:配置Page类调用.page方法:
首先我们知道分页查询需要两个参数:当前是第几页,每页多大
一般我们进行分页查询的时候,都是接受前端发送来的request,比如UserQueryRequest,我们可以让这个UserQueryRequest去继承PageRequest,这样的好处就是,如果以后还有其它模块或者业务需要分页,我们也直接继承就行。
/**
* 分页请求体
*/
@Data
public class PageRequest {
/*
页面大小
*/
int pageSize = 10;
/*
当前是第几页
*/
int pageNum = 1;
}
package com.usercenter.usercenterproject.Pojo.Request;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class UserQueryRequest extends PageRequest implements Serializable {
/**
*
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户昵称
*/
private String username;
/**
* 账号
*/
private String userAccount;
/**
* 性别
*/
private Integer gender;
/**
* 电话
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 用户状态 0-正常
*/
private Integer userStatus;
/**
* 0 : 普通用户 1:管理员
*/
private Integer role;
/**
* 星球编号
*/
private String planetCode;
/**
* 标签 json 列表
*/
private String tags;
/**
* 个人简介
*/
private String profile;
}
@GetMapping("/list/page")
public BaseResponse<Page<User>> listpageUser(UserQueryRequest userQueryRequest,HttpServletRequest httpServletRequest){
if(userQueryRequest==null){
throw new BusinessException(ErrorCode.PARAMS_ERROR,"查询信息为空");
}
userQueryRequest.setPageNum(1);
userQueryRequest.setPageSize(5);
Page<User> page = new Page<>(userQueryRequest.getPageNum(),userQueryRequest.getPageSize());
User user = new User();
BeanUtils.copyProperties(userQueryRequest,user);
QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
Page<User> userPagelist = userService.page(page, queryWrapper);
return ResultUtils.success(userPagelist);
}
整体的代码逻辑:
从请求参数中取出页号和页面大小,接着new一个Page对象,再使用Service的.page方法调用接受返回参数即可。
二:Mybatis-plus乐观锁:
有乐观锁,相反也有悲观锁
悲观锁我们都知道就是Java中的一个关键字:就相当于java中synchronized
悲观锁就是:一个数据库只有一个入口,也只有一把锁,每个线程进去抢锁,抢到锁的进去,并且把入口锁住,等这个线程完事了,再把锁释放,其它线程再进行抢锁
这就是很好懂的逻辑
下面的这个乐观锁的逻辑:就是乐观的认为并发冲突的概率较低,因此不需要提前加锁
什么意思呢,就是这个数据库呢没有入口,very open,每个进程都能进来,那问题就来了,那每个人都能进来,怎么保证数据的一致性呢?
可以用一个版本号进行记录
比如这个数据一开始版本都是1,表示没有被数据更改过。
然后有两个线程同时进来了,有一个线程修改了这个数据之前对照了一个这个version,发现没问题没人改过,接着修改了数据,并且把这个version字段+1.,
这个时候另一个线程也修改了数据,修改之后,唉,发现这时候的版本号不一样了,那里面执行回退操作,回退回没修改之前。
用这种方式来保证数据的一致性。
具体步骤:
1:添加拦截器:
@Configuration
@MapperScan("com.usercenter.usercenterproject.mapper")
public class MybatisPlusConfig {
/**
* 新增分页拦截器,并设置数据库类型为mysql
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
2:在实体类的字段上面加上@Version
我们上面分析过了这个流程,我们知道,如果你想保证数据的一致,需要在数据库表的字段上添加一个字段version
这样Mybatis-plus就会自动帮你管理这个版本了。
测试:
//演示乐观锁生效场景
@Test
public void test10(){
//步骤1: 先查询,在更新 获取version数据
//同时查询两条,但是version唯一,最后更新的失败
User user = userMapper.selectById(4);
User user1 = userMapper.selectById(4);
user.setUserAccount("ljh111");
user1.setUserAccount("ljh333");
userMapper.updateById(user);
//乐观锁生效,失败!
userMapper.updateById(user1);
}
执行这个方法,我们就会发现数据库一条数据的userAccount字段只有一条被修改了。
三:禁止全局删除和修改
这个也很好理解,就是全局修改和删除是一个很危险的操作,导致数据库的丢失巴拉巴拉的
Mybatis-plus的逻辑删除也是为了保证数据的安全,一般不会轻易删除数据。
要想实现禁止全局修改和删除的功能也很简单也是一个拦截器:
@Configuration
@MapperScan("com.usercenter.usercenterproject.mapper")
public class MybatisPlusConfig {
/**
* 新增分页拦截器,并设置数据库类型为mysql
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
测试:
@Test
public void testQuick11(){
User user = new User();
user.setUsername("test");
user.setEmail("xxx@mail.com");
//Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
//全局更新,报错
userService.saveOrUpdate(user,null);
}
Mybatis-plus - x插件:
Mybatis-X-Generator插件能快速帮我们生成实体类,Mapper及其注解,Service。
现在idea的插件上商店里面下载插件
然后点击这个,一般我是生成generator的一个文件夹,生成之后修改一下类名就行。
我记得我在一篇文章里面写道过这个操作,这里就不写了。