MyBatis-Plus

1.MyBatis-Plus简介

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

MyBatis-Plus实现数据库CRUD – 努力生活认真工作

2.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 操作智能分析阻断,也可自定义拦截规则,预防误操作

3.MyBatis-Plus的框架结构

4.MyBatis-Plus入门案例

  1. 创建数据库并创建表,向表中插入数据
CREATE TABLE user
(
	id BIGINT(20) NOT NULL COMMENT '主键ID',
	name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
	age INT(11) NULL DEFAULT NULL COMMENT '年龄',
	email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
	PRIMARY KEY (id)
);

INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
  1. 利用初始化向导搭建SpringBoot环境

项目结构

  1. 引入相关依赖
<!--        SpringBoot场景启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
<!--   单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
<!--        MyBatisPlus场景启动器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
<!--        Mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
<!--        Lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
  1. 创建application.yaml配置文件并配置数据源和连接数据库的相关信息
spring:
  datasource:
#    配置SpringBoot默认的数据源
    type: com.zaxxer.hikari.HikariDataSource
#    配置连接数据库的各个信息
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: root
  1. 创建实体类
@Data
@AllArgsConstructor
public class User {
    private Long id;
    private String name;
    private Integer age;
    private String email;
}
  1. 创建mapper接口并继承BaseMapper接口(接口可以继承接口)
public interface UserMapper extends BaseMapper<User> {

}

在BaseMapper类中,已经有定义好的增删改查SQL

  1. 在主程序类中添加MapperScan注解用于批量扫描Mapper

image-20230612091356802

  1. 测试

在测试类中自动注入创建的接口动态生成的代理类,并调用mybatis-plus中提供的方法

image-20230612091408686

查询出user列表


@Autowired(required = false)
private UserMapper userMapper;

@Test
public void findAllUser(){
    List<User> users = userMapper.selectList(null);
    users.forEach(System.out::println);
}

5.添加日志功能

在SpringBoot的配置文件当中添加mybatis-plus的日志功能

选择默认日志功能就行

添加日志功能后会显示sql语句

6.BaseMapper分析

实体类Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能

public interface BaseMapper<T> extends Mapper<T> {

    /* 插入一条记录*/
    int insert(T entity);

    /*** 根据 ID 删除*/
    int deleteById(Serializable id);

    /*** 根据实体(ID)删除*/
    int deleteById(T entity);

    /*** 根据 columnMap 条件,删除记录*/
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /*** 根据 entity 条件,删除记录*/
    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /*** 删除(根据ID或实体 批量删除)*/
    int deleteBatchIds(@Param(Constants.COLL) Collection<?> idList);

    /*** 根据 ID 修改*/
    int updateById(@Param(Constants.ENTITY) T entity);

    /*** 根据 whereEntity 条件,更新记录*/
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /*** 根据 ID 查询*/
    T selectById(Serializable id);

