06-SpringBoot (六) --- 整合mybatis-plus 及其相关用法

image-20230402144835563

有兄弟可能就会问了,前文刚整理了一个Mybatis,现在又出来个Mybatis-plus是什么鬼啊,害,难言之隐,我很烦,当我们整合完mybatis的时候,去做SoringBoot项目你会发现,几乎没人用mybatis!大家都是用的mybatis-plus!那能怎么办吧,继续学呗,咱搞计算机的,不就是不停的学嘛,那接下来我们看看什么是mybatis-plus吧。(悄悄咪咪,我昨天在知乎上面看到了一篇文章,问题是mybatis是不是凉凉了,回答竟然全都是凉凉心痛啊,明白!经典白学呗)

声明

文章结合了官方文档,以及部分实例思路采用了尚硅谷教学视频:Mybatis-plus

什么是Mybatis-plus

MyBatis-Plus (简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

以上是摘自Mybatis-plus官网的一段话,我们直接挑我们关注的重点,它的作用是增强,如何增强?大家看过上文,都应该有一个感触mapper.xml文件还是太难写了!那么,mybatis-plus它来了!,他能帮我们轻松解决这些问题~

Mybatis-plus特性:

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

引入依赖

<!--   mybatis-plus 依赖     -->
<dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.1</version>
</dependency>

配置application.yaml

application.yaml不用变化,这里我贴一下上文的配置

spring:
  # 数据库链接配置
  datasource:
    url: jdbc:mysql://localhost:3306/数据库名称?useUnicode=true&characterEncoding=UTF8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: 用户名
    password: 数据库密码
    # 链接池
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 链接池初始化大小
      initial-size: 5
      # 最大活跃数
      max-active: 20
      # 最小空闲数
      min-idle: 5
      # 最大等待时间
      max-wait: 60000

# mybatis配置
mybatis:
  check-config-location: true
  #  mybatis框架配置文件,对mybatis的生命周期起作用
  # config-location: "classpath:mybatis-config.xml"
  #  mapper映射文件路径
  mapper-locations: "classpath:/mappers/*.xml"
  #  指定Java对象别名所在的包为所有entity
  type-aliases-package: "com.pan.entity.*"

实例测试

创建实体类Student

@Data
public class Student {

    private Long id;	// 注意我们这里的id类型为Long,因为mybatis-plus的id生成是依据雪花算法,id值较长。

    private String name;

    private Integer age;

    private String hobby;

}

创建数据库相应表

image-20230402155434911

相应的创建数据表的时候id的类型要选择bigint

在Mapper包下面添加StudentMapper接口

@Repository
public interface StudentMapper extends BaseMapper<Student> {
}

细心的同学可能已经注意到在这个接口里面,我们继承了一个BaseMapper<Student>BaseMapper是什么?点进去

image-20230402161625138

可以看到,BaseMapper里面有很多方法,插入、根据id删除、根据一个map条件删除…

所以BaseMapper其默认提供了一系列的增删改查的基础方法。这样我们就不用手敲mapper.xml啦~

在测试类下面测试

@SpringBootTest
class SpringBoot01ApplicationTests {

    @Autowired
    private StudentMapper studentMapper;


    @Test
    public void testSelectList(){
        //selectList()根据MP内置的条件构造器查询一个list集合,null表示没有条件,即查询所有
        studentMapper.selectList(null).forEach(System.out::println);
    }

}

**注意注意!**在此之前你在启动类上面加了@MapperScan包扫描了嘛,是加在启动类上面!

@SpringBootApplication
@MapperScan("com.pan.mapper")
public class SpringBoot01Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringBoot01Application.class, args);
    }

}

启动测试,输出结果

输出结果:
Student(id=1, name=tom, age=18, hobby=篮球)
Student(id=2, name=jack, age=20, hobby=足球)
Student(id=3, name=lucy, age=17, hobby=跳舞)
相应的sql语句:
SELECT id,name,age,hobby FROM student 

要是想在控制台看到sql语句需要在application.yaml里面配置:

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

基础CRUD

