• MyBatisPlus


    MyBatis-Plus

    一、MyBatis-Plus 简介

    官方文档帮助文档: 简介 | MyBatis-Plus (baomidou.com)

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

    MyBatis-Plus 提供了 通用 mapper 接口 和 service ,不需要写sql 语句,直接生成 sql 语句。

    特性

    • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
    • 损耗小:启动即会自动注入基本 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 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。

    • MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
    • 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库

    框架结构

    framework

    • 右边部分是 MyBatis-Plus 的场景,包含了四种jar包。

    • mybatis-plus 操作数据库中的表是由实体类决定的,字段名是由实体类中的属性决定的。

    二、MyBatis-Plus 入门案例

    a、引入mybatis-plus依赖

            <dependency>
                <groupId>com.baomidougroupId>
                <artifactId>mybatis-plus-boot-starterartifactId>
                <version>3.5.1version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    b、使用 SpringBoot自带的数据源 com.zaxxer.hikari.HikariDataSource 也行,使用Druid数据源也可以

            
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>druid-spring-boot-starterartifactId>
                <version>1.2.8version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    c、配置数据库信息

    spring:
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test
        password: root
        username: root
        type: com.alibaba.druid.pool.DruidDataSource
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    d、创建实体类

    @Data
    //MyBatis-Plus自动根据实体类小写去对应数据库中的表。可以通过 @TableName 指定表名
    @TableName("t_user") 
    public class User {
        private Long id ;
        private String userName ;
        private String password ;
        private String realName ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    e、mapper接口

    public interface UserMapper extends BaseMapper<User> {
    
        //不需要编写 sql 方法,直接继承 BaseMapper
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    f、主程序中【配置类上】,配置 Mapper接口扫描【或者在 mapper 接口上加上 @Mapper 注解】

    //扫描mapper包下的所有mapper接口
    @MapperScan("com.example.mapper")
    
    • 1
    • 2

    g、测试

    class MybatisPlusApplicationTests {
    
        @Autowired
        //userMapper 会报错
        //运行时是没问题,因为 在 SpringBoot 中默认接口是无法实例化的。但实际上 mapper 交给了 动态代理类了。
        private UserMapper userMapper ;
    
        @Test
        @DisplayName("查询")
        void contextLoads() {
            //queryWrapper: 条件构造器,如果没有条件就 null
            List<User> users = userMapper.selectList(null);
            for (User user : users) {
                System.out.println(user);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    h、增加日志功能

    mybatis-plus:
      configuration:
        map-underscore-to-camel-case: false #不使用驼峰命名转换。默认是开启的。
    #    开启日志功能
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意:

    mybatis 会自动将实体类中的属性名转换为符合数据库的字段名

    比如:属性名= userName ====> 自动转换为:user_name

    如果你数据库该字段名为 userName 是会报错的。

    Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column ‘user_name’ in ‘field list’

    三、实现增删改查的基本功能

    1、增加

        /**
         * 插入一条记录
         *
         * @param entity 实体对象
         */
        int insert(T entity);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
        @Test
        @DisplayName("增加")
        void test_insert() {
            //INSERT INTO t_user ( id, userName, password, realName ) VALUES ( ?, ?, ?, ? )
            int res = userMapper.insert(new User(null, "markbolo", "0000", "马克波罗"));
            //断言
            //id = 1504988968188436481  是通过雪花算法生成的iD
            Assertions.assertEquals(1,res);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2、删除

        /**
         * 根据 ID 删除
         *
         * @param id 主键ID
         */
        int deleteById(Serializable id);
    
    
        /**
         * 根据 map 集合 条件,删除记录
         *
         * @param columnMap 表字段 map 对象
         */
        int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
    
    
        /**
         * 删除(根据ID或实体 批量删除)
         *
         * @param idList 主键ID列表或实体列表(不能为 null 以及 empty)
         */
        int deleteBatchIds(@Param(Constants.COLLECTION) Collection<?> idList);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
        @Test
        @DisplayName("根据ID删除")
        void test_deleteById() {
            // DELETE FROM t_user WHERE id=?
            int res = userMapper.deleteById(1504988968188436481L); //超出 int 类型范围,加个  L 表示long类型
            //断言
            Assertions.assertEquals(1,res);
        }
    
        @Test
        @DisplayName("根据条件删除")
        void test_deleteByMap() {
            // DELETE FROM t_user WHERE realName = ? AND password = ?
            Map<String,Object> map = new HashMap<>();
            //将查询条件封装到map集合中
            map.put("realName","老五");
            map.put("password","0000");
            int res = userMapper.deleteByMap(map);
            //断言
            Assertions.assertEquals(1,res);
        }
    
        @Test
        @DisplayName("根据ID批量删除")
        void test_deleteBatch() {
            // DELETE FROM t_user WHERE realName = ? AND password = ?
            List<Long> list = Arrays.asList(1504988968188436482L, 1504988968188436483L, 1504988968188436484L);//转换成一个list集合
    
            int res = userMapper.deleteBatchIds(list);
            //断言
            Assertions.assertEquals(3,res);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    ID 是通过雪花算法生成的 long 类型。

    3、修改

        /**
         * 根据 ID 修改
         *
         * @param entity 实体对象
         */
        int updateById(@Param(Constants.ENTITY) T entity);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
        @Test
        @DisplayName("根据ID修改")
        void test_updateByID() {
            //UPDATE t_user SET userName=?, password=?, realName=? WHERE id=?
            //根据实体类ID 进行修改
            int res = userMapper.updateById(new User(1504988968188436485L,"rose","0000","rose"));
            //断言
            Assertions.assertEquals(1,res);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4、查询

       @Test
        @DisplayName("根据ID查询单个用户信息")
        void test_selectForOne() {
            //SELECT id,userName,password,realName FROM t_user WHERE id=?
            User user= userMapper.selectById(23L);
            System.out.println(user);
        }
    
        @Test
        @DisplayName("根据ID查询多个用户信息")
        void test_selectForMore() {
            //SELECT id,userName,password,realName FROM t_user WHERE id IN ( ? , ? , ? )
            List<Long> ids = Arrays.asList(23L, 35L, 30L);
            List<User> users = userMapper.selectBatchIds(ids);
            System.out.println(users);
        }
    
        @Test
        @DisplayName("根据map和查询用户信息")
        void test_selectForMap() {
            //SELECT id,userName,password,realName FROM t_user WHERE id = ? AND userName = ?
            Map<String,Object> map = new HashMap<>();
            //将查询条件封装在 map集合中
            map.put("userName","rose");
            map.put("id",1504988968188436485L);
            List<User> users = userMapper.selectByMap(map);
            System.out.println(users);
        }
    
        @Test
        @DisplayName("查询所有用户信息")
        void test_selectForList() {
            //SELECT id,userName,password,realName FROM t_user
            List<User> users = userMapper.selectList(null);
            System.out.println(users);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    如果 baseMapper 提供的方法不满足需求,可以自定义sql。

    和 mybatis 一样。在 mapper 接口写 sql 方法。mapper 映射文件里 sql 语句。

    和 mybatis 不一样的是,在SpringBoot 中 mapper映射文件有默认位置: classpath*:/mapper/**/*.xml

    也可以通过:mybatis-plus-mapper-locations: 指定mapper映射文件位置

    四、Service 接口

    说明:

    • 通用 Service CRUD 封装 IService 接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 save 插入 page 分页 前缀命名方式区分 Mapper 层避免混淆。
    • 在 mapper 中: select 查询,delete 删除,insert 增加 ,update 修改
    • 泛型 T 为任意实体对象
    • 建议如果存在自定义通用 Service 方法的可能,建议自己创建 service 接口 和实现类 ,继承 MyBatis-Plus 提供的 IServiceServiceImpl
    • 对象 Wrapper条件构造器

    MyBatis-Plus 提供了 service 接口 和 实现类,但是为了满足开发需要,

    public interface UserService extends IService<User> {
        // IService :泛型为 实体类类型。
    }
    
    • 1
    • 2
    • 3
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
        // ServiceImpl : MyBatis-Plus 提供的 IService 的实现类。
        //第一个泛型:mapper 接口
        //第二个泛型“:实体类对象
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    IService 提供的 批量增加功能:

        @Autowired
        UserService userService ;
    
        @Test
        @DisplayName("批量增加")
        void test_saveBatch() {
            List<User> users = Arrays.asList(
                    new User(null, "aaa", "000", "aaa"),
                    new User(null, "bbb", "000", "bbb"),
                    new User(null, "ccc", "000", "ccc"));
            //参数是一个list集合
            boolean saveBatch = userService.saveBatch(users);
            System.out.println(saveBatch);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    五、MyBatis-Plus 中常用的注解

    1、@TableName : MyBatis-Plus 默认根据实体类名找数据库中的表,如果不一致,可通过 此注解指定表名。

    @TableName("t_user") : 表示对应数据库表名为:t_user

    还可以通过 全局配置 指定表名前缀:

    #    设置数据库表名的前缀
    mybatis-plus:
      global-config:
        db-config:
          table-prefix: t_user  
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2、@TableId : 标注在属性上,将该属性对应的 字段名 看做主键。

    ​ value 属性: @TableId(value = " ") 指定 主键 id 名

    ​ type 属性 :@TableId(type = IdType.AUTO) 设置主键生成策略为自增。【默认是使用雪花算法 ASSIGN_ID】

    ​ 使用条件:数据库中的主键也必须设置为 自增。

    可以通过全局配置设置所有主键为自增:

    mybatis-plus:
      global-config:
        db-config:
    #      设置全局 id 自增功能
          id-type: auto 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3、**@TableField ** : 设置属性名对应的字段名。

    @TableField(value = "user_name") 当属性名和字段名不一致时,可通过 @TableField 注解指定字段名。

    4、@TableLogic

    • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
    • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库 中仍旧能看到此条数据记录
    • 使用场景:可以进行数据恢复

    在数据库中增加 is_delete 字段,用来表示删除状态。 1 表示逻辑删除 0 表示未删除

    1647672865847

    //逻辑删除语句,其实是修改语句,将删除的数据的删除状态 更改为 1 
    UPDATE t_user SET is_delete=1 WHERE uid IN ( ? , ? , ? ) AND is_delete=0 
    
    • 1
    • 2

    数据库中仍然有删除后的数据,但是使用查询语句 是查询不出来的。

    六、条件构造器 Wrapper

    1647673576652

    • Wrapper : 顶级类构造器
    • AbstractLambdaWrapper : 支持 lambda 表达式
      • LambdaUpdateWrapper : 使用 lambda 进行修改的条件构造器
      • LambdaQueryWrapper : 使用 lambda 进行查询的条件构造器
    • UpdateWrapper : 修改 的条件构造器
    • QueryWrapper : 查询 的条件构造器,删除 也用 QueryWrapper

    在使用条件构造器时,默认用 and 拼接

    6.1 queryWrapper

    1、组装条件查询

        @Test
        @DisplayName("条件构造器")
        public void test01(){
            //比如:需要查询 用户名带 a 并且 密码不为空  的用户信息
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.like("user_name","a").isNotNull("password");
    
            List<User> list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    条件构造器支持链式增加,并且方法名的意思和数据库中的 关键字 的意思一样。

    参考: 条件构造器 | MyBatis-Plus (baomidou.com)

    2、组装排序条件

        @Test
        @DisplayName("组装排序条件")
        public void test02(){
            //比如:按照id的降序查询
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.orderByDesc("uid");
            // queryWrapper.orderByAsc("uid");   升序
    
            List<User> list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3、组装删除条件

        @Test
        @DisplayName("组装删除条件")
        public void test03(){
            //比如:删除密码为空的数据
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.isNull("password");
            boolean remove = userService.remove(queryWrapper);
            System.out.println(remove);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4、组装修改条件

        @Test
        @DisplayName("组装修改条件--queryWrapper")
        public void test04(){
            //比如:修改 用户名中带有 a 或者 密码为 0000 的真实姓名为  小明
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.like("user_name","a")
                    //默认组装是由 and 拼接的。 使用 or() 用 or 拼接
                    .or()
                    .eq("password","0000");
            //update() 中有俩个参数:
            //1、实体类 用来修改信息
            //2、queryWrapper:用来获取可以修改的用户信息。
            User user = new User();
            user.setUserName("小明");
            boolean update = userService.update(user, queryWrapper);
            System.out.println(update);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5、条件优先级

    使用 and(Consumer consumer) 方法,会将 ( ) 里面的条件用 括号 括起来。提高优先级。

    里面的 参数就是一个 wrapper

    以下代码执行的 sql 语句:

    UPDATE t_user SET real_name=? WHERE is_delete=0 AND (user_name LIKE ? AND (password = ? OR real_name IS NULL))

        @Test
        @DisplayName("条件优先级")
        public void test05(){
            //比如:修改 1、用户名中带有 a 并且 2、(密码为 0000 或者 真实姓名为 null ) 的真实姓名为  小红
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.like("user_name","a")
                    //MyBaits中的 lambda表达式是优先执行的。
                    .and(i -> i.eq("password","0000").or().isNull("real_name"));
            User user = new User();
            user.setRealName("小红");
            boolean update = userService.update(user, queryWrapper);
            System.out.println(update);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    6、组装 select 子句

    目前位置默认查询出来的字段是所有字段,如何查询出来为指定的字段

    QueryWrapper select(String... columns)

        @Test
        @DisplayName("组装 select 子句")
        public void test06(){
    
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            //指定查询字段
            queryWrapper.select("user_name","real_name");
    
            List<Map<String, Object>> maps = userService.listMaps(queryWrapper);
            maps.forEach(System.out::println);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    6.2 updateWrapper

    前面在进行修改时: 使用 queryWrapper 用来设置修改条件,实体类用来设置修改内容

    updateWrapper : 既可以 用来设置 修改条件,又可以设置修改的内容。

        @Test
        @DisplayName("组装修改条件 --- updateWrapper  ")
        public void test08(){
            比如:修改 用户名中带有 a 或者 密码为 0000 的真实姓名为  小明
            UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
            //修改条件
            userUpdateWrapper.like("user_name","a").or().eq("password","0000");
            //进行修改
            userUpdateWrapper.set("real_name","小明");
            boolean update = userService.update(userUpdateWrapper);
            System.out.println(update);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    6.3 使用condition 组装条件

    下面模拟以下 在开发中的场景:

        @Test
        @DisplayName("condition")
        public void test09(){
            //假设以下条件参数是从浏览器传送过来的。
            String userName = "小明" ;
            String password = "0000" ;
            String realName = "";
    
            //进行查询。但是不需要将 空的参数作为查询条件,就需要增加判断
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            if (StringUtils.isNotBlank(userName)){
                //如果userName不为空,就作为查询条件
                queryWrapper.like("user_name",userName) ;
            }
            if (StringUtils.isNotBlank(password)){
                //如果 password 不为空,就作为查询条件
                queryWrapper.like("password",password) ;
            }
            if (StringUtils.isNotBlank(realName)){
                //如果 realName 不为空,就作为查询条件
                queryWrapper.like("real_name",realName) ;
            }
            List<User> list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    这样判断是非常麻烦的,可以通过 condition 这个参数用来判断。

    每一个 方法里都会提供一个 condition 参数,这个参数就是用来 判断该条件是否组装在sql语句后面

        @Test
        @DisplayName("condition")
        public void test010(){
            //假设以下参数是从浏览器传送过来的。
            String userName = "小明" ;
            String password = "0000" ;
            String realName = "";
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            //第一个参数:condition : 组装条件
            //第二个参数:字段名
            //第三个参数:条件
            queryWrapper.like(StringUtils.isNotBlank(userName),"user_name",userName)
                    .like(StringUtils.isNotBlank(password),"password",password)
                    .like(StringUtils.isNotBlank(realName),"real_name",realName) ;
    
            List<User> list = userService.list(queryWrapper);
            list.forEach(System.out::println);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    七、MyBatis-Plus 插件

    1、分页插件

    开启分页功能:

        //开启分页功能
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor (){
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor() ;
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            return  interceptor ;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    分页使用:

        @Test
        public void test01(){
            // limit start,size  start :表示起始索引,size:显示条数
            //current : 当前页码 。  (当前页码 -1) * size = start
            // size :每页显示条数
            Page<User> page = new Page<>(1,3);
            //查询并且分页
            userService.page(page);
            System.out.println(page);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    page 中的属性:

    records : 每页的数据

    pages : 总页数

    total : 总记录数

    current : 当前页码

    size :每页显示条数

    hasNext :是否有下一页

    hasPrevious : 是否有上一页

    2、乐观锁和悲观锁

    有这样一个场景:

    一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把 商品价格增加50元。小

    李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太

    高,可能会影响销量。又通知小王,你把 商品价格降低30元

    此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王

    也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据

    库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就

    完全被小王的覆盖了。

    现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1

    万多

    也就是说当俩个人同时操作数据库时,都是对同一条数据同时进行的修改。和 银行取钱的场景类似

    上面的故事

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

    如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证

    最终的价格是120元。

    模拟修改冲突:

        @Test
        public void test11(){
            //小李查询价格
            Product productLi = productMapper.selectById(1);
            System.out.println("小李查询的价格 :" + productLi.getPrice());
            //小王查询价格
            Product productWang = productMapper.selectById(1);
            System.out.println("小王查询的价格 :" + productWang.getPrice());
            //小王和小李获取的价格都是 100
    
            //小李修改价格 150
            productLi.setPrice(productLi.getPrice()+50);
            productMapper.updateById(productLi);
            System.out.println("小李修改完价格:" + productLi.getPrice());
            //小王修改价格 70 小王修改的价格会覆盖小李修改的价格
            productWang.setPrice(productWang.getPrice()-30);
            productMapper.updateById(productWang);
            System.out.println("小王修改完价格:" + productWang.getPrice());
    
            //老板查询价格  70 
            Product productBoss = productMapper.selectById(1);
            System.out.println("老板查询的价格:" + productBoss.getPrice());
            //
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在老板查询的时候,商品价格为 70 ,小王的修改已经覆盖了小李的修改。

    乐观锁实现流程

    实现乐观锁一般是在数据库中 增加 version 字段,表示版本号,在修改数据前获取版本号。修改数据时不仅要修改数据,还要修改版本号。【version+1】

    比如:小王和小李第一次获取价格和版本号时,price=100, version=1

    小李在修改数据时,先获取版本号=1,和第一次获取价格时的版本号一致,就可以修改: price 100 + 50 = 150version: 1+1=2

    小王在修改数据时,先获取 version = 2 【因为小李在修改时将版本号增加了 1 】,由于版本号和之前获取的不一致,所以小王的修改操作就不会成功。最后 price=150

    增加乐观锁插件 :

    a、在属性上增加 @Version 注解

        @Version //表示乐观锁版本号字段
        private Integer version ;
    
    • 1
    • 2

    b、增加乐观锁插件

        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor (){
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor() ;
            //增加分页插件
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            //增加乐观锁插件
            interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
            return  interceptor ;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    最后 老板查询的价格:150.0

    因为在小王想要修改价格的时候,获取到的版本号和第一次获取的时候不一致,所以并没有修改成功。

    优化修改流程,使小王的操作成功:

    在小王进行修改时,修改失败后在重新查询,修改价格。

        @Test
        public void test11(){
            //小李查询价格
            Product productLi = productMapper.selectById(1);
            System.out.println("小李查询的价格 :" + productLi.getPrice());
            //小王查询价格
            Product productWang = productMapper.selectById(1);
            System.out.println("小王查询的价格 :" + productWang.getPrice());
            //小王和小李获取的价格都是 100
    
            //小李修改价格 150
            productLi.setPrice(productLi.getPrice()+50);
            productMapper.updateById(productLi);
            System.out.println("小李修改完价格:" + productLi.getPrice());
    
            //小王修改价格 70 小王修改的价格会覆盖小李修改的价格
            productWang.setPrice(productWang.getPrice()-30);
            int res = productMapper.updateById(productWang);
            
            if (res ==0 ){
                //结果=0,说明小王操作失败。就重新查新,重新修改
                Product newProduct = productMapper.selectById(1);
                newProduct.setPrice(newProduct.getPrice() - 30);
                productMapper.updateById(newProduct) ;
            }
            System.out.println("小王修改完价格:" + productWang.getPrice());
    
            //老板查询价格
            Product productBoss = productMapper.selectById(1);
            System.out.println("老板查询的价格:" + productBoss.getPrice());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    八、通用枚举

    在表中有些字段值是固定的,比如性别:只有 男和女,这时候就可以通过枚举来实现。

    @EnumValue + type-enums-package 实现该功能

    增加枚举类:

    @Getter
    public enum SexEnum {
        MALE(1, "男"),
        FEMALE(2, "女");
    
        //将注解标识的属性值存储到数据库。
        @EnumValue
        private Integer sex;
        private String sexName;
    
        SexEnum(Integer sex, String sexName) {
            this.sex = sex;
            this.sexName = sexName;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    配置全局文件扫描枚举:

    mybatis-plus:
    	type-enums-package: com.example.enums
    
    • 1
    • 2

    测试:

        @Test
        public void test01(){
            User user = new User();
            user.setUserName("lisi");
            user.setRealName("李四");
            user.setPassword("0000");
            //使用枚举赋值
            user.setSex(SexEnum.MALE);
            int insert = userMapper.insert(user);
            System.out.println(insert);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    九、代码生成器

    a、引入依赖

            
            <dependency>
                <groupId>com.baomidougroupId>
                <artifactId>mybatis-plus-generatorartifactId>
                <version>3.5.2version>
            dependency>
            
            <dependency>
                <groupId>org.freemarkergroupId>
                <artifactId>freemarkerartifactId>
                <version>2.3.30version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    b、自动生成代码,可直接拷贝

                    FastAutoGenerator.create(
                            "jdbc:mysql://localhost:3306/book",
                            "root",
                            "root")
                            .globalConfig(builder -> {
                                builder.author("yang") // 设置作者
                                        .enableSwagger() // 开启 swagger 模式
                                        .fileOverride() // 覆盖已生成文件
                                        .outputDir("C://user//"); // 指定输出目录
                            })
                            .packageConfig(builder -> {
                                builder.parent("com.example") // 设置父包名
                                        .moduleName("") // 设置父包模块名
                                        .pathInfo(Collections.singletonMap(OutputFile.xml, "C://user//")); // 设置mapperXml生成路径
                            })
                            .strategyConfig(builder -> {
                                builder.addInclude("t_book") // 设置需要生成的表名
                                        .addTablePrefix("t_", "c_"); // 设置过滤表前缀
                            })
                            .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                            .execute();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    十、多数据源

    适用于多种场景:纯粹多库、 读写分离、 一主多从、 混合模式等

    a、引入依赖

            
            <dependency>
                <groupId>com.baomidougroupId>
                <artifactId>dynamic-datasource-spring-boot-starterartifactId>
                <version>3.5.0version>
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    b、增加配置

    spring:
      datasource:
        dynamic:
          primary: master #设置默认的数据源或者数据源组,默认值即为master
          strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
          datasource:
            master:
              url: jdbc:mysql://localhost:3306/test
              username: root
              password: root
              driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
            slave_1:
              url: jdbc:mysql://localhost:3306/book
              username: root
              password: root
              driver-class-name: com.mysql.jdbc.Driver
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    c、只需要在 service 使用 @DS() 注解指定数据源即可。

    @DS 可以标注在方法上或者类上【就近原则,方法优先】

    @Service
    @DS("master") //指定master数据源
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    @Service
    @DS("slave_1") //指定数据源
    public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements IBookService {
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    十一、MyBatis-X 插件

    插件搜索:MyBatisX 插件 下载安装 重启
    在这里插入图片描述

  • 相关阅读:
    23种设计模式——策略模式
    js实现关闭子窗口时刷新父窗口
    vue3后台管理系统封装的弹窗组件
    Python 教程之控制流(12)组合迭代器
    微信小程序分享功能
    【CSS】background怎么设置多个背景图
    1769. 移动所有球到每个盒子所需的最小操作数
    复杂逻辑的开发利器—Mendix快速实现AQL质量抽检
    世界数字工厂的发展现状究竟如何?仅10%公司实施完成!
    Nginx基础篇-Nginx Location
  • 原文地址:https://blog.csdn.net/aetawt/article/details/126105008