    /*** 查询(根据ID 批量查询)*/
    List<T> selectBatchIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);

    /*** 查询(根据 columnMap 条件*/
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /*** 根据 entity 条件,查询一条记录*/
    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 条件,判断是否存在记录*/
    default boolean exists(Wrapper<T> queryWrapper) {
        Long count = this.selectCount(queryWrapper);
        return null != count && count > 0;
    }

    /*** 根据 Wrapper 条件,查询总记录数*/
    Long selectCount(@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 条件,查询全部记录(并翻页*/
    <P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /*** 根据 Wrapper 条件,查询全部记录(并翻页)*/
    <P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

7.实现最基本的CRUD功能

7.1添加功能

    @Test
    public void addUser(){
//        创建user对象
        User user = new User(null,"班尼特",18,"2533694604@qq.com");
        int i = userMapper.insert(user);
        System.out.println(i);
    }

日志打印出sql语句

image-20230612091546682

7.2删除功能

根据id删除

@Test
public void deleteUser(){
    int i = userMapper.deleteById(6);
    System.out.println(i);
}

日志打印出sql语句

image-20230612091615695

根据Map集合中所设置的条件进行删除

@Test
public void deleteUser2(){
    Map<String,Object> map = new HashMap();
    map.put("name","钟离");
    map.put("age",24);
    int i = userMapper.deleteByMap(map);
    System.out.println(i);
}

image-20230612091633062

根据List集合中的id实现批量删除

@Test
@Transactional
public void deleteUser3(){
    List<Long> list = Arrays.asList(1l,2l,3l);
    int i = userMapper.deleteBatchIds(list);
    System.out.println(i);
}

image-20230612091652304

7.3修改功能

@Test
@Transactional
public void updateUser(){
    User user = new User(5L,"钟离",22,"2533694604@qq.com");
    int i = userMapper.updateById(user);
    System.out.println(i);
}

image-20230612091713970

7.4查询功能

@Test
public void selectUser(){
    User user = userMapper.selectById(1L);
    System.out.println(user);
}

image-20230612091726837

其他查询功能和其他功能类似

8.自定义功能

有些时候BaseMapper接口提供的CRUD方法并不能满足我们的需求,那这样的情况下就需要我们自定义功能

  1. 首先创建mapper映射文件夹,并且在映射文件夹里创建接口的映射文件

  1. 在接口中添加方法,并且在mapper映射文件中添加相应的映射
public interface UserMapper extends BaseMapper<User> {
//    根据id查询用户信息为map集合
    public Map<String,Object> selectMapById(Long id);
}
<mapper namespace="cn.edu.pdsu.mybatisplus.mapper.UserMapper">
<!--public Map<String,Object> selectMapById();-->
    <select id="selectMapById" resultType="java.util.Map">
        select id,name,age,email from user where id = #{id}
    </select>
</mapper>

9.通用的Service CRUD接口

9.1对IService的概述

说明:

通用 Service CRUD 封装IService接口,进一步封装 CRUD,采用 get 查询单行,remove删除,list 查询集合,page分页。前缀命名方式区分 Mapper层避免混淆,泛型T为任意实体对象。

IService:MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑 详情查看源码IService和ServiceImpl

Service层接口可以继承IService接口,IService是顶级接口。里面同样有很对多方法

userSerivce继承了IService,userServiceImpl实现了userService,由于IService接口中含有很多方法,userServiceImpl就要实现IService中的全部方法吗?

UserService接口继承IService接口(添加实体类泛型)

public interface UserService extends IService<User> {
}

UserServiceImpl实现类实现UserService接口,并继承IService的实现类ServiceImpl(添加实体类泛型)

image-20230612091847318

答案是继承顶级接口IService的实现类ServiceImpl

ServiceImpl类中需要定义泛型<Mapper接口,JavaBean>

image-20230612091857783

9.2 查询表中的记录数

创建测试类,自动化注入UserService,并测试表中的记录数

使用聚合函数sum求和

public void testSum() {
    QueryWrapper queryWrapper = new QueryWrapper<>();
    queryWrapper.select("IFNULL(sum(score),0) as totalScore")
            .eq("playlist_id", 1);
    Map<String,Object> map = ranksService.getMap(queryWrapper);
    System.out.println(map.get("totalScore"));
}
SELECT IFNULL(sum(score),0) as totalScore 
FROM ranks WHERE (playlist_id = ?)

9.3 批量添加

在BaseMapper中没有批量添加的功能

而在IService中含有批量添加的功能,泛型为List集合

测试

@Test
@Transactional
public void insertBatch(){
    List list = new ArrayList<User>();
    for (int i = 0; i < 10; i++) {
        User user = new User();
        user.setName("宵宫"+i);
        user.setAge(20+i);
        list.add(user);
    }
    boolean b = userService.saveBatch(list);
    System.out.println(b);
}

批量插入成功

image-20230612092019894

10.mybatis-plus常用注解

10.1@TableName

作用:@TableName注解主要是实现实体类型和数据库中的表实现映射。

首先提出一个问题,MyBatisPlus是如何根据JavaBean来对应数据库中的表名呢,看看Mapper接口

image-20230612092054003

数据库中的表名

image-20230612092100671

可以看出BaseMapper类定义的泛型的表名一致,MyBatisPlus就是这样对应数据库中的表名的

但当JavaBean与数据库表名不一致时

image-20230612092113728

解决方案:

  1. @TableName注解,可以指定JavaBean对应的表名

image-20230612092131188

  1. 在配置文件中全局配置表前缀

image-20230612092144406

即每次在mapper中操作表时,都会添加统一前缀“t_”

  #    配置全局表统一前缀
global-config:
  db-config:
    table-prefix: t_

10.2 @TableId

作用:@TableId注解是专门用在主键上的注解

  1. 若表中的主键名不是id,则可以使用@TableId在实体类中指定主键

  1. @Tableid注解的value属性

如果数据库中的主键字段名和实体中的属性名,不一样且不是驼峰之类的对应关系,可以在实体中表示主键的属性上加@TableId注解,并指定@Tableid注解的value属性值为表中主键的字段名既可以对应上。

image-20230612092232161

  1. @Tableid注解的type属性

mybatis-plus默认生成主键的策略是“雪花算法”,而可以通过type属性来更改默认生成策略

常用的主键策略:

描述
ldType.ASSIGN_ ID (默认) 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关
ldType.AUTO 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,
否则无效

即要在实体类中设置主键自动递增,则在表中也需要设置主键自动递增

  1. 通过全局配置主键生成策略

则实体类中不需要再添加type属性

10.3 @TableField注解

作用:@TableField注解是用来解决实体类中的属性名和字段名不一致的情况

如实体类中的其中的一个属性名为userName,而数据库表中的字段为user_name

image-20230612092642632

mybatis-plus中会默认配置实体类驼峰对应数据库表中字段的下划线,而在mybatis中要进行手动配置

而如果实体类中属性名与数据库表中字段名不一样的情况应该采用@TableField注解来解决

10.4 @TableLogic注解

作用:@TableLogic注解表示逻辑删除

逻辑删除和物理删除的概念

物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据

逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录

使用场景:可以进行数据恢复

image-20230612092806911

11.条件构造器wrapper

11.1Wrapper简介

BaseMapper接口中的条件构造器wrapper,用来封装条件。功能是select …from … where 中的where部分

  • Wrapper :条件构造抽象类,最顶端父类
    • AbstractWrapper :用于查询条件封装,生成sql的where条件
      • QueryWrapper :查询条件封装
      • UpdateWrapper : Update 条件封装
      • AbstractLambdaWrapper :使用Lambda 语法
        • LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
        • LambdaUpdateWrapper : Lambda更新封装Wrapper

11.2 条件构造器queryWrapper(常用查询删除功能)

11.2.1组装查询条件

调用selectList方法,其参数是queryWrapper类型

image-20230612092949020

设置查询条件

    @Test
    public void test01(){
//        查询用户名包含叶,年龄在20-30之间,邮箱信息不为空的用户
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("name","叶").
                between("age",10,20).
                isNotNull("email");
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

生成的SQL语句

SELECT uid AS id,name AS userName,
age,email FROM user 
WHERE (name LIKE ? 
AND age BETWEEN ? AND ? 
AND email IS NOT NULL)

11.2.2 组装排序条件

    @Test
    public void test02(){
//        查询用户信息,按照年龄降序排列,若年龄相同,则按照id升序排列
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        queryWrapper.orderByDesc("age").
                orderByAsc("uid");
        List<User> users = userMapper.selectList(queryWrapper);
        users.forEach(System.out::println);
    }

生成的SQL语句

SELECT uid AS id,name AS userName,age,email 
FROM user 
ORDER BY age DESC,uid ASC

image-20230612093035906

11.2.3组装删除条件

参数同样是queryWrapper类型

image-20230612093049751

@Test
public void test03() {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.isNull("email");
    int delete = userMapper.delete(queryWrapper);
    System.out.println(delete);
}
DELETE FROM user WHERE (email IS NULL)

11.2.4组装修改条件

image-20230612093114613

第一个参数:实体类对象设置要修改的字段和修改的值

    @Test
    public void test04() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//        修改年龄大于18,并且用户名中含有叶,或则邮箱为空的用户
        queryWrapper.gt("age",20)
                .like("name","叶")
                .or()
                .isNull("email");
        User user = new User();
        user.setUserName("叶天帝");
        int update = userMapper.update(user, queryWrapper);
        System.out.println(update);
    }
UPDATE user SET name=? 
WHERE (age > ? AND name LIKE ? OR email IS NULL)

11.2.5组装select子查询

image-20230612093138883

image-20230612093153647

@Test
public void test05(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.select("name","age");
    List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
    maps.forEach(System.out::println);
}
SELECT name,age FROM user

image-20230612093218429

11.2.6组装子查询

@Test
public void test06(){
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.inSql("uid","select uid from user where age < 20");
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

sql语句

SELECT uid AS id,name AS userName,age,email 
FROM user WHERE 
(uid IN (
select uid from user where age < 20))

image-20230612093245124

11.3 条件构造器updateWrapper

11.3.1修改功能

其有两个功能,可以设置修改的条件和设置修改的字段

@Test
public void test07(){
    UpdateWrapper<User> updateWrapper = new UpdateWrapper();
    //        修改年龄大于18,并且用户名中含有叶,或则邮箱为空的用户
    updateWrapper.gt("age",18)
            .like("name","叶")
            .or()
            .isNull("email");
    updateWrapper.set("name","枫原万叶");
    int update = userMapper.update(null, updateWrapper);
    System.out.println(update);
}
UPDATE user SET name=? WHERE 
(age > ? AND name LIKE ? OR email IS NULL)

11.4模拟开发中组装条件的情况(动态拼接SQL)

11.4.1常规方式

    @Test
    public void test08(){
        String username = "";
        Integer ageBegin = 10;
        Integer ageEnd = 20;
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(username)){
//            isNotBlank判断某个字符串是否为空或则空白符
            queryWrapper.like("name",username);
        }
        if (ageBegin != null){
            queryWrapper.ge("age",ageBegin);
        }
        if (ageEnd != null){
            queryWrapper.le("age",ageEnd);
        }
        List<User> users = userMapper.selectList(queryWrapper);
        System.out.println(users);
    }

sql语句

SELECT uid AS id,name AS userName,age,email 
FROM user WHERE (age >= ? AND age <= ?)

11.4.1使用condition组装条件的方式

image-20230612093358324

image-20230612093406504

从以上可以看出,每个函数都有两种形式,第二种方式的参数condition用于条件判断,若为true则执行,false不执行。

简化常规方式

@Test
public void test09(){
    String username = "";
    Integer ageBegin = 10;
    Integer ageEnd = 20;
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.like(StringUtils.isNotBlank(username),"name",username)
            .ge(ageBegin != null,"age",ageBegin)
            .le(ageEnd != null,"age",ageEnd);
    List<User> users = userMapper.selectList(queryWrapper);
    users.forEach(System.out::println);
}

sql语句

SELECT uid AS id,name AS userName,age,email 
FROM user WHERE (age >= ? AND age <= ?) 	

11.3 条件构造器LambdaQueryWrapper

用于防止字段名写错的情况

image-20230612093435629

@Test
public void test10(){
    String username = "";
    Integer ageBegin = 10;
    Integer ageEnd = 20;
    LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper();
    queryWrapper.like(StringUtils.isNotBlank("username"),User::getUserName,username)
            .ge(ageBegin != null,User::getAge,ageBegin)
            .le(ageEnd != null,User::getAge,ageEnd);
    List list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

11.4 条件构造器LambdaUpdateWrapper

12.分页插件的配置和使用

12.1分页插件的配置步骤

  1. 创建mybatis-plus分页插件的配置类
@Configuration
public class MybatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//      DbType表示数据库类型
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

page参数

image-20230612093455485

12.2 对所有数据进行查询分页(selectPage函数的第二个参数为null)

其中selectPage方法

根据 entity 条件,查询全部记录(并翻页)

Params:

  • page – 分页查询条件
  • queryWrapper – 实体对象封装操作类(可以为 null)
    @Test
    public void testPage() {
        Page<User> page = new Page<>(1, 3);
        userMapper.selectPage(page, null);
//        当前页码
        System.out.println(page.getCurrent());
//        总页数
        System.out.println(page.getPages());
//        每页显示的条数
        System.out.println(page.getSize());
//        总记录数
        System.out.println(page.getTotal());
//        获取当前页数据
        System.out.println(page.getRecords());
//        判断是否有上一页
        System.out.println(page.hasPrevious());
//        判断是否有下一页
        System.out.println(page.hasNext());
    }

12.3 采用条件构造器进行条件分页(selectPage函数的第二个参数为条件构造器)

功能需求:查询指定年龄范围内的user,并且分页

    public void testPage() {
        Page<User> page = new Page<>(1, 3);
        QueryWrapper queryWrapper = new QueryWrapper();
        queryWrapper.gt("age",18);
        userMapper.selectPage(page, queryWrapper);
//        当前页码
        System.out.println(page.getCurrent());
//        总页数
        System.out.println(page.getPages());
//        每页显示的条数
        System.out.println(page.getSize());
//        总记录数
        System.out.println(page.getTotal());
//        获取当前页数据
        System.out.println(page.getRecords());
//        判断是否有上一页
        System.out.println(page.hasPrevious());
//        判断是否有下一页
        System.out.println(page.hasNext());
    }

13.自定义分页插件

功能需求:查询指定年龄范围内的user,并且分页

首先在Mapper接口中定义方法

// 自定义分页插件
Page\<User\> getPage(Page\<User\> page,Integer age);

返回值为Page对象

  • 参数1:mybatis-plus提供的分页对象
  • 参数2:年龄参数

Mapper映射文件

<!--        Page<User> getPage(Page<User> page,Integer age);-->
    <select id="getPage" resultType="cn.edu.pdsu.mybatisplus.pojo.User">
        select * from user where age > #{age}
    </select>

测试:

    public void test01(){
        //创建分页对象page
        Page<User> page = new Page<>(1,3);
        Page<User> userPage = userMapper.getPage(page, 18);
        //        当前页码
        System.out.println(userPage.getCurrent());
//        总页数
        System.out.println(userPage.getPages());
//        每页显示的条数
        System.out.println(userPage.getSize());
//        总记录数
        System.out.println(userPage.getTotal());
//        获取当前页数据
        System.out.println(userPage.getRecords());
//        判断是否有上一页
        System.out.println(userPage.hasPrevious());
//        判断是否有下一页
        System.out.println(userPage.hasNext());
    }

image-20230612093641932

14.乐观锁插件

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

乐观锁实现原理:

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

14.1场景引入

一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。 现在商品价格是70元,比成本价低10元。

上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过 了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。

环境模拟

创建Product实体类

创建实体类接口,并继承BaseMapper接口。在测试类中进行模拟

    @Autowired(required = false)
    UserMapper userMapper;
    @Autowired(required = false)
    ProductMapper productMapper;

    @Test
    public void test01() {
//        小李查询商品价格
        Product product1 = productMapper.selectById(1);
        System.out.println("小李查询到的商品价格为:" + product1.getPrice());
        //        小李查询商品价格
        Product product2 = productMapper.selectById(1);
        System.out.println("小王查询到的商品价格为:" + product2.getPrice());

//        小李将商品价格加50
        product1.setPrice(product1.getPrice() + 50);
        productMapper.updateById(product1);

//        小王将商品价格减去30
        product2.setPrice(product2.getPrice() - 30);
        productMapper.updateById(product2);

//        老板查询价格
        Product product3 = productMapper.selectById(1);
        System.out.println("老板查询到的商品价格为:" + product3.getPrice());
    }

image-20230612093733522

14.2乐观锁的实现流程

  1. 在要操作的数据实体类中为版本号字段添加@Version注解

  1. 在配置类中添加乐观锁插件的配置项
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//      添加分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//        添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
    @Test
    public void test01() {
//        小李查询商品价格
        Product product1 = productMapper.selectById(1);
        System.out.println("小李查询到的商品价格为:" + product1.getPrice());
        //        小李查询商品价格
        Product product2 = productMapper.selectById(1);
        System.out.println("小王查询到的商品价格为:" + product2.getPrice());

//        小李将商品价格加50
        product1.setPrice(product1.getPrice() + 50);
        productMapper.updateById(product1);

//        小王将商品价格减去30
        product2.setPrice(product2.getPrice() - 30);
        int i = productMapper.updateById(product2);
        if (i == 0){
//            小王重新获取
            Product productNew = productMapper.selectById(1);
            productNew.setPrice(productNew.getPrice() - 30);
            productMapper.updateById(productNew);
        }

//        老板查询价格
        Product product3 = productMapper.selectById(1);
        System.out.println("老板查询到的商品价格为:" + product3.getPrice());
    }

首先小李和小王同时获取商品信息,版本号version初始值为0,在小李修改商品信息后,被@Version注解标注的字段version就会加1,。轮到小王时version的值不对应,小王就重新获取商品信息,再对商品进行更改。

image-20230612093807460

15.通用枚举

所谓的枚举,就是规定好指定的取值范围,所有的内容只能从指定的范围中取得。简单地说就是枚举是一个被命名的整数常数的集合,用于声明一组带标识符的常数

表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现

一:在数据库表中添加gender字段,并将其类型设置为int类型,规定1为男,2为女。

二:创建枚举

public enum genderEnum {
    MALE(1,"男"),
    FEMALE(2,"女");
    //    将注解所标识的属性的值存储到数据库中
    @EnumValue
    private Integer gender;
    private String sexName;

    genderEnum(Integer gender, String sexName) {
        this.gender = gender;
        this.sexName = sexName;
    }
}

image-20230612093841624

测试:

@Test
public void test01(){
    User user = new User();
    user.setUserName("温迪");
    user.setEmail("2533694604@qq.com");
    user.setGender(GenderEnum.MALE);
    int insert = userMapper.insert(user);
    System.out.println(insert);
}

因为采用了 @EnumValue注解,所以是将gender的属性的值存储到数据库中

image-20230612093905667

16.代码生成器

  1. 添加依赖
<!--        代码生成器的核心依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generate</artifactId>
            <version>2.3.3</version>
        </dependency>
<!--            freemarker模板依赖-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
        </dependency>
  1. 创建测试类
        public static void main (String[]args){
            FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatisplus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai", " root", "root")
                            .globalConfig(builder -> {
                                builder.author("xuhuaiang") // 设置作者
//.enableSwagger() // 开启 swagger 模式
                                        .fileOverride() // 覆盖已生成文件
                                        .outputDir("D://mybatis_plus"); // 指定输出目录
                            })
                            .packageConfig(builder -> {
                                builder.parent("cn.edu.pdsu") // 设置父包名
                                        .moduleName("mybatisPlus_codeAuto") // 设置父包模块名
                                        .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://mybatis_plus"));
// 设置mapperXml生成路径
                            })
                            .strategyConfig(builder -> {
                                builder.addInclude("t_user") // 设置需要生成的表名
                                        .addTablePrefix("t_", "c_"); // 设置过滤表前缀
                            })
                            .templateEngine(new FreemarkerTemplateEngine())
                            // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                            .execute();
        }

17.多数据源

从两个数据库中分别查询数据

  1. 引入依赖
<!--        动态数据源-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>
  1. 配置数据源
spring:
  datasource:
    dynamic:
      #    设置默认的数据源,默认值为master
      primary: master
      # 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
      strict: false
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/mybatisplus?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root
        slave_1:
          url: jdbc:mysql://localhost:3306/mybatis_plus_1?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: root
  1. 创建实体类Product和User,并创建对应的接口和Service层,在UserServiceImpl和ProductServiceImpl中指定数据源
@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

}
@Service
@DS("slave_1")
public class ProductServiceImpl extends ServiceImpl<ProductMapper,Product> implements ProductService {
    
}
  1. 测试
@Test
public void test01(){
    User user = userService.getById(1);
    System.out.println("我是mybatisplus数据库中user表中的数据:"+user);

    Product product = productService.getById(1);
    System.out.println("我是mybatis_plus_1数据库中product表中的数据:"+product);

}

image-20230612094101604

18.MyBatisX

MyBatis-Plus为我们提供了强大的mapper和service模板,能够大大的提高开发效率 但是在真正开发过程中,MyBatis-Plus并不能为我们解决所有问题,例如一些复杂的SQL,多表 联查,我们就需要自己去编写代码和SQL语句,我们该如何快速的解决这个问题呢,这个时候可 以使用MyBatisX插件 MyBatisX一款基于 IDEA 的快速开发插件,为效率而生。

image-20230612094110860

18.1MyBatisX代码快速生成

  1. 创建新工程,添加依赖,在配置文件中添加数据源,配置数据库信息

  1. 使用idea中的数据库模块连接上数据库

  2. 点击表名,配置MyBatisX-Generator选项

image-20230612094203056

项目生成成功

18.2使用MyBatis快速生成CRUD

  1. 删除功能

在接口中使用delete关键字选择方法

单条件删除

image-20230612094232474

多条件删除

image-20230612094250389

使用快捷键Alt+Enter生成完整方法

image-20230612094301601

image-20230612094314101

在映射文件中生成的SQL语句
image-20230612094322523

其他方法类似


MyBatis-Plus
https://xhablog.online/2022/06/30/MyBatis-Plus/
作者
Xu huaiang
发布于
2022年6月30日
许可协议