上文有提到,BaseMapper里面包含了基本的CRUD操作,我们来简单的测试几个:

插入

@Test
public void testInsert(){
    // 记得在实体类上加 @AllArgsConstructor 注解才有全参构造器
    Student stu = new Student(null,"jan",15,"钢琴");

    int insert = studentMapper.insert(stu);
    System.out.println("执行行数:"+insert);
    System.out.println("获取该id:"+stu.getId());
}
执行结果:
执行行数:1
获取该id:1642527932894511105
执行的sql语句:INSERT INTO student ( id, name, age, hobby ) VALUES ( ?, ?, ?, ? ) 

删除

  1. 删除有多种,根据id删除

    @Test
    public void testDeleteById(){
        //通过id删除用户信息
        //DELETE FROM student WHERE id=?
        int result = studentMapper.deleteById(1642527932894511105L);	// 注意长id后面加了个L
        System.out.println("执行行数:"+result);
    }
    
  2. 根据id批量删除

    @Test
    public void testDeleteBatchIds(){
        //通过多个id批量删除
        //DELETE FROM student WHERE id IN ( ? , ? , ? )
        List<Long> idList = Arrays.asList(1L, 2L, 3L);
        int result = studentMapper.deleteBatchIds(idList);
        System.out.println("执行行数:"+result);
    }
    
  3. 根据map条件删除

    @Test
    public void testDeleteByMap(){
        //根据map集合中所设置的条件删除记录
        //DELETE FROM student WHERE name = ? AND age = ?
        Map<String, Object> map = new HashMap<>();
        map.put("age", 20);
        map.put("name", "jack");
        int result = studentMapper.deleteByMap(map);
        System.out.println("执行行数:"+result);
    }
    

修改

@Test
public void testUpdateById(){
    Student student = new Student(4L, "ben", 22, null);
    //UPDATE student SET name=?, age=? WHERE id=?
    int result = studentMapper.updateById(student);
    System.out.println("受影响行数:"+result);
}

查询

  1. 查询也有多个,全查询

    @Test
    public void testSelectList(){
        //查询所有用户信息
        //SELECT id,name,age,hobby FROM student
        List<User> list = studentMapper.selectList(null);
        list.forEach(System.out::println);
    }
    
  2. 根据id查询

    @Test
    public void testSelectById(){
        //根据id查询用户信息
        //SELECT id,name,age,hobby FROM student WHERE id=?
        Student stu = studentMapper.selectById(4L);
        System.out.println(stu);
    }
    
  3. 根据多个id查询

    @Test
    public void testSelectBatchIds(){
        //根据多个id查询多个用户信息
        //SELECT id,name,age,hobby FROM student WHERE id IN ( ? , ? )
        List<Long> idList = Arrays.asList(1L, 2L);
        List<User> list = userMapper.selectBatchIds(idList);
        list.forEach(System.out::println);
    }
    
    
  4. 根据map条件查询

    @Test
    public void testSelectByMap(){
        //通过map条件查询用户信息
        //SELECT id,name,age,hobby FROM student WHERE name = ? AND age = ?
        Map<String, Object> map = new HashMap<>();
        map.put("age", 15);
        map.put("name", "jan");
        List<User> list = userMapper.selectByMap(map);
        list.forEach(System.out::println);
    }
    

BaseMapper里面也还有不少其他方法,这里贴出完整的BaseMapper源码,下载源码就有中文注释噢~

package com.baomidou.mybatisplus.core.mapper;
public interface BaseMapper<T> extends Mapper<T> {
    
    /**
    * 插入一条记录
    * @param entity 实体对象
    */
    int insert(T entity);
    
    /**
    * 根据 ID 删除
    * @param id 主键ID
    */
    int deleteById(Serializable id);
    
    /**
    * 根据实体(ID)删除
    * @param entity 实体对象
    * @since 3.4.4
    */
    int deleteById(T entity);
    
    /**
    * 根据 columnMap 条件,删除记录
    * @param columnMap 表字段 map 对象
    */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
    
