一文搞定MybatisPlus

MybatisPlus简介

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

 (来自官网) 

体验Mybatisplus

1.创建SpringBoot工程,导入mysql,lombok,spring web

2.改setting里面的encoding为utf-8

3.pom文件中引入依赖

<!--        druid数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
<!--        mybatisplus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>

4.yml文件中设置数据源相关参数

4.编写实体类

package com.example.entity;
import lombok.Data;
@Data
public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
}

 

5.编写mapper接口

继承BaseMapper,指定泛型为User

加上@Mapper注解

tips:如何让springboot的启动类扫到mapper文件夹里面的mapper接口

1.加上@Mapper注解,同时需要保证mapper文件夹在启动类所在包或其子包中(也就是启动类所在的层次要比被扫描的文件夹更高或同级)

2.启动类加上@MapperScan,指定要扫描的文件夹

 6.写一个测试类,进行测试

@SpringBootTest
public class TestUserMapper {
    @Autowired
    private UserMapper userMapper;


    @Test
    void testSelectAll(){
        List<User> users = userMapper.selectList(null);
        System.out.println("users:"+users);
    }
}

ps:如果是第二种方式扫描mapper接口,userMapper会爆红,这是因为UserMapper是一个接口,不能实例化对象 。只有在服务器启动IOC容器初始化后,由框架创建DAO接口的代理对象来注入。 现在服务器并未启动,所以代理对象也未创建,IDEA查找不到对应的对象注入,所以提示报红 一旦服务启动,就能注入其代理对象,所以该错误提示不影响正常运行。 

Mybatisplus标准CURD以及分页

    @Test
    void testInsert(){
        //增
        User user = new User();
        user.setName("李四");
        user.setAge(18);
        user.setPassword("1599");
        user.setTel("18959657411");
        int i = userMapper.insert(user);
        System.out.println("新增了"+i+"条数据");
    }

    @Test
    void testDelete(){
        //删
        int i = userMapper.deleteById(1827551884282552322L);
        System.out.println("删除了"+i+"条数据");
    }

    @Test
    void testUpdate(){
        //改
        User user = new User();
        user.setId(1L);
        user.setName("Tom·A·Cat");
        int i = userMapper.updateById(user);
        System.out.println("修改了"+i+"条数据");
    }

ps:修改的时候,只修改实体对象中有值的字段 。如果是我们以前用的mybatis做开发,没有指定password,age和tel,这些字段会被覆盖为null,mybatisplus的优势就体现出来了。

    @Test
    void testSelect(){
        //查
        User user = userMapper.selectById(1L);
        System.out.println(user);
    }

分页

官网:分页插件 | MyBatis-Plus

首先要添加一个分页拦截器

package com.example.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MybatisPlusConfig {
    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
        // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
        return interceptor;
    }
}

使用分页功能

    @Test
    void testPage(){
        //1.创建IPage对象(Page实现了IPage接口),设置分页参数
        Page<User> page = new Page<>(1,3);//当前页码为1  每页5条数据
        //2.执行分页查询
        userMapper.selectPage(page,null);//没有指定条件
        //3.获取分页结果
        System.out.println("当前页码:"+page.getCurrent());
        System.out.println("每页显示数:"+page.getSize());
        System.out.println("一共多少页:"+page.getPages());
        System.out.println("一共多少条数据:"+page.getTotal());
        System.out.println("数据:"+page.getRecords());
    }

 *****************************************************

DQL编程控制

条件查询

 我们需要依靠wrapper来构建要查询的条件

构建条件查询的三种方式

年龄小于10岁的

1.QueryWrapper
    //1.QueryWrapper
    @Test
    void testQuery01(){
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.lt("age",10);
        System.out.println(userMapper.selectList(qw));
    }
2.lambda方式的QueryWrapper
    //2.lambda方式的QueryWrapper
    @Test
    void testQuery02(){
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.lambda().lt(User::getAge,10);
        System.out.println(userMapper.selectList(qw));
    }

