前言:
这一篇的标题是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)。
直接上案例:
在查询之前注入一下:
@Autowiredprivate UserMapper userMapper;@Resourceprivate TeamMapper teamMapper;@Resourceprivate UserService userService;
1://查询用户名包含test,创建时间在2024-06-07和2024-07-01之间,并且邮箱不为null的用户信息
@Testpublic void test1() throws ParseException {//查询用户名包含test,创建时间在2024-06-07和2024-07-01之间,并且邮箱不为null的用户信息// 定义日期格式SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");// 转换字符串为 DateDate 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升序排列
@Testpublic 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为空的用户
@Testpublic 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的用户信息修改
@Testpublic void test4() throws ParseException {//将createTime小于2024-07-01并且用户名中包含有test或邮箱为null的用户信息修改QueryWrapper<User> queryWrapper = new QueryWrapper<>();// 定义日期格式SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");// 转换字符串为 DateDate 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字段
@Testpublic 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的用户信息修改
@Testpublic void test6() throws ParseException {//将createTime小于2024-07-01并且用户名中包含有test或邮箱为null的用户信息修改UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();// 定义日期格式SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");// 转换字符串为 DateDate 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条数据
@Testpublic 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中出现
@Testpublic 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也能知道这个查询的条件
@Testpublic void test9() throws ParseException {/*userAccount 为ljh126,并且username包含字段testuserAccoutn 为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*/@Beanpublic 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*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());return interceptor;}}
2:在实体类的字段上面加上@Version
我们上面分析过了这个流程,我们知道,如果你想保证数据的一致,需要在数据库表的字段上添加一个字段version
这样Mybatis-plus就会自动帮你管理这个版本了。
测试:
//演示乐观锁生效场景@Testpublic 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*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());return interceptor;}}
测试:
@Testpublic 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的一个文件夹,生成之后修改一下类名就行。
我记得我在一篇文章里面写道过这个操作,这里就不写了。