    /**
    * 根据 entity 条件,删除记录
    * @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where
    语句)
    */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    /**
    * 删除(根据ID 批量删除)
    * @param idList 主键ID列表(不能为 null 以及 empty)
    */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends
    Serializable> idList);
    
    /**
    * 根据 ID 修改
    * @param entity 实体对象
    */
    int updateById(@Param(Constants.ENTITY) T entity);
    
    /**
    * 根据 whereEntity 条件,更新记录
    * @param entity 实体对象 (set 条件值,可以为 null)
    * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成
    where 语句)
    */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER)
    Wrapper<T> updateWrapper);
    
    /**
    * 根据 ID 查询
    * @param id 主键ID
    */
    T selectById(Serializable id);
    
    /**
    * 查询(根据ID 批量查询)
    * @param idList 主键ID列表(不能为 null 以及 empty)
    */
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends
    Serializable> idList);
    /**
    * 查询(根据 columnMap 条件)
    * @param columnMap 表字段 map 对象
    */
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object>
    columnMap);
    
    /**
    * 根据 entity 条件,查询一条记录
    * <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常
    </p>
    * @param queryWrapper 实体对象封装操作类(可以为 null)
    */
    default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
    	List<T> ts = this.selectList(queryWrapper);
    	if (CollectionUtils.isNotEmpty(ts)) {
    		if (ts.size() != 1) {
    			throw ExceptionUtils.mpe("One record is expected, but the query result is multiple 								records");
    		}
    		return ts.get(0);
    	}
    	return null;
    }
    
    /**
    * 根据 Wrapper 条件,查询总记录数
    * @param queryWrapper 实体对象封装操作类(可以为 null)
    */
    Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    /**
    * 根据 entity 条件,查询全部记录
    * @param queryWrapper 实体对象封装操作类(可以为 null)
    */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    /**
    * 根据 Wrapper 条件,查询全部记录
    * @param queryWrapper 实体对象封装操作类(可以为 null)
    */
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T>
    queryWrapper);
    
    /**
    * 根据 Wrapper 条件,查询全部记录
    * <p>注意: 只返回第一个字段的值</p>
    * @param queryWrapper 实体对象封装操作类(可以为 null)
    */
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    /**
    * 根据 entity 条件,查询全部记录(并翻页)
    * @param page 分页查询条件(可以为 RowBounds.DEFAULT)
    * @param queryWrapper 实体对象封装操作类(可以为 null)
    */
    <P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
    
    /**
    * 根据 Wrapper 条件,查询全部记录(并翻页)
    * @param page 分页查询条件
    * @param queryWrapper 实体对象封装操作类
    */
    <P extends IPage<Map<String, Object>>> P selectMapsPage(P page,@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

Service层

仅仅凭借BaseMapper的基础CRUD并不够实现我们项目的复杂逻辑,所以在mybatis-plus中还有一个接口IService以及它的实现类ServiceImpl,里面的方法很多,同学们可以点进去自己阅读观看

创建service包,并在下面建立接口StudentService:

public interface StudentService extends IService<Student> {
    //  StudentService继承IService模板提供的基础功能
}

service包下面建立包serviceImpl存放其接口实现类StudentServiceImpl:

@Service
public class StudentServiceImpl extends ServiceImpl<StudentMapper, Student> implements StudentService {
    // ServiceImpl实现了IService,提供了IService中基础功能的实现
	// 若ServiceImpl无法满足业务需求,则可以使用自定的StudentService定义方法,并在实现类中实现
    
}

测试

  1. 查询记录总数

    @Test
    public void testGetCount(){
        //  SELECT COUNT( * ) FROM student 
        long count = studentService.count();
        System.out.println("总记录数:"+count);
        // 总记录数:4
    }
    
  2. 批量插入

    @Test
    public void testSaveBatch(){
        // SQL长度有限制,海量数据插入单条SQL无法实行,
        // 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
        ArrayList<Student> stus = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            Student stu = new Student();
            stu.setName("abc" + i);
            stu.setAge(15 + i);
            stus.add(stu);
        }
        //SQL:INSERT INTO student ( name, age ) VALUES ( ?, ? )
        studentService.saveBatch(stus);
    }
    

    查询以下现有数据:

    Student(id=1, name=tom, age=18, hobby=篮球)
    Student(id=2, name=jack, age=20, hobby=足球)
    Student(id=3, name=lucy, age=17, hobby=跳舞)
    Student(id=1642527932894511105, name=jan, age=15, hobby=钢琴)
    Student(id=1642539138866819074, name=abc0, age=15, hobby=null)
    Student(id=1642539138908762113, name=abc1, age=16, hobby=null)
    Student(id=1642539138908762114, name=abc2, age=17, hobby=null)
    