解决了字段可能写错的问题 。

3.LambdaQueryWrapper
    //3.LambdaQueryWrapper
    @Test
    void testQuery03(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.lt(User::getAge,10);
        System.out.println(userMapper.selectList(lqw));
    }

不用 .lambda()了,代码更加简洁。 

多条件查询

年龄大于10小于20的 (and)
    //年龄大于10且小于20
    @Test
    void testQuery04(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.gt(User::getAge,10).lt(User::getAge,20);//支持链式编程
        System.out.println(userMapper.selectList(lqw));
    }
年龄小于10大于20的   (or)
    //年龄小于10或大于20
    @Test
    void testQuery05(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.lt(User::getAge,10).or().gt(User::getAge,20);// or()
        System.out.println(userMapper.selectList(lqw));
    }

null条件判定 

需求

用户输入两个年龄(minAge和maxAge),查询用户指定年龄范围里的User,但是如果用户只想查看18岁以下的User,只填写了maxAge为18,那么minAge为null,这时候要怎样让minAge不加入条件呢?MybatisPlus早就想到了这一点。

 **************************************************************

写一个AgeField实体类来模拟前端传来的minAge和maxAge

package com.example.entity;
import lombok.Data;
@Data
//模拟前端传过来的最小年龄和最大年龄
public class AgeField {
    private Integer minAge;
    private Integer maxAge;
}
 测试代码
    /**
     * null判定
     */
    @Test
    void testQuery06(){
        AgeField af = new AgeField();
        af.setMinAge(10);
        af.setMaxAge(20);
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.gt(af.getMinAge()!=null,User::getAge,af.getMinAge())
           .lt(af.getMaxAge()!=null,User::getAge,af.getMaxAge());
        List<User> users = userMapper.selectList(lqw);
        System.out.println(users);
        System.out.println(users.size());
    }

参数condition:为真时该条件才加入查询语句

参数column:表中字段

参数val:传入的字段值

 数据库

共15条记录

测试结果

传入10和20 ,查到两条数据

 现在我们把这两条注掉,两条的条件都为false,不加入查询条件,相当于select * from user

//        af.setMinAge(10);
//        af.setMaxAge(20);

 查到15条数据,也就是user表中的所有数据,符合预期。

查询投影

user表中新增一个sex字段,同时修改实体类

查询结果包含模型类中部分属性

LambdaQueryMapper写法

    @Test
    //查询结果包含模型类中部分属性
    void testQuery07(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        //查询年龄在10~20之间的所有用户的id,name,age信息
        lqw.select(User::getId,User::getName,User::getAge);
        lqw.gt(User::getAge,10);
        lqw.lt(User::getAge,20);
        System.out.println(userMapper.selectList(lqw));
    }

QueryMapper写法

    @Test
    void testQuery08(){
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.select("id","name","age");
        qw.gt("age",10);
        qw.lt("age",20);
        System.out.println(userMapper.selectList(qw));
    }
 测试结果

查询结果包含模型类中未定义属性

聚合和分组,无法使用lambda表达式完成,正如MP官网所说的,MybatisPlus只是对Mybatis进行增强,MP无法做到的事情我们还是要老老实实地回到mapper接口里面写sql。

聚合查询
    @Test
    //聚合
    void testQuery09(){
        QueryWrapper<User> qw = new QueryWrapper<>();
        
        //这里也可以连着写,为了方便看,用逗号分开
        qw.select("count(*) as count",
                "max(age) as maxAge",
                "min(age) as minAge",
                "sum(age) as sumAge",
                "avg(age) as avgAge");
        
        //用selectMaps方法,返回一个map,这个map的key为聚合字段,value为聚合结果
        List<Map<String, Object>> maps = userMapper.selectMaps(qw);
        System.out.println(maps);
    }

分组查询

