官方中文文档—->mybatisPlus

mybatisPlus简介

简介

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

愿景 我们的愿景是成为 MyBatis 最好的搭档,就像魂斗罗中的 1P、2P,基友搭配,效率翻倍

image.png

特征

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

    框架结构

    image.png

    入门案例

    导入依赖

    1. <!--导入mybatis-plus场景启动器-->
    2. <dependency>
    3. <groupId>com.baomidou</groupId>
    4. <artifactId>mybatis-plus-boot-starter</artifactId>
    5. <version>3.5.1</version>
    6. </dependency>
    7. <!--导入lombok依赖-->
    8. <dependency>
    9. <groupId>org.projectlombok</groupId>
    10. <artifactId>lombok</artifactId>
    11. <optional>true</optional>
    12. </dependency>
    13. <!--导入mysql驱动依赖-->
    14. <dependency>
    15. <groupId>mysql</groupId>
    16. <artifactId>mysql-connector-java</artifactId>
    17. <scope>runtime</scope>
    18. </dependency>
    19. <!--导入德鲁伊连接池-->
    20. <dependency>
    21. <groupId>com.alibaba</groupId>
    22. <artifactId>druid-spring-boot-starter</artifactId>
    23. <version>1.1.17</version>
    24. </dependency>

    配置文件

    spring:
    datasource:
      type: com.alibaba.druid.pool.DruidDataSource
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://ip号/mybatis_plus?characterEncoding=utf-8&useSSL=false
      username: root
      password: 12345ssdlh
    mybatis-plus:
    configuration:
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
      #配置日志,可在控制台打印出sql语句
    

    实体类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
      private Long id;
      private String name;
      private Integer age;
      private String email;
    }
    

    mapper接口

    import com.atguigu.mybatisplus_01.pojo.User;
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import org.apache.ibatis.annotations.Mapper;
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
    }
    

    测试

    @Autowired
    private UserMapper userMapper;
    

    查询所有数据

    @Test
    void contextLoads() {
      //通过条件构造器查询一个list集合,若没有条件,则可以设置null为参数
      List<User> users = userMapper.selectList(null);
      users.forEach(System.out::println);
    }
    

    其他功能

    /*插入数据*/
    @Test
    void testInsert(){
      User user=new User(null,"斩杀",56,"@zhansha.com");
      int i = userMapper.insert(user);
      System.out.println(i+"行数据已插入,id值为"+user.getId());
      //其中插入数据的主键id值会通过雪花算法生成,并可以直接赋给此实体类
    }
    /*根据id删除数据*/
    @Test
    void testDeleteById(){
      int i = userMapper.deleteById(1509518991436038145L);
      //加L表示为long类型
      System.out.println(i+"行数据已删除");
    }
    /*根据其他属性设置map集合删除数据*/
    @Test
    void testDeleteByMap(){
      Map<String,Object> map=new HashMap<>();
      map.put("age",24);
      map.put("email","test5@baomidou.com");
      //可添加多个删除条件,必须满足所有条件的数据才能被删除
      int i = userMapper.deleteByMap(map);
      System.out.println(i+"行数据已删除");
    }
    /*根据id批量删除数据*/
    @Test
    void testDeleteBatchIds(){
      List<Long> list = Arrays.asList(3L, 4L);
      int i = userMapper.deleteBatchIds(list);
      System.out.println(i+"行数据已删除");
    }
    /*根据id修改数据*/
    @Test
    void testUpdate(){
      User user=new User();
      user.setId(1L);
      user.setAge(15);
      user.setEmail("123@weichat.com");
      int i = userMapper.updateById(user);
      System.out.println(i+"行数据已修改");
    }
    /*根据id查询*/
    @Test
    void testSelect(){
      User user = userMapper.selectById(1L);
      System.out.println(user.toString());
    }
    /*根据id批量查询数据*/
    @Test
    void testSelectBatchIds(){
      List<Long> list = Arrays.asList(1L, 2L, 3L, 6L);
      List<User> users = userMapper.selectBatchIds(list);
      System.out.println(users.toString());
    }
    /*根据其他属性设置map集合查询数据*/
    @Test
    void testSelectByMap(){
      Map<String,Object> map=new HashMap<>();
      map.put("name","Jack");
      map.put("email","test2@baomidou.com");
      //可添加多个查询条件,必须满足所有条件的数据才能查询到
      List<User> users = userMapper.selectByMap(map);
      users.forEach(System.out::println);
    }
    

    自定义sql

  • 也可以自定义sql进行查询,可通过创建mapper映射文件来编写自定义sql;

  • image.png
  • 可以在配置文件中配置mapper映射文件的位置,也可以不用配置采用默认路径;
  • 写法同mabatis;

    通用Service

  • 通用Service CRUD封装IService(opens new window)接口,进一步封装CRUD采用get查询单行remove删除list查询集合page分页前缀命名方式区分Mapper层避免混淆;

  • 泛型T为任意实体对象;
  • 建议如果存在自定义通用Service方法的可能,请创建自己的IBaseService继承Mybatis-Plus提供的接口IService及其实现类ServiceImpl
  • 对象Wrapper为条件构造器;

    public interface IService<T> {...
      //泛型T通常填自己创建的实体类
    
    public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {...
      //泛型T通常填自己创建的实体类,泛型M通常填自己创建的mapper映射文件
    

    操作

    自定义UserService接口

    public interface UserService extends IService<User> {}
    

    自定义UserServiceImpl接口实现类

    @Service
    public class UserServiceImpl 
      extends ServiceImpl<UserMapper, User> implements UserService {}
    

    测试

    @Autowired
    private UserService us;
    @Test//查询总记录数
    void test(){
      long l = us.count();
      System.out.println(l+"条数据");
    }
    @Test//批量添加数据
    void testInsertMore(){
      List<User> list=new ArrayList<>();
      for(int i=1;i<=10;i++){
          User user=new User();
          user.setName("钢铁侠0"+i+"型");
          user.setAge(i*i);
          user.setEmail(i*i*i+"@qq.com");
          list.add(user);
      }
      boolean b = us.saveBatch(list);
      System.out.println("插入成功与否?"+b);
    }
    

    MybatisPlus常用注解

    @TableName

  • 实体类名和数据库表名必须保持一致,如果不一致,就需要使用@TableName注解;

    单独设置

    @TableName("t_user")
    public class User {...
    //User为实体类名,t_user为数据库表名
    

    批量设置

    如果所有的数据库表名对比实体类名都有默认的前缀,亦可以在配置文件中进行全局配置数据库表统一前缀,如下:

    mybatis-plus:
    global-config:
      db-config:
        table-prefix: _t
        #例如:User为实体类名,t_user为数据库表名
    

    @TableId

    主键名不是id

    mybaitsplus中主键名必须叫id,如果不是就需要用到@TableId注解进行标识;
    【指的是数据库表的主键名不是id,不是数据库表名和实体类属性名和数据库表列名不一样】

    public class User {
      @TableId//将属性所对应的字段指定为主键
      private Long uid;//uid为主键名
      ...
    

    @TableId的value属性

    数据表主键名与实体类属性名不一致

    public class User {
      @TableId(value = "id")//id为主键名
      //@TableId的value属性用于指定主键的字段
      private Long uid;
    

    @TableId的type属性

  • type属性表示当前主键生成策略,默认是雪花算法;

  • 只有不手动设置主键值时才会用到主键生成策略,如果自己手动设置了主键值就使用手动设置的值;

    常用的主键策略:

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

如果我们不想用雪花算法,想用mysql的自增策略,需要做以下操作:

  1. 要将mysql数据库里的表结构改为自增;
  2. 在主键上标识@TableId注解并在写入type属性值,如下:
    public class User {
     @TableId(value = "id",type = IdType.AUTO)
     private Long uid;
     ...
    

    统一设置主键生成策略

    mybatis-plus:
    global-config:
     db-config:
       id-type: auto #统一设置主键生成策略为自增策略
    

    雪花算法

    背景
    需要选择合适的方案去应对数据规模的增长,以应对逐渐增长的访问压力和数据量。数据库的扩展方式主要包括:业务分库、主从复制,数据库分表。
    数据库分表
    将不同业务数据分散存储到不同的数据库服务器,能够支撑百万甚至千万用户规模的业务,但如果业务继续发展,同一业务的单表数据也会达到单台数据库服务器的处理瓶颈。例如,淘宝的几亿用户数据,如果全部存放在一台数据库服务器的一张表中,肯定是无法满足性能要求的,此时就需要对单表数据进行拆分。
    单表数据拆分有两种方式:垂直分表和水平分表。示意图如下:
    image.png
    垂直分表
    垂直分表适合将表中某些不常用且占了大量空间的列拆分出去。
    例如,我们是一个婚恋网站,用户在筛选其他用户的时候,主要是用age和sex两个字段进行查询,而nickname和description两个字段主要用于展示,一般不会在业务查询中用到。description本身又比较长,因此我们可将这两个字段独立到另一张表中,这样在查询age和sex时,就能带来一定性能提升。
    水平分表
    水平分表适合表行数特别大的表,有的公司要求单表行数超过5000万就必须进行分表,这个数字可以作为参考,但并不是绝对标准,关键还是要看表的访问性能。对于一些比较复杂的表,可能超过1000万就要分表了;而对于一些简单的表,即使存储数据超过1亿行,也可以不分表。但不管怎样,当看到表的数据量达到千万级别时,作为架构师就要警觉起来,因为这很可能是架构的性能瓶颈或者隐患。水平分表相比垂直分表,会引入更多的复杂性,例如要求全局唯一的数据id该如何处理;
    雪花算法
    雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
    image.png

    @TableField

    如果数据库表中的非主键名与实体类属性名不一致时,就需要用到@TableField注解;
    public class User {
     @TableId(value = "id",type = IdType.AUTO)
     private Long uid;
     @TableField(value = "user_name")
     private String PeopleName;
     ....//数据库表列名为user_name
    
    注:如果数据库表的列名和实体类属性名符合驼峰命名规则,则无需加@TableField注解,因为mybatis-plus默认开启驼峰命名;

    @TableLogic

    @TableLogic注解用来标识逻辑删除属性字段
  • 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据;
  • 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录;

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

      使用

      首先在数据库表中添加一个字段用来作为删除标识字段,int类型,默认值设置为0
      image.png
      在实体类中的该属性上面标识@TableLogic注解:
      @TableLogic
      private int isDelete;
      
      此时再次用mybatis-plus查询所有数据时已经查不到该数据时已经查不到了,因为此时默认将删除操作编译为了修改操作:
      image.png
      将查询操作编译加上了判断is_delete条件:
      image.png

      条件构造器wapper和常用接口

      wapper介绍

      条件构造器wapper就是用来封装条件的,因为执行增删改查等等操作需要一些条件;
      image.png
      Wrapper:条件构造抽象类,最顶端父类;
  • AbstractWrapper:用于查询条件封装,生成sql的where条件;

    • QueryWrapper:查询条件封装,删除操作也用和查询相同的QueryWrapper,修改也可以使用QueryWrapper;
    • UpdateWrapper:Update条件封装(除了可以设置筛选条件,还可以设置修改的数据)
    • AbstractLambdaWrapper:使用Lambda语法;
      • LambdaQueryWrapper:用于Lambda语法使用的查询Wrapper;
      • LambdaUpdateWrapper:Lambda更新封装Wrapper;

        QueryWrapper

        组装查询条件

        @Autowired
        private UserMapper userMapper;
        @Test
        void test01(){
        //查询用户名包含0,年龄在5到20之间,邮箱信息不为null的用户信息;
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.like("user_name","0")
            .between("age",5,20)
            .isNotNull("email");
        List<User> list = userMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
        }
        
        控制台编译的sql:

        ==> Preparing: SELECT id AS uid,user_name AS PeopleName,age,email,is_delete FROM t_user WHERE is_delete=0 AND (user_name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL) ==> Parameters: %0%(String), 5(Integer), 20(Integer)

组装排序条件

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

控制台编译的sql:

==> Preparing: SELECT id AS uid,user_name AS PeopleName,age,email,is_delete FROM t_user WHERE is_delete=0 ORDER BY age DESC,id ASC ==> Parameters:

组装删除条件

@Test
void test03(){
    //删除邮箱中包含xxx字段的用户信息;
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    queryWrapper.like("email","xxx");
    int i = userMapper.delete(queryWrapper);
    System.out.println(i+"条数据被删除");
}

控制台编译的sql:

==> Preparing: UPDATE t_user SET is_delete=1 WHERE is_delete=0 AND (email LIKE ?) ==> Parameters: %xxx%(String)

组装修改条件

@Test
void test04(){
    //将(年龄大于20并且用户名中包含a)或邮箱为null的用户信息
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    queryWrapper.gt("age",20)
            .like("user_name","a")
            .or()//默认是and连接,如需要or连接的话用”.or()“方法
            .isNull("email");
    User user=new User();
    user.setEmail("test@guigu.com");
    user.setPeopleName("雄安命");
    int i = userMapper.update(user, queryWrapper);
    //当实体类属性值不为null时才进行修改;
    System.out.println(i+"条数据已修改");
}

控制台编译的sql:

==> Preparing: UPDATE t_user SET email=? WHERE is_delete=0 AND (age > ? AND user_name LIKE ? OR email IS NULL) ==> Parameters: test@guigu.com(String), 20(Integer), %a%(String)

条件的优先级

@Test
void test05(){
    //将用户名中包含0并且(年龄大于20或邮箱为null)的用户信息进行修改
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    queryWrapper.like("user_name","0")
            .and(x->x.gt("age",20).or().isNull("email"));
    //lambda表达式中的属性优先执行
    //x形参代表条件构造器
    User user=new User();
    user.setEmail("test@guigu.com");
    user.setPeopleName("小航");
    int i = userMapper.update(user, queryWrapper);
    System.out.println(i+"条数据已修改");
}

控制台编译的sql:

==> Preparing: UPDATE t_user SET user_name=?, email=? WHERE is_delete=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL)) ==> Parameters: 小航(String), test@guigu.com(String), %0%(String), 20(Integer)

观察and()方法源码可以发现,形参Param其实就是条件构造器,这里用x来代表条件构造器;


/**
 * 查询条件封装
 * <p>嵌套</p>
 * <li>泛型 Param 是具体需要运行函数的类(也是 wrapper 的子类)</li>
 *
 * @author hubin miemie HCL
 * @since 2017-05-26
 */
public interface Nested<Param, Children> extends Serializable {

    /**
     * ignore
     */
    default Children and(Consumer<Param> consumer) {

lambda表达式复习

Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代吗(将代像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。

1、举例:

**(o1,o2)->Integer.compare(o1,o2);**

2、格式:

**->**:lambda表达式的操作符或者箭头操作符
**->**左边:指的是**(o1,o2)**,是lambda表达式的形参列表
**->**右边:指的是**Integer.compare(o1,o2);**,是lambda表达式的方法体

3、使用(分6种情况):
Consumer<String> consumer1=new Consumer<String>() {
    @Override
    public void accept(String s) {

    }
};
Comparator<Integer> comparator=new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return 0;
    }
};

①无参无返回值

格式:**()->{方法体}**

②一个参数无返回值

格式:**(类型 参数)->{方法体}**
例如:Consumer<String> consumer=(String s)->{System._out_.println(s);};

③“类型推断”

如果数据类型可以推断出,类型就可以省略,因为可由编译器推断得出,称为“类型推断”;
格式:**(参数名)->{方法体}**
例如:Consumer<String> consumer=(s)->{System._out_.println(s);};

④一个参数时,参数小括号可省略

格式:**参数名->{方法体}**
例如:Consumer<String> consumer=s->{System._out_.println(s);};

⑤两个及以上参数,多条执行语句,有返回值

格式:**(类型1 参数1,类型2 参数3...)->{执行语句1;执行语句2...;return...;}**
例如:

Comparator<Integer> comparator=(o1,o2)->{
            System.out.println(o1);
            System.out.println(o2);
            return o1.compareTo(o2);
};

⑥lambda体只有一条语句时,return和大括号都可以省略

格式:**(类型1 参数1,类型2 参数3...)->返回结果**
例如:Comparator<Integer> comparator=(o1,o2)->o1.compareTo(o2);

4、lambda表达式的本质

作为接口(只有一个抽象方法,即函数式接口)的实例;

5、总结

**->左边**lambda形参列表的参数类型可以省略(类型推断),如果Lambda形参列表只有一个参数,其一对**()**也可以省略;
**->右边**lambda体应该使用—对**{}**包裹;如果lambda体只有一条执行语句(可能是return语句),可以省略这一对**{}**return关键字;

组装select查询语句

@Test
void test06(){
    //仅查询用户的用户名和邮箱信息
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    queryWrapper.select("user_name","email");
    List<Map<String, Object>> list = userMapper.selectMaps(queryWrapper);
    list.forEach(System.out::println);
}

控制台编译的sql:

==> Preparing: SELECT user_name,email FROM t_user WHERE is_delete=0 ==> Parameters:

组装子查询

@Test
void test07(){
    //用子查询来实现“查询id<=100的用户信息”的功能,如下
    /*SELECT * FROM t_user WHERE id IN(
            SELECT id FROM t_user WHERE id<=100
            );*/
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    queryWrapper.inSql("id","SELECT id FROM t_user WHERE id<=100");
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

控制台编译的sql:

==> Preparing: SELECT id AS uid,user_name AS PeopleName,age,email,is_delete FROM t_user WHERE is_delete=0 AND (id IN (SELECT id FROM t_user WHERE id<=100)) ==> Parameters:

UpdateWrapper

@Test
void test08(){
    UpdateWrapper<User> updateWrapper=new UpdateWrapper<>();
    //将用户名中包含0并且(年龄大于3或邮箱为null)的用户信息进行修改
    updateWrapper.like("user_name","0")
            .and(i->i.gt("age",3).or().isNull("email"));
    //.gt();大于
    updateWrapper.set("user_name","还望").set("email","ZUA.com");
    int i = userMapper.update(null, updateWrapper);
    System.out.println(i+"条数据已修改");
}

控制台编译的sql:

==> Preparing: UPDATE t_user SET user_name=?,email=? WHERE is_delete=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL)) ==> Parameters: 还望(String), ZUA.com(String), %0%(String), 3(Integer)

模拟开发中组装条件

要求:根据条件进行查询(根据用户名模糊查询和年龄区间筛选),如果为空为null为空白符则不筛选该条件;

传统方法

@Test
void test09(){
    String username="";
    Integer ageBegin=20;
    Integer ageEnd=30;
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    if(StringUtils.isNotBlank(username)){
        //import org.junit.platform.commons.util.StringUtils;
        //isNotBlank判段某个字段是否不为空字符串,不为null,不为空白符;
        queryWrapper.like("user_name",username);
    }
    if(ageBegin!=null){
        queryWrapper.ge("age",ageBegin);
        //.ge();大于等于
    }
    if(ageEnd!=null){
        queryWrapper.le("age",ageEnd);
    }
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

控制台编译的sql:

==> Preparing: SELECT id AS uid,user_name AS PeopleName,age,email,is_delete FROM t_user WHERE is_delete=0 AND (age >= ? AND age <= ?) ==> Parameters: 20(Integer), 30(Integer)

简单写法-condition组装条件

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

控制台编译的sql:

==> Preparing: SELECT id AS uid,user_name AS PeopleName,age,email,is_delete FROM t_user WHERE is_delete=0 AND (age >= ? AND age <= ?) ==> Parameters: 20(Integer), 30(Integer)

LambdaQueryWrapper

@Test
void test11(){
//根据条件进行查询(根据用户名模糊查询和年龄区间筛选),如果为空为null为空白符则不筛选该条件
    String username="";
    Integer ageBegin=20;
    Integer ageEnd=30;
    LambdaQueryWrapper<User> queryWrapper=new LambdaQueryWrapper<>();
    queryWrapper.like(StringUtils.isNotBlank(username),User::getPeopleName,username)
                .ge(ageBegin!=null,User::getAge,ageBegin)
                .le(ageEnd!=null,User::getAge,ageEnd);
    List<User> list = userMapper.selectList(queryWrapper);
    list.forEach(System.out::println);
}

控制台编译的sql:

==> Preparing: SELECT id AS uid,user_name AS PeopleName,age,email,is_delete FROM t_user WHERE is_delete=0 AND (age >= ? AND age <= ?) ==> Parameters: 20(Integer), 30(Integer)

LambdaUpdateWrapper

@Test
void test12(){
    //将用户名中包含0并且(年龄大于3或邮箱为null)的用户信息进行修改
    LambdaUpdateWrapper<User> updateWrapper=new LambdaUpdateWrapper<>();
    updateWrapper.like(User::getPeopleName,"a")
            .and(i->i.gt(User::getAge,3).or().isNull(User::getEmail));
    updateWrapper.set(User::getPeopleName,"大黄").set(User::getEmail,"email.com");
    int i = userMapper.update(null, updateWrapper);
    System.out.println(i+"条数据已修改");
}

控制台编译的sql:

==> Preparing: UPDATE t_user SET user_name=?,email=? WHERE is_delete=0 AND (user_name LIKE ? AND (age > ? OR email IS NULL)) ==> Parameters: 大黄(String), email.com(String), %a%(String), 3(Integer)

插件

1、分页插件

MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能,如下:

添加配置类

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

测试

@Autowired
UserMapper userMapper;
@Test
public void testPage01(){
    //分查询所有数据,1是显示条目的起始索引(从1开始),每次查3条
    Page<User> page=new Page<>(1,3);
    userMapper.selectPage(page, null);
    System.out.println("当前查询数据:"+page.getRecords());
    System.out.println("当前页码:"+page.getCurrent()+";每页显示条数;"+page.getSize());
    System.out.println("总页数:"+page.getPages());
    System.out.println("总记录数:"+page.getTotal());
    System.out.println("是否有上一页:"+page.hasPrevious());
    System.out.println("是否有下一页:"+page.hasNext());
}

控制台编译的sql:

==> Preparing: SELECT id AS uid,user_name AS PeopleName,age,email,is_delete FROM t_user WHERE is_delete=0 LIMIT ? ==> Parameters: 3(Long)

【因为mybatis-plus中分页的起始索引从1开始,故当起始索引=1时,分页起始索引可以省略】

自定义分页功能

即:自定义我们的查询语句,在自己写的sql语句中通过分页插件来实现分页功能;

mapper接口

@Mapper
public interface UserMapper extends BaseMapper<User> {
    /*通过年龄查询用户信息并分页
    *page:MyBatisPlus提供的分页对象,必须位于mapper接口中第一个参数的位置*/
    Page<User> selectPageVo(@Param("page")Page<User> page,@Param("age")Integer age);
}

mapper映射文件

<select id="selectPageVo" resultType="User">
    select id as uid,user_name as PeopleName,age,email from t_user where age > #{age}
</select>

注意:如果resultType不想写全类名,可以直接在配置文件中配置类型别名所对应的包:

mybatis-plus:
  type-aliases-package: com.atguigu.mybatisplus_01.pojo
  #配置类型别名所对应的包(默认别名为表名且不区分大小写)

测试

@Test
public void testPage02(){
    Page<User> page=new Page<>(1,3);
    userMapper.selectPageVo(page, 3);
    System.out.println("当前查询数据:"+page.getRecords());
    System.out.println("当前页码:"+page.getCurrent()+";每页显示条数;"+page.getSize());
    System.out.println("总页数:"+page.getPages());
    System.out.println("总记录数:"+page.getTotal());
    System.out.println("是否有上一页:"+page.hasPrevious());
    System.out.println("是否有下一页:"+page.hasNext());
}

==> Preparing: select id as uid,user_name as PeopleName,age,email from t_user where age > ? LIMIT ? ==> Parameters: 3(Integer), 3(Long)

注意:由于是自定义的sql语句,因此查到了已经逻辑删除了的数据;

2、乐观锁

小李和小王同时操作商品后台系统。

乐观锁与悲观锁

  • 上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出被修改后的价格。
  • 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作。

    乐观锁实现流程

  • 数据库中添加version字段;

  • 取出记录时,获取当前version;
  • 更新时,version+1,如果where语句中的version版本不对,则更新失败;

    Mybatis-Plus实现乐观锁

    实体类

    在实体类中表示版本号的字段上面添加@Version注解,如下:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Product {
      private Long id;
      private String name;
      private Integer price;
      @Version//表示乐观锁版本号字段
      private Integer version;
    }
    

    配置类

    配置类中添加乐观锁组件,如下:

    @Configuration
    public class MyBatisPlusConfig {
      @Bean
      public MybatisPlusInterceptor mybatisPlusInterceptor(){
          MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor();
          interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
          //添加分页插件
          interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
          //添加乐观锁插件
          return interceptor;
      }
    }
    

    通用枚举

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

    数据库中插入枚举类型

    枚举类

    @AllArgsConstructor
    @Getter
    public enum SexEnum {
      MALE(1,"男"),
      FEMALE(0,"女");
      @EnumValue//在枚举类型中要填入数据库的属性上面标注此注解
      private Integer sex;
      private String sexName;
    }
    

    实体类

    @TableName("t_user")
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
      @TableId(value = "id",type = IdType.AUTO)
      private Long uid;
      @TableField(value = "user_name")
      private String PeopleName;
      private Integer age;
      private String email;
      private SexEnum sex;
      @TableLogic
      private int isDelete;
    }
    

    application.yaml

    mybatis-plus:
    #扫描通用枚举的包
    type-enums-package: com.atguigu.mybatisplus_01.enums
    

    测试

    @Autowired
    UserMapper userMapper;
    @Test
    void test01(){
      User user=new User();
      user.setPeopleName("张十年");
      user.setAge(16);
      user.setSex(SexEnum.FEMALE);
      int i = userMapper.insert(user);
      System.out.println(i+"条数据插入了");
    }
    

    mybatis-plus代码生成器

    简介

    mybatis-plus代码生成器mybatis逆向工程的区别:

  • mybatis逆向工程:通过表逆向生成实体类mapper接口mapper映射文件

  • mybatis-plus代码生成器:通过表生成控制层业务层持久层mapper接口mapper映射文件

    引入依赖

    <!--导入mybatis-plus代码生成器-->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-generator</artifactId>
      <version>3.5.1</version>
    </dependency>
    <!--导入freemarker依赖
         代码生成过程中会使用到freemarker引擎模板-->
    <dependency>
      <groupId>org.freemarker</groupId>
      <artifactId>freemarker</artifactId>
      <version>2.3.31</version>
    </dependency>
    

    快速生成程序

    ```java import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.Collections;

public class xxxxx { public static void main(String[] args) { FastAutoGenerator.create( “jdbc:mysql://124.70.84.192:3306/mybatisplus?characterEncoding=utf-8&userSSL=false”, //数据库链接地址 “root”, //用户名 “12345ssdlh”)//密码 .globalConfig(builder -> { builder.author(“atguigu”) //设置作者 //.enableSwagger() //开启swagger模式 .fileOverride() //覆盖已生成文件 .outputDir(“E:\Pictures\mysql”); //指定生成文件的输出目录 }) .packageConfig(builder -> { builder.parent(“com.atguigu”) //设置父包名 .moduleName(“mybatisplus”) //设置父包模块名 .pathInfo(Collections.singletonMap( OutputFile.mapperXml, “E:\Pictures\mysql”)); //指定mapperXml生成路径 }) .strategyConfig(builder -> { builder.addInclude(“t_user”) //设置需要生成的表名 .addTablePrefix(“t“, “c_”);//设置过滤表前缀 }) .templateEngine(new FreemarkerTemplateEngine()) //使用Freemarker引擎模板,默认的是Velocity引擎模板 .execute(); } }

执行之后的结果如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/23158036/1648897122559-9af579b5-6b16-42f2-aa1e-1adcf69b5353.png#clientId=u334db8eb-dc49-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=400&id=ua5e86bbb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=450&originWidth=576&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35161&status=done&style=none&taskId=u775bcc75-20f0-4436-8b96-1d7a25a9871&title=&width=512)
<a name="Pao7W"></a>
# 多数据源

- 适用于多种场景:纯粹多库、 读写分离、 一主多从、 混合模式等 
- 场景说明:我们创建两个库,每个库一张表,通过一个测试用例分别获取用户数据(1个库)与商品数据(另1个库),如果获取到说明多库模拟成功;
<a name="em9Hl"></a>
## 引入依赖
```xml
<!--导入多数据源依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.0</version>
</dependency>

配置文件配置多数据源

spring:
  #配置数据源信息
  datasource:
    dynamic:
      #设置默认的数据源或者数据源组,默认值即为master
      primary: master
      #严格匹配数据源(默认false)
        #为true时未匹配到指定数据源抛异常,
        #为false时未匹配到指定数据源时使用默认数据源
      strict: false
      datasource:
        master:
          url: jdbc:mysql://124.70.84.192:3306/mybatis_plus
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 12345ssdlh
        slave_1:
          url: jdbc:mysql://124.70.84.192:3306/mybatis_plus_1
          driver-class-name: com.mysql.cj.jdbc.Driver
          username: root
          password: 12345ssdlh

创建User相关

实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("t_user")
public class User {
    @TableId
    private Integer id;
    private String userName;
    private Integer age;
    private Integer sex;
    private String email;
    @TableLogic
    @TableField("is_delete")
    private Integer isDeleted;
}

mapper

@Mapper
public interface UserMapper extends BaseMapper<User> {}

service

import com.atguigu.Bean.User;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {}
import com.atguigu.Bean.User;
import com.atguigu.mapper.UserMapper;
import com.atguigu.service.UserService;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
@DS("master")
public class UserServiceImpl 
    extends ServiceImpl<UserMapper, User> 
    implements UserService {}

创建Product相关

实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    private Integer id;
    private String name;
    private Integer price;
    @Version
    private Integer version;
}

mapper

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

service

import com.atguigu.Bean.Product;
import com.baomidou.mybatisplus.extension.service.IService;
public interface ProductService extends IService<Product> {}
import com.atguigu.Bean.Product;
import com.atguigu.mapper.ProductMapper;
import com.atguigu.service.ProductService;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
@DS("slave_1")
public class ProductServiceImpl 
    extends ServiceImpl<ProductMapper, Product> 
    implements ProductService {}

测试

@Autowired
UserService userService;
@Autowired
ProductService productService;
@Test
void test01(){
    System.out.println(userService.getById(1));
    System.out.println(productService.getById(1));}

结果:
1、都能顺利获取对象,则测试成功;
2、如果我们实现读写分离,将写操作方法加上主库数据源,读操作方法加上从库数据源,自动切
换,是不是就能实现读写分离?
**@DS**可以注解在类上或方法上;

MyBatisX插件

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

MyBatisX插件用法:—->>官方介绍
IDEA安装MyBatisX插件如下:
image.png

mybatisX快速生成mapper,service,bean等

image.png

mybatisX快速生成CRUD

添加

image.png
之后就可直接生成mapper接口方法和mapper映射文件中的sql语句;

删除

image.png
之后同上alt+enter进行选择,之后就可直接生成mapper接口方法和mapper映射文件中的sql语句;

修改

image.png
之后同上alt+enter进行选择,之后就可直接生成mapper接口方法和mapper映射文件中的sql语句;

查询

image.png
之后同上alt+enter进行选择,之后就可直接生成mapper接口方法和mapper映射文件中的sql语句;