常用注解

​ 从刚刚的测试我们会发现,我们只是在application.yaml文件指定了数据库名称,全程没有提及表明,只是在Mapper接口继承BaseMapper的时候设置了泛型Student,而我们的操作表名字也是student。因此,不难想到,MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定,即实体类型决定,且默认操作的表名和实体类型的类名一致

@TableName

那如果实体类型的类名和操作表名不一样怎么办?答案就是我们的注解@TableName

假如我们现在数据库的表名已经改为s_student,我们只需要在实体类上面添加@Table("s_student")

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("s_student")
public class Student {

    private Long id;

    private String name;

    private Integer age;

    private String hobby;

}

全局配置解决

在开发中,实体类所对应的表都有固定的前缀,例如 s_ 此时,可以使用MyBatis-Plus提供的全局配置,为实体类所对应的表名设置默认的前缀,那么就 不需要在每个实体类上通过@TableName标识实体类对应的表

mybatis-plus:
	configuration:
		# 配置MyBatis日志
		log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
	global-config:
		db-config:
		# 配置MyBatis-Plus操作表的默认前缀
			table-prefix: s_

@TableId

​ 在通常情况下,我们的数据表里面主键一般都是id,Mybatis-plus在实现CRUD操作的时候也会默认将id列作为主键列,那么我们想换一个列作为主键怎么做呢?例如将我们的名字作为主键

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    private Long id;

    @TableId("name")		// 注意,这只是一个假设,一般没人会把名字作为主键列
    private String name;

    private Integer age;

    private String hobby;

}

@TableField

​ 当我们的实体类中的属性名和字段名不一致的情况时,我们可以使用此注解

  1. 若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格,此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格,无须担心

  2. 如果不是上种情况,而是名字就是不一样,例如实体类中name,数据表中studentname

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Student {
    
        private Long id;
    
        @TableField("studentname")		// 添加注解实现
        private String name;
    
        private Integer age;
    
        private String hobby;
    
    }
    

@TableLogic

​ 在我们的项目开发中,许多的删除并不是真的删除,只是你前端页面看不到了,如果真的删除了,那万一用户后面要求恢复数据怎么办,所以我们引出了逻辑删除is_delete

在实体类,数据表中分布添加字段is_delete0表示启用,1表示停用

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {

    private Long id;

    private String name;

    private Integer age;

    private String hobby;

    @TableLogic
    private int is_delete;

}

image-20230403101228355

条件构造器 — 重中之重

​ Mybatis-Plus为了让用户自由的构建查询条件, 简单便捷,没有额外的负担 , 能够有效提高开发效率,推出了条件构造器,它主要用于 处理 sql 拼接,排序,实体参数查询等。并且条件构造器支持链式结构,可以一直.

部分基本条件说明:(以下引用自官网)