相当于 select sex,count(*) from user group by sex;

    @Test
    //分组
    void testQuery10(){
        //按性别分组
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.select("sex,count(*) as count");
        qw.groupBy("sex");
        List<Map<String, Object>> maps = userMapper.selectMaps(qw);
        System.out.println(maps);
    }

深入体验查询条件的设置

条件构造器 | MyBatis-Plus

等值查询

    @Test
    void testQuery11(){
        //等值查询 模拟登录校验
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.eq(User::getName,"张三").eq(User::getPassword,"zs666");
        User loginUser = userMapper.selectOne(lqw);//只查一个用selectOne就可以
        System.out.println(loginUser);
    }

范围查询

    @Test
    void testQuery12(){
        //范围查询 lt le gt ge between
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.between(User::getAge,10,20);
        userMapper.selectList(lqw);
    }

模糊查询

   @Test
    void testQuery13(){
        /**
         * 模糊查询 like
         * %:匹配任意字符
         * _:匹配任意单个字符
         * **********************
         * 官网中并没有看到匹配任意单个字符要怎么写,
         * 如果要匹配 刘亦菲、刘二菲、刘三菲...这样的名字可能还是要自己写sql (like 刘_菲)
         */
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        //lqw.like(User::getName,"o"); //名字里带o的   like %o%
        //lqw.likeRight(User::getName,"杨"); // like 杨%
        //lqw.likeLeft(User::getName,"at"); // like %at
        lqw.notLike(User::getName,"月"); //not like %月%
        userMapper.selectList(lqw);
    }

排序查询

1.

    @Test
    void testQuery14(){
        //排序查询
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        /**
         * 参数:condition isAsc columns
         * conidtion:条件,为true时 orderby 加入查询语句
         * isAsc:是否为升序
         * columns:按哪个列排序
         */
        //按年龄降序,年龄一样按id降序
        lqw.orderBy(true,false,User::getAge,User::getId);
        userMapper.selectList(lqw);
    }

 

 2.

    @Test
    void testQuery15(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        //按年龄降序,按id升序
        //注意这里哪个字段写在前面,它的优先级就更高
        lqw.orderByDesc(User::getAge);
        lqw.orderByAsc(User::getId);
        userMapper.selectList(lqw);
    }

字段映射和表名映射

问题1:表字段与编码属性设计不同步

实体类里面是pwd,数据库里面是password

解决:

问题2:编码中添加了数据库中未定义的属性

问题3:采用默认查询开放了更多的字段查看权限

pwd是敏感数据,如果进行查询,返回的json数据中携带了pwd,这是很危险的,因此我们需要在查询中将pwd默认隐藏

    @Test
    void testQuery16(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        userMapper.selectList(lqw);
    }

默认的查询是查不出来pwd的

    @Test
    void testQuery17(){
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
        lqw.select(User::getPwd);
        userMapper.selectList(lqw);
    }

指定要查pwd,这样是可以查出来的

问题4:表名与编码开发设计不同步

DML编程控制

id生成策略

MP通过@TableId控制id的生成策略

ctrl+鼠标左键,进入IdType源码

    /**
     * 数据库ID自增
     * <p>该类型请确保数据库设置了 ID自增 否则无效</p>
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
     */
    NONE(1),
    /**
     * 用户输入ID
     * <p>该类型可以通过自己注册自动填充插件进行填充</p>
     */
    INPUT(2),

    /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
     * 分配ID (主键类型为number或string),
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
     *
     * @since 3.3.0
     */
    ASSIGN_ID(3),
    /**
     * 分配UUID (主键类型为 string)
     * 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
     */
    ASSIGN_UUID(4),
    /**
     * @deprecated 3.3.0 please use {@link #ASSIGN_ID}
     */
    @Deprecated
    ID_WORKER(3),
    /**
     * @deprecated 3.3.0 please use {@link #ASSIGN_ID}
     */
    @Deprecated
    ID_WORKER_STR(3),
    /**
     * @deprecated 3.3.0 please use {@link #ASSIGN_UUID}
     */
    @Deprecated
    UUID(4);

