zoukankan      html  css  js  c++  java
  • MyBatisPlus入门学习

    MyBatisPlus

    概述

    官网:https://baomidou.com/

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

    MyBatisPlus可以节省我们大量的工作时间,所有的CRUD可以自动化完成。

    JPA,tk-mapper,MyBatisPlus。

    特性:

    • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
    • 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作

    快速入门

    使用第三方插件:

    1. 导入对应的依赖
    2. 研究依赖如何配置
    3. 编写代码,拓展

    步骤

    1. 创建数据库,mybaits_plus

    2. 创建user表

      DROP TABLE IF EXISTS user;
      
      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');
      
      --真实开发中,version(乐观锁),deleted(逻辑删除),gmt_create,gmt_modified
      
    3. 初始化项目(使用SpringBoot初始化,导入依赖)

      <!--数据库驱动-->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
      </dependency>
      <!--lombok-->
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
      </dependency>
      <!--mybatis-plus-->
      <!--mybatis-plus是自己开发的,并非官方的-->
      <dependency>
          <groupId>com.baomidou</groupId>
          <artifactId>mybatis-plus-boot-starter</artifactId>
          <version>3.0.5</version>
      </dependency>
      

      mybatis-plus可以节省我们大量的代码,尽量不要同时导入mybatis和mybatis-plus!避免版本的差异!

    4. 连接数据库,和mybatis相同。

      application.properties

      # mysql5
      spring.datasource.username=root
      spring.datasource.password=123456
      spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false
      spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
      
      #mysql8 驱动不同com.mysql.cj.jdbc.Driver(可兼容mysql5),需要配置时区 serverTimezone=GMT%2B8
      
    5. 传统方式:pojo-dao(连接mybatis,配置mapper.xml)-service-controller

    6. 使用mybatis-plus后,pojo-mapper接口-使用

      pojo

      package com.zr.pojo;
      
      @Data
      @AllArgsConstructor
      @NoArgsConstructor
      public class User {
      
          private Long id;
          private String name;
          private Integer age;
          private String email;
      }
      

      mapper接口

      package com.zr.mapper;
      
      //在对应的mapper上实现基本的类 BaseMapper
      @Repository //代表持久层的
      public interface UserMapper extends BaseMapper<User> {
      
          //所有的CRUD操作已经编写完成了
          //不需要配置其它文件了
      
      }
      

      主启动类

      package com.zr;
      
      //扫描mapper文件夹
      @MapperScan("com.zr.mapper")
      @SpringBootApplication
      public class MybatisPlusApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(MybatisPlusApplication.class, args);
          }
      
      }
      

      测试

      package com.zr;
      
      @SpringBootTest
      class MybatisPlusApplicationTests {
          //继承了BaseMapper,所有的方法都来自于父类
          //我们也可以编写自己的拓展方法
          @Autowired
          private UserMapper userMapper;
      
          @Test
          void contextLoads() {
              //参数是一个 Wrapper,条件构造器,这里先不用 null
              //查询全部用户
              List<User> users = userMapper.selectList(null);
              users.forEach(System.out::println);
      
          }
      }
      

    注意点:主启动类上扫描mapper下的所有接口。

    配置日志输出

    我们所有的sql现在是不可见的,我们希望知道它是怎么执行的,就必须看日志。

    application.properties中增加

    #配置日志
    mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
    

    日志输出结果:

    CRUD拓展

    插入

    package com.zr;
    
    @SpringBootTest
    class MybatisPlusApplicationTests {
        //继承了BaseMapper,所有的方法都来自于父类
        //我们也可以编写自己的拓展方法
        @Autowired
        private UserMapper userMapper;
    
        //测试插入
        @Test
        public void testInsert(){
    
            User user = new User();
            user.setName("周周");
            user.setAge(20);
            user.setEmail("813794474@qq.com");
    
            userMapper.insert(user);  //自动生成ID
            System.out.println(user.toString());
        }
    
    }
    

    测试结果:

    数据库插入ID的默认值为:全局唯一的ID。

    主键生成策略

    可查询分布式系统唯一id生成解决方案。

    雪花算法:SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法(long型的ID)。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id,其中41bit作为毫秒数,10bit作为机器的id(5bit是数据中心,5bit是机器id),12bit作为毫秒内的流水号(意味着每个节点在每秒可产生4096个ID),最后有一位符号位,永远是0。在分布式系统中的应用十分广泛,且ID 引入了时间戳,基本上保持自增的。

    主键配置:

    字段上加 @TableId(type = IdType.AUTO)

    数据库字段一定要是递增。

    package com.zr.pojo;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    
        //对应数据库中的主键(uuid,自增id,雪花算法,redis,zookeeper)
        @TableId(type = IdType.AUTO)
        private Long id;
        private String name;
        private Integer age;
        private String email;
    }
    

    测试插入。

    源码:

    public enum IdType {
        AUTO(0), //数据库id自增
        NONE(1),  //未设置主键
        INPUT(2),  //手动输入
        ID_WORKER(3),  //默认的全局唯一id
        UUID(4),  //全局唯一id
        ID_WORKER_STR(5);  // ID_WORKER的字符串表示法
    }
    

    更新操作

    测试方法中添加:

    //更新
    @Test
    public void testUpdate(){
        User user = new User();
        user.setId(22L);
        user.setName("周周zzzz");
        user.setAge(20);
    
        userMapper.updateById(user);
    }
    

    sql自动动态配置

    自动填充

    创建时间,修改时间!这些操作都是自动化完成的,我们不希望手动更新。

    阿里巴巴开发手册:所有的数据库,gmt_creat,gmt-modified几乎所有的表都要配置上,而且需要自动化。

    方式一:数据库级别(工作中不允许修改数据库的,不建议使用)

    1. 在表中新增字段,create_time,update_time(create_time不勾选根据当前时间更新)

    2. 再次测试插入方法,需要先把实体类同步!

      private Date createTime;
      private Date updateTime;
      
    3. 再次测试更新操作。

    方式二:代码级别

    1. 删除时间的默认值

    2. 实体类字段属性上需要增加注解

      //字段插入填充内容
      @TableField(fill = FieldFill.INSERT)
      private Date createTime;
      
      @TableField(fill = FieldFill.INSERT_UPDATE)
      private Date updateTime;
      
    3. 编写处理器来处理注解

      package com.zr.handler;
      
      @Slf4j
      @Component  //加到IOC容器中
      public class MyMetaObjectHandler implements MetaObjectHandler {
          //插入时的填充策略
          @Override
          public void insertFill(MetaObject metaObject) {
              log.debug("start insert....");
              this.setFieldValByName("createTime",new Date(),metaObject);
              this.setFieldValByName("updateTime",new Date(),metaObject);
          }
      
          //更新时的填充策略
          @Override
          public void updateFill(MetaObject metaObject) {
              log.debug("start update....");
              this.setFieldValByName("updateTime",new Date(),metaObject);
          }
      }
      
    4. 测试插入,观察时间更新。

    乐观锁

    乐观锁:顾名思义非常乐观,总是认为不会出现问题,无论干什么都不去上锁,如果出现了问题,再次更新值测试。

    悲观锁:顾名思义非常悲观,总是认为会出现问题,无论干什么都加锁,再去操作。

    乐观锁实现方式:

    • 取出记录时,获取当前version
    • 更新时,带上这个version
    • 执行更新时, set version = newVersion where version = oldVersion
    • 如果version不对,就更新失败
    乐观锁,先查询,获取版本号
    ---A
    update user set name = "zhour",version=version+1
    where id = 2 and version = 1
    
    ---B 线程先完成,这个时候versioon=2,A执行失败
    update user set name = "zhour",version=version+1
    where id = 2 and version = 1
    

    测试乐观锁MP插件:

    数据库中增加version字段

    同步实体类

    //乐观锁的注解
    @Version
    private Integer version;
    

    注册组件(主启动类的扫描mapper放到这里)MyBatisPlusConfig

    package com.zr.config;
    
    //扫描mapper文件夹
    @MapperScan("com.zr.mapper")
    @EnableTransactionManagement
    @Configuration //代表是一个配置类
    public class MyBatisPlusConfig {
        //注册乐观锁插件
        @Bean
        public OptimisticLockerInterceptor optimisticLockerInterceptor(){
            return new OptimisticLockerInterceptor();
        }
    }
    

    测试:添加以下代码

    //测试乐观锁成功
    @Test
    public void testOptimisticLocker(){
        //线程1
        //查询用户信息
        User user = userMapper.selectById(222L);
        //修改用户信息
        user.setName("zhourrr");
        user.setEmail("813794474@qq.com");
        //执行更新操作
        userMapper.updateById(user);
    
    }
    
    
    //测试乐观锁失败,多线程下
    @Test
    public void testOptimisticLocker2(){
        //线程1
        User user1 = userMapper.selectById(222L);
        user1.setName("zhourrr111");
        user1.setEmail("813794474@qq.com");
    
        //模拟另外一个线程执行了插队操作
        User user2 = userMapper.selectById(222L);
        user2.setName("zhourrr222");
        user2.setEmail("813794474@qq.com");
        userMapper.updateById(user2);
    
        //自旋锁来多次尝试提交
        userMapper.updateById(user1); //如果没有乐观锁就会覆盖插队线程的值
    }
    

    查询操作

    //测试批量查询
    @Test
    public void testSelectById(){
        User user = userMapper.selectById(222L);
        System.out.println(user);
    }
    
    @Test
    public void testSelectBatchIds(){
        List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
        // System.out.println(users);
        users.forEach(System.out::println);
    }
    
    //按条件查询 Map
    @Test
    public void testSelectByMap(){
        HashMap<String,Object> map = new HashMap();
        //自定义查询
        map.put("name","周周zzzz");
    
        List<User> users = userMapper.selectByMap(map);
        users.forEach(System.out::println);
    }
    

    分页查询

    分页在网站的使用非常多。

    1. 原始用limit分页
    2. pageHelper 第三方插件
    3. MP内置了分页插件

    使用:

    1. 配置拦截器组即可(官网示例)

      MyBatisPlusConfig中添加

       //分页插件
          @Bean
          public PaginationInterceptor paginationInterceptor() {
              return new PaginationInterceptor;
          }
      
    2. 直接使用page对象即可

      //测试分页查询
      @Test
      public void testPage(){
          //参数1:当前页
          //参数2:页面的大小
          Page<User> page = new Page<>(1,5);
          userMapper.selectPage(page,null);
          page.getRecords().forEach(System.out::println);
          System.out.println(page.getTotal());
      }
      

    删除操作

    根据id删除

    //测试删除
    @Test
    public void testDeleteById(){
        userMapper.deleteById(1342445919922073602L);
    }
    //通过id批量删除
    @Test
    public void testDeleteBatchByIds(){
        userMapper.deleteBatchIds(Arrays.asList(1342445919922073602L,222));
    }
    //通过Map删除
    @Test
    public void testDeleteByMap(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("name","zhourrr222");
        userMapper.deleteByMap(map);
    }
    

    逻辑删除

    物理删除:从数据库中直接移除

    逻辑删除:在数据库中没有被移除,而是通过一个变量让它失效

    应用:管理员可以查看被删除的内容,防止数据的丢失,类似于回收站!

    测试:

    1. 在数据库中增加一个deleted字段

    2. 实体类中增加属性

      @TableLogic  //逻辑删除注解
      private Integer deleted;
      
    3. 配置 MyBatisPlusConfig

      //逻辑删除组件
      @Bean
      public ISqlInjector sqlInjector(){
          return new LogicSqlInjector();
      }
      

      application.properties中添加

      #配置逻辑删除
      # 逻辑已删除值(默认为 1)
      # 逻辑未删除值(默认为 0)
      mybatis-plus.global-config.db-config.logic-delete-value=1
      mybatis-plus.global-config.db-config.logic-not-delete-value=0
      
    4. 测试

    观察数据库中id为111的用户deleted的值变为1.

    再次查询id为111的用户,显示为空(只会查询deleted值为0的用户)。

    性能分析插件

    在开发中,我们会遇到一些慢sql。测试,durid....

    MP也提供了性能分析插件,如果超过这个时间就停止运行。

    1. 导入插件

      //sql执行效率插件
      @Bean
      @Profile({"dev","test"})  //设置dev,test环境开启
      public PerformanceInterceptor performanceInterceptor(){
          PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
          performanceInterceptor.setMaxTime(100); //设置sql能够执行的最大时间
          performanceInterceptor.setFormat(true); //是否格式化sql语句
          return performanceInterceptor;
      }
      

      在application.properties中配置环境为测试或者开发环境。

      #设置开发环境
      spring.profiles.active=dev
      
    2. 测试使用

      @Test
      void contextLoads() {
          //参数是一个 Wrapper,条件构造器,这里先不用 null
          //查询全部用户
          List<User> users = userMapper.selectList(null);
          users.forEach(System.out::println);
      
      }
      

    sql执行的时间和格式化的sql

    执行的时间超过了设定的时间就会抛出异常。

    使用性能分析插件可以帮助我们提高效率!

    条件构造器

    AbstractWrapper.

    我们写一些复杂的sql就可以使用它来替代。

    新建一个测试类 WrapperTest。

    以下的测试结合日志的 SQL 语句来分析。

    测试一:查询name不为空的用户,并且邮箱也不为空,且年龄大于12的用户

    package com.zr;
    
    @SpringBootTest
    public class WrapperTest {
    
        @Autowired
        private UserMapper userMapper;
    
        @Test
        void contextLoads() {
            //查询name不为空的用户,并且邮箱也不为空,且年龄大于12的用户
            QueryWrapper<User> wrapper = new QueryWrapper<>();
            wrapper
                    .isNotNull("name")
                    .isNotNull("email")
                    .ge("age",12);
            userMapper.selectList(wrapper).forEach(System.out::println);
        }
    }
    

    测试二:查询名字为周周zzzz的用户

    @Test
    void test2(){
        //查询名字为周周zzzz的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name","周周zzzz");
        User user = userMapper.selectOne(wrapper); //查询一个数据,查询多个用list或者map
        System.out.println(user);
    }
    

    测试三:查询年龄在10-20之间的用户

    @Test
    void test3(){
        //查询年龄在10-20之间的用户
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.between("age",10,20);
        userMapper.selectCount(wrapper);  //查询结果数
    }
    

    测试四:查询名字中没有字母e,且邮箱是以t开头的(t%)

    @Test
    void test4(){
        //模糊查询
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper
                .notLike("name","e")
                .likeRight("email","t");
        List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
        maps.forEach(System.out::println);
    }
    

    测试五:子查询

    @Test
    void test5(){
        // id 在子查询中查出来
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.inSql("id","select id from user where id < 3");
        List<Object> objects = userMapper.selectObjs(wrapper);
        objects.forEach(System.out::println);
    }
    

    测试六:通过 id 降序排序

    @Test
    void test6(){
        // 通过 id 降序排序
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("id");
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
    

    其它更多测试参考mybatis-plus官方文档。

    代码自动生成器

    AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

    package com.zr;
    
    public class ZrCode {
        public static void main(String[] args) {
            //代码自动生成
            AutoGenerator mpg = new AutoGenerator();
    
            //全局配置
            GlobalConfig gc = new GlobalConfig();
            String projectPath = System.getProperty("user.dir");
            gc.setOutputDir(projectPath+"/src/main/java");
            gc.setAuthor("zhour");
            gc.setOpen(false);
            gc.setFileOverride(false); //是否覆盖
            gc.setServiceName("%sService");  //去Service的 i 前缀
            gc.setIdType(IdType.ID_WORKER);
            gc.setDateType(DateType.ONLY_DATE);
            gc.setSwagger2(true);
    
            mpg.setGlobalConfig(gc);
    
            //数据源配置
            DataSourceConfig ds = new DataSourceConfig();
            ds.setUrl("jdbc:mysql://localhost:3306/mybaits_plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT");
            ds.setDriverName("com.mysql.cj.jdbc.Driver");
            ds.setUsername("root");
            ds.setPassword("123456");
            ds.setDbType(DbType.MYSQL);
    
            mpg.setDataSource(ds);
    
            //包的配置
            PackageConfig pc = new PackageConfig();
            pc.setModuleName("blog");
            pc.setParent("com.zrcode");
            pc.setEntity("entity");
            pc.setMapper("mapper");
            pc.setController("controller");
            pc.setService("service");
    
            mpg.setPackageInfo(pc);
    
            //策略配置
            StrategyConfig strategy = new StrategyConfig();
            strategy.setInclude("user");  //设置要映射的表名
            strategy.setNaming(NamingStrategy.underline_to_camel); //转驼峰命名
            strategy.setColumnNaming(NamingStrategy.underline_to_camel); //转驼峰命名
            strategy.setEntityLombokModel(true);
            strategy.setLogicDeleteFieldName("deleted");  //逻辑删除
    
            //自动填充
            TableFill createTime = new TableFill("create_time", FieldFill.INSERT);
            TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
            ArrayList<TableFill> list = new ArrayList<>();
            list.add(createTime);
            list.add(updateTime);
            strategy.setTableFillList(list);
    
            //乐观锁
            strategy.setVersionFieldName("version");
            strategy.setRestControllerStyle(true);
            strategy.setControllerMappingHyphenStyle(true);  //localhost:8080/hello_id_3,下划线命名
    
            mpg.setStrategy(strategy);
    
            mpg.execute();
        }
    }
    

    本文项目结构目录:

  • 相关阅读:
    再战设计模式(九)之组合模式
    再战设计模式(八)之桥接模式
    再战设计模式(七)之代理模式
    nyoj 题目2 括号配对问题
    剑指offer 面试题38
    杭电 1005
    九度oj 题目1552:座位问题
    九度oj 题目1482:玛雅人的密码 清华大学机试
    九度oj 题目1496:数列区间
    九度oj 题目1495:关键点
  • 原文地址:https://www.cnblogs.com/zhou-zr/p/14698200.html
Copyright © 2011-2022 走看看