表达式解释实例
ne不等于 <>ne(“name”, “老王”)—>name <> ‘老王’
gt大于 >gt(“age”, 18)—>age > 18
eq等于 =eq(“name”, “老王”)—>name = ‘老王’
ge大于等于 >=ge(“age”, 18)—>age >= 18
lt小于 <lt(“age”, 18)—>age < 18
le小于等于 <=le(“age”, 18)—>age <= 18
betweenBETWEEN 值1 AND 值2between(“age”, 18, 30)—>age between 18 and 3
notBetweenNOT BETWEEN 值1 AND 值2notBetween(“age”, 18, 30—>age not between 18 and 30
likeLIKE ‘%值%’like(“name”, “王”)—>name like '%王%
notLikeNOT LIKE ‘%值%’notLike(“name”, “王”)—>name not like '%王%
likeLeftLIKE ‘%值’likeLeft(“name”, “王”)—>name like '%王
likeRightLIKE ‘值%’likeRight(“name”, “王”)—>name like ‘王%’
notLikeLeftNOT LIKE ‘%值’notLikeLeft(“name”, “王”)—>name not like '%王
notLikeRightNOT LIKE ‘值%’notLikeRight(“name”, “王”)—>name not like ‘王%’
isNull字段 IS NULLisNull(“name”)—>name is null
isNotNull字段 IS NOT NULLisNotNull(“name”)—>name is not null
in字段 IN (value.get(0), value.get(1), …)in(“age”,{1,2,3})—>age in (1,2,3)
notIn字段 NOT IN (value.get(0), value.get(1), …)notIn(“age”,{1,2,3}) —> age not in (1,2,3)
orderByAsc排序:ORDER BY 字段, … ASC,升序orderByAsc(“id”, “name”) —> order by id ASC,name ASC
orderByDesc排序:ORDER BY 字段, … DESC,降序orderByDesc(“id”, “name”) —> order by id DESC,name DESC
or拼接 OR,主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)eq(“id”,1).or().eq(“name”,“老王”) —> id = 1 or name = ‘老王’
andAND 嵌套and(i -> i.eq(“name”, “李白”).ne(“status”, “活着”)) —> and (name = ‘李白’ and status <> ‘活着’)
apply该方法可用于数据库函数 动态入参的params对应前面applySql内部的{index}部分.这样是不会有sql注入风险的,反之会有!1. apply(“id = 1”)—>id = 1
2. apply(“date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’”)—>date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’“)
3. apply(“date_format(dateColumn,‘%Y-%m-%d’) = {0}”, “2008-08-08”)—>date_format(dateColumn,‘%Y-%m-%d’) = ‘2008-08-08’”)

