Mybatis-plus笔记

前言:

这一篇的标题是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的一个文件夹,生成之后修改一下类名就行。

我记得我在一篇文章里面写道过这个操作,这里就不写了。

相关推荐
静止了所有花开44 分钟前
SpringMVC学习笔记(二)
笔记·学习
码上一元2 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
计算机-秋大田2 小时前
基于微信小程序的养老院管理系统的设计与实现,LW+源码+讲解
java·spring boot·微信小程序·小程序·vue
魔道不误砍柴功4 小时前
简单叙述 Spring Boot 启动过程
java·数据库·spring boot
失落的香蕉4 小时前
C语言串讲-2之指针和结构体
java·c语言·开发语言
枫叶_v4 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端
wclass-zhengge4 小时前
SpringCloud篇(配置中心 - Nacos)
java·spring·spring cloud
路在脚下@4 小时前
Springboot 的Servlet Web 应用、响应式 Web 应用(Reactive)以及非 Web 应用(None)的特点和适用场景
java·spring boot·servlet
黑马师兄4 小时前
SpringBoot
java·spring
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习