AUTO(0)

id自增,同时数据库中要设置id自增

    @TableId(type = IdType.AUTO)
    private Long id;
ALTER TABLE tb_user  AUTO_INCREMENT=16;
    @Test
    void testInsert01(){
        User user = new User();
        user.setName("李薇");
        user.setPwd("12637489");
        user.setAge(50);
        user.setSex(1);
        user.setTel("959559999");
        user.setId(20L);
        userMapper.insert(user);
    }

指定它的id为20,insert到数据库的id仍是16

删除这条数据,重新insert一次

id为17,由此知道AUTO_INCREMENT不会因为数据的删除而回退
 

NONE(1)

该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)

    @TableId(type = IdType.NONE)
    private Long id;

执行两次 testInsert01 ,指定id为20,25,结果如下

 

INPUT(2)

需要自己输入id

    @TableId(type = IdType.INPUT)
    private Long id;

执行两次 testInsert01 ,指定id为30,35,结果如下


ASSIGN_ID(3)<——过时的ID_WORKER和ID_WORKER_STR合并

雪花算法生成id(可兼容数值型和字符串型)

    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

指定id为40,这样生成的id为40

    @Test
    void testInsert01(){
        User user = new User();
        user.setName("李薇");
        user.setPwd("12637489");
        user.setAge(50);
        user.setSex(1);
        user.setTel("959559999");
        user.setId(40L);
        userMapper.insert(user);
    }

不指定id,按照雪花算法生成id

//        user.setId(45L);

雪花算法生成的id的格式

 

ASSIGN_UUID(4)<——过时的UUID

以UUID生成算法作为id生成策略

需要表中主键的id为varchar(32),

因为生成的uuid为32位,形如b098c2c603a317326af11fe64371e916

    @TableId(type = IdType.ASSIGN_UUID)
    private String id;

执行1次添加

 

多数据删除

    @Test
    void testDelete01(){
        //根据id删除多条数据
        ArrayList<Long> list = new ArrayList<>();
        list.add(1827910579805876225L);
        list.add(40L);
        userMapper.deleteBatchIds(list);
    }

成功删除两条数据

逻辑删除

问题分析

实战演练 

1.数据库表中添加一个deleted字段,默认0

2.实体类添加属性
    //0表示正常值 1表示该数据已被逻辑删除
    @TableLogic(value = "0",delval = "1")
    private Integer deleted;
3.测试
3.1先进行一次查询所有,注意现在数据库里面的记录的deleted字段都是0

    @Test
    void testLogic(){
        userMapper.selectList(null);
    }

查出来所有的19条记录(注意这个sql,我们的代码中并没有显示地设定任何查询条件)

 3.2 删除第一条数据再进行一次查询所有
    @Test
    void testLogic(){
        userMapper.deleteById(1L);
        userMapper.selectList(null);
    }

 id为1的记录已被逻辑删除,这里只有18条记录

deleted字段为1,表示被逻辑删除了 

3.3 指定查询条件查一下

    @Test
    void testLogic02(){
        userMapper.selectById(1L);
    }

 他会自动把逻辑删除字段加到查询条件里

4.设置逻辑删除字段的另一种方式:在yml文件中配置
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志
  global-config:
    banner: off # 关闭MybatisPlus启动图标
    db-config:
      table-prefix: tb_ #表名前缀
      logic-delete-field: deleted #逻辑删除字段名
      logic-not-delete-value: 0 #正常值
      logic-delete-value: 1 #逻辑删除后的值

乐观锁

 实现思路

 0.添加拦截器