QueryWrapper

  1. 组装查询条件

    @Test
    public void test01(){
        //查询用户名包含j,年龄在15到25之间,并且爱好不为null的学生信息
        // SELECT id,name,age,hobby,is_delete FROM student WHERE is_delete=0
        // AND (name LIKE ? AND age BETWEEN ? AND ? AND hobby IS NOT NULL)
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("name", "j")
            .between("age", 15, 25)
            .isNotNull("hobby");
        List<Student> list = studentMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }
    
  2. 组装排序条件

    @Test
    public void test02(){
        //按年龄降序查询学生,如果年龄相同则按id升序排列
        // SELECT id,name,age,hobby,is_delete FROM student 
        // WHERE is_delete=0 ORDER BY age DESC,id ASC 
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        queryWrapper
            .orderByDesc("age")
            .orderByAsc("id");
        List<Student> users = studentMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }
    
  3. 组装删除条件

    @Test
    public void test03(){
        //删除hobby为空的学生
        // UPDATE student SET is_delete=1 WHERE is_delete=0 AND (name IS NULL)
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        queryWrapper.isNull("hobby");
        //条件构造器也可以构建删除语句的条件
        int result = studentMapper.delete(queryWrapper);
        System.out.println("受影响的行数:" + result);
    }
    
  4. 条件的优先级 (lambda表达式内的逻辑优先运算)

    @Test
        public void test04() {
            QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
            //将(年龄大于17并且用户名中包含有j)或hobby为null的学生信息修改
            // UPDATE student SET age=?, hobby=? WHERE is_delete=0 
            // AND (name LIKE ? AND age > ? OR hobby IS NULL) 
            queryWrapper
                    .like("name", "j")
                    .gt("age", 17)
                    .or()
                    .isNull("hobby");
            Student stu = new Student();
            stu.setAge(18);
            stu.setHobby("滑雪");
            int result = studentMapper.update(stu, queryWrapper);
            System.out.println("受影响的行数:" + result);
        
    

    lambda表达式内的逻辑优先运算

    @Test
        public void test05() {
            QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
            //将用户名中包含有j并且(年龄大于17或hobby为null)的学生信息修改
            // UPDATE student SET age=?, hobby=? WHERE is_delete=0 
            // AND (name LIKE ? AND (age > ? OR hobby IS NULL))
            //lambda表达式内的逻辑优先运算
            queryWrapper
                    .like("name", "j")
                    .and(i -> i.gt("age", 17).or().isNull("hobby"));
            Student stu = new Student();
            stu.setAge(18);
            stu.setHobby("滑雪");
            int result = studentMapper.update(stu, queryWrapper);
            System.out.println("受影响的行数:" + result);
        }
    
  5. 组装select子句

    @Test
    public void test06() {
        //查询学生信息的name和age字段
        //SELECT name,age FROM student WHERE is_delete=0 
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("name", "age");
        //selectMaps()返回Map集合列表,通常配合select()使用,
        // 避免User对象中没有被查询到的列值为null
        List<Map<String, Object>> maps = studentMapper.selectMaps(queryWrapper);
        maps.forEach(System.out::println);
    }
    
  6. 实现子查询

    @Test
    public void test07() {
        //查询id小于等于3的学生信息
        // SELECT id,name,age,hobby,is_delete FROM student WHERE is_delete=0 
        // AND (id IN (select id from student where id <= 3))
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        queryWrapper.inSql("id", "select id from student where id <= 3");
        List<Student> list = studentMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }
    

UpdateWrapper

@Test
public void test08() {
    //将(年龄大于15或hobby为null)并且学生名中包含有j的学生信息修改
    //组装set子句以及修改条件
    //UPDATE student SET age=?,hobby=? WHERE is_delete=0 
    //AND (name LIKE ? AND (age > ? OR hobby IS NULL)) 
    UpdateWrapper<Student> updateWrapper = new UpdateWrapper<>();
    //lambda表达式内的逻辑优先运算
    updateWrapper
        .set("age", 15)
        .set("hobby", "乒乓球")
        .like("name", "j")
        .and(i -> i.gt("age", 15).or().isNull("hobby"));
    //这里必须要创建User对象,否则无法应用自动填充。如果没有自动填充,可以设置为null
    //Student stu = new Student();
    //stu.setName("mark");
    //int result = userMapper.update(stu, updateWrapper);

    int result = studentMapper.update(null, updateWrapper);
    System.out.println(result);
}

condition

​ 我们的组装条件数据来源于用户输入,是可选的,因此我们在组装这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若没有选择则一定不能组装,以免影响SQL执行的结果

  1. 方法一,普通方法

    @Test
    public void test09() {
        //定义查询条件,有可能为null(用户未输入或未选择)
        String username = null;
        Integer ageBegin = 10;
        Integer ageEnd = 24;
        QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
        // SELECT id,name,age,hobby,is_delete FROM student WHERE
        // is_delete=0 AND (age >= ? AND age <= ?)
        //StringUtils.isNotBlank()判断某字符串是否不为空且长度不为0且不由空白符(whitespace)构成
        if(StringUtils.isNotBlank(username)){
            queryWrapper.like("name","j");
        }
        if(ageBegin != null){
            queryWrapper.ge("age", ageBegin);
        }
        if(ageEnd != null){
            queryWrapper.le("age", ageEnd);
        }
    
        List<Student> users = studentMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }
    
  2. 方法二,使用带condition参数的重载方法构建查 询条件

    @Test
        public void test10() {
            //定义查询条件,有可能为null(用户未输入或未选择)
            String username = null;
            Integer ageBegin = 10;
            Integer ageEnd = 24;
            QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
            // SELECT id,name,age,hobby,is_delete FROM student WHERE 
            // is_delete=0 AND (age >= ? AND age <= ?)
            queryWrapper
                    .like(StringUtils.isNotBlank(username), "username", "a")
                    .ge(ageBegin != null, "age", ageBegin)
                    .le(ageEnd != null, "age", ageEnd);
    
            List<Student> users = studentMapper.selectList(queryWrapper);
            users.forEach(System.out::println);
        }
    

LambdaQueryWrapper

LambdaQueryWrapperQueryWrapper类似,只不过使用的是lambda语法。

@Test
public void test11() {
    //定义查询条件,有可能为null(用户未输入)
    String name = "j";
    Integer ageBegin = 15;
    Integer ageEnd = 24;
    LambdaQueryWrapper<Student> queryWrapper = new LambdaQueryWrapper<>();
    // SELECT id,name,age,hobby,is_delete FROM student WHERE is_delete=0 
    // AND (name LIKE ? AND age >= ? AND age <= ?) 
    //避免使用字符串表示字段,防止运行时错误
    queryWrapper
        .like(StringUtils.isNotBlank(name), Student::getName, name)
        .ge(ageBegin != null, Student::getAge, ageBegin)
        .le(ageEnd != null, Student::getAge, ageEnd);
    List<Student> users = studentMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

LambdaUpdateWrapper

同理

@Test
public void test12() {
    //组装set子句
    LambdaUpdateWrapper<Student> updateWrapper = new LambdaUpdateWrapper<>();
    // UPDATE student SET age=?,hobby=? WHERE is_delete=0 
    // AND (name LIKE ? AND (age < ? OR hobby IS NULL))
    updateWrapper
        .set(Student::getAge, 18)
        .set(Student::getHobby, "芭蕾")
        .like(Student::getName, "j")
        .and(i -> i.lt(Student::getAge, 24).or().isNull(Student::getHobby)); 
    //lambda表达式内的逻辑优先运算
    Student stu = new Student();
    int result = studentMapper.update(stu, updateWrapper);
    System.out.println("受影响的行数:" + result);
}

链式调用 lambda 式

// 区分:
// 链式调用 普通
UpdateChainWrapper<T> update();
// 链式调用 lambda 式。注意:不支持 Kotlin 
LambdaUpdateChainWrapper<T> lambdaUpdate();

// 等价示例:
query().eq("id", value).one();
lambdaQuery().eq(Entity::getId, value).one();

// 等价示例:
update().eq("id", value).remove();
lambdaUpdate().eq(Entity::getId, value).remove();

插件

分页插件

MyBatis Plus自带分页插件,但是需要配置,在config包下配置MybatisPlusConfig

@Configuration
@MapperScan("com.pan.mapper")   //可以将主类中的注解移到此处,主类上的就可以删了
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new
                PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

在测试之前我们加几条数据:

image-20230403165354393

编写测试

@Test
public void testPage(){
    // 设置分页参数
    Page<Student> page = new Page<>(1, 5);
    studentMapper.selectPage(page, null);
    // 获取分页数据
    List<Student> list = page.getRecords();
    list.forEach(System.out::println);
    System.out.println("当前页:"+page.getCurrent());
    System.out.println("每页显示的条数:"+page.getSize());
    System.out.println("总记录数:"+page.getTotal());
    System.out.println("总页数:"+page.getPages());
    System.out.println("是否有上一页:"+page.hasPrevious());
    System.out.println("是否有下一页:"+page.hasNext());
}

输出结果:

Student(id=1, name=tom, age=18, hobby=篮球, is_delete=0)
Student(id=2, name=jack, age=18, hobby=芭蕾, is_delete=0)
Student(id=1642810620078280705, name=abc0, age=15, hobby=游泳0, is_delete=0)
Student(id=1642810620095057921, name=abc1, age=16, hobby=游泳1, is_delete=0)
Student(id=1642810620095057922, name=abc2, age=17, hobby=游泳2, is_delete=0)
当前页:1
每页显示的条数:5
总记录数:9
总页数:2
是否有上一页:false
是否有下一页:true

乐观锁插件

当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁实现方式:

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败

同样,先添加配置

@Configuration
@MapperScan("com.pan.mapper")   //可以将主类中的注解移到此处
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 这是分页插件
        interceptor.addInnerInterceptor(new
                PaginationInnerInterceptor(DbType.MYSQL));
        // 这是乐观锁插件
        interceptor.addInnerInterceptor(new
                OptimisticLockerInnerInterceptor());
        
        return interceptor;
    }
}

