[前端学java14-Mybatis Plus] 分页插件 和 乐观锁插件

300 阅读9分钟

导航

[react] Hooks

[封装01-设计模式] 设计原则 和 工厂模式(简单抽象方法) 适配器模式 装饰器模式
[封装02-设计模式] 命令模式 享元模式 组合模式 代理模式
[封装03-设计模式] Decorator 装饰器模式在前端的应用
[封装04-设计模式] Publish Subscribe 发布订阅模式在前端的应用
[封装05-ElementUI源码01] Row Col Container Header Aside Main Footer

[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署

[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] koa
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend

[源码-vue06] Vue.nextTick 和 vm.$nextTick
[源码-vue07] keep-alive

[源码-react01] ReactDOM.render01
[源码-react02] 手写hook调度-useState实现

[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[部署04] [复习] gitlabCI + docker-compose + ssh免密登录 + 最全Dockerfile [部署05] Kubernetes01

[数据结构和算法01] 二分查找和排序
[数据结构和算法02] 回文字符串
[数据结构和算法03] 栈 和 队列
[数据结构和算法04] 链表 和 树

[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[深入24] Fiber01
[深入25] Typescript
[深入26] Drag

[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
[前端学java05-SpringBoot实战] 常用注解 + redis实现统计功能
[前端学java06-SpringBoot实战] 注入 + Swagger2 3.0 + 单元测试JUnit5
[前端学java07-SpringBoot实战] IOC扫描器 + 事务 + Jackson
[前端学java08-SpringBoot实战总结1-7] 阶段性总结
[前端学java09-SpringBoot实战] 多模块配置 + Mybatis-plus + 单多模块打包部署
[前端学java10-SpringBoot实战] bean赋值转换 + 参数校验 + 全局异常处理
[前端学java11-SpringSecurity] 配置 + 内存 + 数据库 = 三种方式实现RBAC
[前端学java12-SpringSecurity] JWT
[前端学java13-SpringCloud] Eureka + RestTemplate + Zuul + Ribbon
[前端学java14-Mybatis Plus] 分页插件 和 乐观锁插件

复习笔记-01
复习笔记-02
复习笔记-03
复习笔记-04

前置知识

(1) 一些单词

radix 基数
trap 陷阱 

optimism 乐观 // optimistic 乐观的 adj
pessimism 悲观

(2) Mybatis-plus 的安装和使用

1. 安装 Mybatis-Plus maven 依赖
--- 

<!-- mybatis plus -->
<!-- mybatis plus包含了 ( mybatis-spring-boot-starter ) 和 ( spring-boot-starter-data-jdbc ) -->
<!-- 在 application.yml 文件中通过 mybatis-plus: xxx 对其进行定制配置 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.2</version>
</dependency>
2. 在启动类上添加 @MapperScan
---

@MapperScan("com.example.lingjing.mapper")
public class Application {}
3
因为 mybatis-plus 可以和 mybatis 混用
所以 我们也安装配置一下mybatis
---
mybatis安装和配置和使用
---



3.1 安装
安装 mybatis maven 依赖
---
<!-- mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>



3.2 配置
application.yml 中配置 mybatis
---
mybatis:
  # 配置 mybatis 规则
  # 默认的前置路径就是 resources 文件夹中
  # config-location: classpath:mybatis/mybatis-config.xml # mybatis全局配置文件 ( 注意:如果有下面的 configuration 就要注释掉 config-location )
  # 1
  # mapper-locations
  # - mybatis的sql映射文件,注意是 mapper-locations,有s
  # - 默认的路径前缀是 src/main/resources
  # 2
  # classpath
  # - 1. 代表的是 src/main/java
  # - 2. 代表的是 src/main/resources
  # 这里指定了mapper的xml配置查询文件的位置,在 ( mapper类 和 mapper的xml文件 ) 是一一对应的
  mapper-locations: classpath:mapper/*.xml
  
  # configuration
  # - 配置规则,和 mybatis-config.xml 文件中配置是一样的效果,使用一种即可,即 ( configuration ) 或者 ( config-location ) 二选一
  configuration:
    map-underscore-to-camel-case: true
    
    
    
3.3 使用
- mybatis有两种方式来使用
  - 纯注解方式
  - xml方式 ( 更推荐 )
---
编写bean对象
public class UserBean {}
编写mapper类
@Mapper
public interface UserMapper {
    public UserBean getUser(Integer id);
}
编写mapper xml
<mapper namespace="com.example.lingjing.mapper.UserMapper">
    <select id="getUser" resultType="com.example.lingjing.bean.UserBean">
        select * from user where id=#{id}
    </select>
</mapper>
4 mybat-plus的使用
- 可以使用mapper的能力,也可以使用service的能力
  - mapper
  - service
4.1 mapper
- 主要就是编写一个mapper接口去 extends BaseMapper<bean>
---

第一步
@Mapper 
public interface UserMybatisPlusMapper extends BaseMapper<UserMybatisPlusBean> {}

第二步
就可以通过 @Autowired 注入后使用了,比如如下
@SpringBootTest
@Slf4j
public class UserMybatisPlusMapperTest {

    @Autowired
    UserMybatisPlusMapper userMybatisPlusMapper;


    // 1
    // mapper 相关测试
    // - select -> selectById + selectList
    // - update -> updateById
    // - delete -> deleteById
    // - insert -> insert


    // 查 select single
    @Test
    public void mybatisPlusUserSelectTest() {
        UserMybatisPlusBean userPlusBean = userMybatisPlusMapper.selectById(1);
        System.out.println(userPlusBean);
    }

    // 查 select list
    @Test
    public void mybatisPlusUserSelectTestList() {
        QueryWrapper queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "woow_wu7");

        List list = userMybatisPlusMapper.selectList(null); // null表示查询所有
        log.info("list => {}", list);
    }


    // 增 insert
    @Test
    public void mybatisPlusUserInsertTest() {
        UserMybatisPlusBean userPlusBean1 = new UserMybatisPlusBean();

        // 2
        // builder() 是 lombok 的 @Builder 的能力
        UserMybatisPlusBean build = userPlusBean1.builder()
                .name("aaa")
                .age(900)
                .address("bbb")
                .id(900)
                .build();
        log.info("build => {}", build);

        // 3
        // 遇到问题
        // - 问题:lombok 的 builder() 不生效?
        // - 原因:builder()是有返回值的,返回值build才是最终设置过后的值,而不是userPlusBean1

        int status = userMybatisPlusMapper.insert(build);
        log.info("status => {}", status);
    }

    // 改 update
    @Test
    public void mybatisPlusUserUpdateTest() {
        UserMybatisPlusBean userPlusBean2 = new UserMybatisPlusBean();

        // 修改的就是上面 insert 的数据
        UserMybatisPlusBean build = userPlusBean2.builder()
                .name("AAA")
                .age(900)
                .address("BBB")
                .id(900)
                .build();
        log.info("build => {}", build);
        int status = userMybatisPlusMapper.updateById(build);
        log.info("status => {}", status);
    }

    // 删 delete
    @Test
    public void mybatisPlusUserDeleteTest() {
        int status = userMybatisPlusMapper.deleteById(900);
        log.info("status => {}", status);
    }
}
4.2 service
- 主要就是编写一个 ( 接口 ) interface 去 extends ( Iservice<bean对象> )
- 然后编写一个 ( 实现类  ) 去 extends ( ServiceImpl<mapper类, bean对象> )
---

第一步 - service接口
public interface UserMybatisPlusService extends IService<UserMybatisPlusBean> {}

第二步 - service实现类
@Service
public class UserMybatisPlusServiceImpl extends ServiceImpl<UserMybatisPlusMapper, UserMybatisPlusBean> implements UserMybatisPlusService {}

第三步 - 使用
@SpringBootTest
@Slf4j
public class UserMybatisPlusServiceTest {
    @Autowired
    UserMybatisPlusService userMybatisPlusService;
    // 查 list
    @Test
    public void getList() {
        List<UserMybatisPlusBean> list = userMybatisPlusService.list();
        log.info("list => {}", list);
    }
}

(一) mybatis-plus 分页插件

(1) mybatis-plus 分页插件的使用

(1.1) 编写配置类

    1. 在配置类中,新建 new MybatisPlusInterceptor() 实例
    1. 在 MybatisPlusInterceptor实例上调用添加拦截方法 addInnerInterceptor
    1. 添加分页拦截器 new PaginationInnerInterceptor(DbType.MYSQL)
(1.1) 编写配置类
---

@Configuration // 配置类
public class MyBatisPlusPaginationConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {

        // 1
        // MybatisPlusInterceptor

        // 2
        // interceptor.addInnerInterceptor
        
        // 3
        // PaginationInnerInterceptor

        // 4
        // 向MyBatis-Plus的过滤器链中添加 ( 分页拦截器 )
        // 需要设置数据库类型( 主要用于分页方言 )
        
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 拦截器
        
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 
        // mysql类型,我们使用的是 mysql 数据库

        return interceptor;
    }
}

(1.2) 使用

  • 1.生成 page 对象,具有 current, size 属性
  • 2.通过在业务中调用 mapper.selectPage(page对象, wrapper) 即可
@SpringBootTest
@Slf4j
public class TestMybatisPlusPaginationPlugin {

    @Autowired
    UserMybatisPlusMapper userMybatisPlusMapper;

    @Test
    public void testPaginationPlugin() {
    
        // page对象
        // - 由 mybatis-plus 提供
        // - current,size
        Page<UserMybatisPlusBean> objectPage = new Page<>(1, 2); // 查询第1页,每次条数2条

        // selectPage
        // - 是 mybatis-plus mapper 提供的能力
        userMybatisPlusMapper.selectPage(objectPage, null);

        log.info(" objectPage.getRecords() => {}", objectPage.getRecords()); // ----- 查到的数据
        log.info(" objectPage.getTotal() => {}", objectPage.getTotal()); // --------- 总数据条数
        log.info(" objectPage.getPages() => {}", objectPage.getPages()); // --------- 总页码数
        log.info(" objectPage.hasNext() => {}", objectPage.hasNext()); // ----------- 是否有下一页
        log.info(" objectPage.hasPrevious() => {}", objectPage.hasPrevious()); // --- 是否有上一页
    }
}

(2) 自定义分页功能 - 在mybatis的查询语句中使用

  • 其实就是利用mybatis-plus的 ( page对象 ) 来实现
第一步
-mapper接口interface中定义 - 自定义分页的方法
---

/**
 * @desc 通过 用户ID 查询 用户信息
 * @param page Mybatis-plus 提供的分页对象,必须是第一个参数位置
 * @param id 表数据 id
 * @return page
 */
// 重要
// 自定义分页方法
// - 返回值:是mybatis-plus的 - page对象


@Mapper
public interface UserMapper {
    public Page<UserBean> selectPageVo(
            @RequestParam("page") Page<UserBean> page,
            @RequestParam("id") Integer id
    );
}
第二步
- 在 mapper.xml 文件中编写查询语句,就是 mybatis 的那一套
---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace 是 ( mapper类 ) 的全路径,注意分隔符是.而不是/ -->
<mapper namespace="com.example.lingjing.mapper.UserMapper">
    <!-- 查 -->
    <!--    注意:resultType 是返回值类型,即Bean对象的文件路径-->
    <!--    注意:id 是方法名-->
    <!--    问题:那如何使用 别名 呢?-->
    <!--    回答:在 mybatis-plus 的配置项中设置,就不用写bean的全路径了-->
    <select id="selectPageVo" resultType="com.example.lingjing.bean.UserBean">
        select * from user where id=#{id}
    </select>
</mapper>
第三步 - 使用
---
@SpringBootTest
@Slf4j
public class TestCustomPagination {

    @Autowired
    UserMapper userMapper;

    @Test
    public void testCustomPagination() {
        Page<UserBean> objectPage = new Page<UserBean>(1, 2);
        userMapper.selectPageVo(objectPage, 1); // 参数是 page, id

        log.info(" objectPage.getRecords() => {}", objectPage.getRecords()); // ----- 查到的数据
        log.info(" objectPage.getTotal() => {}", objectPage.getTotal()); // --------- 总数据条数
        log.info(" objectPage.getPages() => {}", objectPage.getPages()); // --------- 总页码数
        log.info(" objectPage.hasNext() => {}", objectPage.hasNext()); // ----------- 是否有下一页
        log.info(" objectPage.hasPrevious() => {}", objectPage.hasPrevious()); // --- 是否有上一页
    }
}

image.png

(二) 乐观锁 和 悲观锁

(1) 概念

乐观锁 和 悲观锁
- 悲观锁:并发操作时,阻塞执行(悲观的认为数据会被其他线程同时修改),加锁,读和取都会阻塞
- 乐观锁:并发操作时,读不阻塞(乐观的认为数据不会被其他线程同时修改,随便读),不加锁,写会根据版本号version来判断报错或重试

乐观锁
- 概念:
  - 在操作数据时非常乐观,认为别的线程不会同时修改数据,所以不会上锁
  - 但是在更新的时,会判断一下在此期间,别的线程有没有更新这个数据
- 实现
 - CAS
 - Version版本号机制
- 适用场景
  - 适合并发 ( 读 ) 操作多的场景,( 不加锁 ) 的特点使得读操作的性能高
 
悲观锁
- 概念
 - 在操作数据时非常悲观,每次读数据的时,都认为同时有其他线程在修改数据
 - 所以每次拿到数据时就会 ( 上锁 ),操作完才会释放锁,上锁期间其他线程将会 ( 阻塞 )
- 缺点
  - 阻塞其他线程,效率低
  - 可能造成某个线程永久等待,即发生 ( 死锁 ) 的可能性比较大
- 适用场景
  - 适合并发 ( 写 ) 操作多的场景,( 先加锁再进行写操作 ),保证写操作的数据正确性
  
口诀:
  - 乐观锁,不加锁,读
  - 悲观锁,加锁,写
  
---

案例:
商品价格 100
a和b同时在操作商品价格,a减10,b加10

乐观锁:100 - 10 a操作,b也操作 100 + 10,==================> 最终 110
悲观锁:100 - 10 = 90 a操作完后,b才操作 90 + 10 = 100,=====> 最终 100

(2) mybatis-plus 乐观锁插件

(1) 编写配置类

@Configuration
public class MyBatisPlusOptimisticLockerConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        // 1
        // MybatisPlusInterceptor
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 拦截器

        // 2
        // 向MyBatis-Plus的过滤器链中添加 ( 乐观锁拦截器 )
        // 注意要想 bean 中添加 @Version 注解
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); // 添加乐观锁插件

        return interceptor;
    }
}

(2) bean 对象添加 @Version

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
@Component
@TableName("price") // mybatis-plus 查询的表名
public class PriceBean {

    public Integer price;
    public String name;
    public Integer id;

    @Version // mybatis-plus 标识乐观锁版本号
    public Integer version;
}

(3) 编写mapper接口

@Mapper
public interface ProductMapper extends BaseMapper<ProductBean> {}

(4) 测试乐观锁

@SpringBootTest
@Slf4j
public class TestMybatisPlusOptimisticLocker {

    @Autowired
    ProductMapper productMapper;

    @Test
    public void testMybatisPlusOptimisticLocker() {

        ProductBean productMapper1 = productMapper.selectById(1);
        ProductBean productMapper2 = productMapper.selectById(1);

        // 1 获取到的价格
        log.info("1获取到的价格price1 => {}",  productMapper1.getPrice());
        
        // 2 获取到的价格
        log.info("2获取到的价格price2 => {}", productMapper2.getPrice());


        // 1 将价格 + 10
        productMapper1.setPrice(productMapper1.getPrice() + 10);
        productMapper.updateById(productMapper1); // 更新数据库


        // 2 将价格 - 10
        productMapper2.setPrice(productMapper1.getPrice() - 10);
        productMapper.updateById(productMapper2); // 更新数据库


        ProductBean productMapperEnd = productMapper.selectById(1);
        log.info("最终查询到的价格price => {}",  productMapperEnd.getPrice()); // 110
    }
}

image.png