@Configuration
public class MybatisPlusConfig {
    /**
     * 添加分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());//添加乐观锁的拦截器
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
        // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
        return interceptor;
    }
}
1.表中添加字段version,用作版本控制

给默认值为1,长度为int的最大值11位

 2.实体类中添加属性
    @Version
    private Integer version;
3.测试
测试1

起始数据

执行更新语句

    @Test
    void testLock01(){
        User user = new User();
        user.setId(1L);
        user.setName("tomcat");
        userMapper.updateById(user);
    }

 发现version并没有改变,这是因为我们并没有拿到过version

重新执行如下语句

    @Test
    void testLock02(){
        User user = userMapper.selectById(1L);
        user.setName("tomkitty");
        userMapper.updateById(user);
    }

 我们先查询id为1的用户拿到了该用户的实体(当然包括它的version)这样一来更新就会自动校验version

 测试2

模拟两个请求对同一个记录进行修改 

    void testLock03(){
        User user1 = userMapper.selectById(2L);
        User user2 = userMapper.selectById(2L);

        user1.setName("JerryA");
        user2.setName("JerryB");


        userMapper.updateById(user2);//version被修改成2
        userMapper.updateById(user1);//它还以为是version=1 结果和数据库的version对不上 修改失败
    }

最终结果

代码生成器

1.新创建一个springboot项目

引入mysql lombok,web(会生成controller) 即可

2.在pom文件中加入代码生成器需要的依赖

        <!--代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>
        <!--velocity模板引擎-->
        <dependency>
         <groupId>org.apache.velocity</groupId>
         <artifactId>velocity-engine-core</artifactId>
         <version>2.3</version>
        </dependency>

3.创建代码生成类

package com.example;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;

public class CodeGenerator {
    public static void main(String[] args) {
        //1.获取代码生成器的对象
        AutoGenerator autoGenerator = new AutoGenerator();


        //2.设置数据库相关配置
        DataSourceConfig ds = new DataSourceConfig();
        ds.setDriverName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
        ds.setUsername("root");
        ds.setPassword("root");

        autoGenerator.setDataSource(ds);


        //3.设置全局配置
        GlobalConfig gc = new GlobalConfig();
        //代码生成位置(默认D盘的根目录) user.dir=D:/java_workspace
        gc.setOutputDir("D:/java_workspace/MybatisPlusProject02/src/main/java/com/example");
        //设置生成完毕后是否打开生成代码所在的目录(默认打开)
        gc.setOpen(false);
        gc.setAuthor("chen");//设置作者
        gc.setFileOverride(true);//是否覆盖原来生成的文件
        gc.setMapperName("%sMapper"); //设置名字格式为为 UserMapper,AccountMapper.....
        gc.setIdType(IdType.ASSIGN_ID); //设置id生成策略

        autoGenerator.setGlobalConfig(gc);


        //4.设置包名相关配置
        PackageConfig pc = new PackageConfig();
        //pc.setParent("com.baomidou");
        pc.setParent("mpGenerator");//设置生成的包名
        pc.setEntity("entity");//设置实体类包名
        pc.setMapper("mapper");//设置mapper接口文件夹的名字

        autoGenerator.setPackageInfo(pc);


        //5.策略设置
        StrategyConfig sc = new StrategyConfig();
        sc.setInclude("tb_user");//设置参与代码生成的表
        sc.setTablePrefix("tb_");//设置数据库表前缀,这样它生成的类前面就不会带Tb_了
        sc.setRestControllerStyle(true);//设置控制器为rest风格的控制器
        sc.setVersionFieldName("version");//设置乐观锁字段名
        sc.setLogicDeleteFieldName("deleted");//设置逻辑删除字段名,仍然需要自己指定逻辑删除的值
        sc.setEntityLombokModel(true);//启用lombok

        autoGenerator.setStrategy(sc);


        //6.执行代码生成
        autoGenerator.execute();

    }
}

4.运行代码生成类中的main方法,查看结果

开启Mybatisplus日志

yml文件里面配置

#查看MP执行的sql语句
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

 关闭Spring初始化日志,SpringBoot图标,MybatisPlus图标

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值