在实体类的字段上加上@Version注解,引入尚硅谷实例讲解

故事背景:

一个商品成本80,售价100.老板叫小李将价格增加50,小李暂时未改,一会,老板叫小王降价30,此时小李小王同时改价,小李操作的时候,系统先取出商品价格100元;小王 也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据 库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。

如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证 最终的价格是120元

创建实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {

    private Long id;
    
    private String name;

    private Integer price;

    @Version
    private Integer version;

}

添加mapper

public interface ProductMapper extends BaseMapper<Product> {
    
}

乐观锁实现流程

数据库中添加version字段

取出记录时,获取当前version

SELECT id,name,price,version FROM product WHERE id=1

更新时,version + 1,如果where语句中的version版本不对,则更新失败

UPDATE product SET price=price+50, version=version + 1 WHERE id=1 AND version=1

测试修改冲突

小李查询商品信息: SELECT id,name,price,version FROM t_product WHERE id=?

小王查询商品信息: SELECT id,name,price,version FROM t_product WHERE id=?

小李修改商品价格,自动将version+1 UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=? Parameters: 外星人笔记本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer)

小王修改商品价格,此时version已更新,条件不成立,修改失败

UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=? Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)

最终,小王修改失败,查询价格:150 SELECT id,name,price,version FROM t_product WHERE id=?

相关代码

@Test
public void testConcurrentVersionUpdate() {
    //小李取数据
    Product p1 = productMapper.selectById(1L);
    //小王取数据
    Product p2 = productMapper.selectById(1L);
    //小李修改 + 50
    p1.setPrice(p1.getPrice() + 50);
    int result1 = productMapper.updateById(p1);
    System.out.println("小李修改的结果:" + result1);
    //小王修改 - 30
    p2.setPrice(p2.getPrice() - 30);
    int result2 = productMapper.updateById(p2);
    System.out.println("小王修改的结果:" + result2);

    if(result2 == 0){
        //失败重试,重新获取version并更新
        p2 = productMapper.selectById(1L);
        p2.setPrice(p2.getPrice() - 30);
        result2 = productMapper.updateById(p2);
    }
    System.out.println("小王修改重试的结果:" + result2);

    //老板看价格
    Product p3 = productMapper.selectById(1L);
    System.out.println("老板看价格:" + p3.getPrice());
}

Mybatis-Plus还有许多强大的功能,这里就不赘述了…怎么说能,要想用的熟练,用的巧妙还是得多看看官网,再次奉上官网地址
rsion=? WHERE id=? AND version=? Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer)

最终,小王修改失败,查询价格:150 SELECT id,name,price,version FROM t_product WHERE id=?

相关代码

@Test
public void testConcurrentVersionUpdate() {
    //小李取数据
    Product p1 = productMapper.selectById(1L);
    //小王取数据
    Product p2 = productMapper.selectById(1L);
    //小李修改 + 50
    p1.setPrice(p1.getPrice() + 50);
    int result1 = productMapper.updateById(p1);
    System.out.println("小李修改的结果:" + result1);
    //小王修改 - 30
    p2.setPrice(p2.getPrice() - 30);
    int result2 = productMapper.updateById(p2);
    System.out.println("小王修改的结果:" + result2);

    if(result2 == 0){
        //失败重试,重新获取version并更新
        p2 = productMapper.selectById(1L);
        p2.setPrice(p2.getPrice() - 30);
        result2 = productMapper.updateById(p2);
    }
    System.out.println("小王修改重试的结果:" + result2);

    //老板看价格
    Product p3 = productMapper.selectById(1L);
    System.out.println("老板看价格:" + p3.getPrice());
}

Mybatis-Plus还有许多强大的功能,这里就不赘述了…怎么说能,要想用的熟练,用的巧妙还是得多看看官网,再次奉上官网地址

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值