超详细超全面的MyBatisPlus的所有使用方法

【MyBatis Plus】

配套代码:https://gitee.com/lvshuichangliu/mybatis-plus

一、MyBatisPlus简介

1.1 MyBatist 概述

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

100.png

MyBatis Plus官网:https://baomidou.com/

1.2 MyBatis Plus 的特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 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.3 MyBatis Plus 支持的数据库

任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。

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

1.4 MyBatis Plus 的核心架构

99.png

二、MyBatis Plus快速入门

2.1 创建测试表

DROP TABLE IF EXISTS user;

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `sex` char(1) DEFAULT NULL COMMENT '性别 0:男 1:女',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `birthday` date DEFAULT NULL COMMENT '生日',
  PRIMARY KEY (`id`)
);


INSERT INTO `user` VALUES (1, 'Jone', '1', 27, '2001-10-04');
INSERT INTO `user` VALUES (2, 'Jack', '0', 20, '1999-10-04');
INSERT INTO `user` VALUES (3, 'Tom', '1', 28, '1996-08-12');
INSERT INTO `user` VALUES (4, 'Sandy', '1', 21, '2001-10-04');
INSERT INTO `user` VALUES (5, 'Billie', '0', 24, '1992-09-07');
INSERT INTO `user` VALUES (6, 'Jackson', '0', 18, '1996-08-12');
INSERT INTO `user` VALUES (7, 'Hardy', '1', 25, '1992-09-07');
INSERT INTO `user` VALUES (8, 'Rose', '1', 21, '1992-09-07');
INSERT INTO `user` VALUES (9, 'June', '0', 28, '1992-09-07');
INSERT INTO `user` VALUES (10, 'Aidan', '0', 17, '2001-10-04');

2.2 搭建项目

2.2.1 引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dfbz</groupId>
    <artifactId>01_MyBatisPlus</artifactId>
    <version>1.0-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.3</version>
        <relativePath/>
    </parent>

    <dependencies>
        <!--springboot依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--测试场景-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--mybatis plus场景-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>

        <!--测试单元-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

2.2.2 实体类

package com.dfbz.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Long id;
    private String name;
    private String sex;
    private Integer age;
    private String birthday;
}

2.2.3 Mapper接口

package com.dfbz.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dfbz.entity.User;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {

}

2.2.4 application.yml

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus?serverTimezone=GMT%2b8
    username: root
    password: admin
#配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2.2.5 启动类

package com.dfbz;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootApplication
@MapperScan("com.dfbz.mapper")
public class MyBatisPlusApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBatisPlusApplication.class, args);
    }
}

2.2.6 测试类

package com.dfbz;

import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo01 {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelect() {
        // 传递null代表查询全部
        List<User> userList = userMapper.selectList(null);
        for (User user : userList) {
            System.out.println(user);
        }
    }
}

三、MyBatis Plus的使用

3.1 BaseMapper的基本CURD

在MyBatis Plus中,我们编写的Mapper接口都继承与MyBatis Plus提供的BaseMapper接口,BaseMapper接口包含了MyBatis Plus帮我们提供操作数据库的一系列方法;

官网案例:https://baomidou.com/pages/49cc81/#mapper-crud-接口

  • 使用示例:
package com.dfbz;

import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo01_BaseMapper的基本CURD {

    @Autowired
    private UserMapper userMapper;

    /**
     * 新增
     * INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
     */
    @Test
    public void insert() {
        User user = new User(100L, "Ken", "0",20);
        userMapper.insert(user);
    }

    /**
     * 修改
     * UPDATE user SET name=?, age=?, email=? WHERE id=?
     */
    @Test
    public void update() {
        User user = new User(100L, "Kevin", "0", 25);
        userMapper.updateById(user);
    }


    /**
     * 根据id查询
     * SELECT id,name,age,email FROM user WHERE id=?
     */
    @Test
    public void selectById() {
        User user = userMapper.selectById(100L);
        System.out.println(user);
    }

    /**
     * 根据一批id查询
     * SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
     */
    @Test
    public void selectBatchIds() {
        List<User> userList = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
        for (User user : userList) {
            System.out.println(user);
        }
    }

    /**
     * 根据条件查询数据
     * SELECT id,name,age,email FROM user WHERE name = ? AND id = ?
     */
    @Test
    public void selectByMap() {
        // 封装条件
        HashMap<String, Object> param = new HashMap<>();
        param.put("id",10L);
        param.put("name","Kevin");

        // 根据条件查询,map中的多个条件是并列关系 id=10 and name='小灰灰'
        List<User> userList = userMapper.selectByMap(param);
        for (User user : userList) {
            System.out.println(user);
        }
    }

    /**
     * 根据id删除
     * DELETE FROM user WHERE id=?
     */
    @Test
    public void deleteById() {
        userMapper.deleteById(100L);
    }

    /**
     * 根据id删除一批
     * DELETE FROM user WHERE id IN ( ? , ? , ? )
     */
    @Test
    public void deleteBatchIds() {
        userMapper.deleteBatchIds(Arrays.asList(1, 2, 3));
    }

    /**
     * 根据条件删除数据
     * DELETE FROM user WHERE name = ? AND id = ?
     */
    @Test
    public void deleteByMap() {
        // 封装条件
        HashMap<String, Object> param = new HashMap<>();
        param.put("id",100L);
        param.put("name","Kevin");

        userMapper.deleteByMap(param);
    }
}

3.2 Wrapper查询

通过BaseMapper提供的一些方法我们可以完成一些基本的CRUD,但无法完成复杂条件的查询;对于复杂条件的查询,MyBatis Plus提供了Wrapper接口来处理;

Wrapper是MyBatis Plus提供的一个条件构造器,主要用于构建一系列条件,当Wrapper构建完成后,可以使用Wrapper中的条件进行查询、修改、删除等操作;

  • Wrapper的继承体系如下:

98.png

Wrapper是条件构造抽象类,最顶端父类,其主要实现类有如下:

  • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
    • QueryWrapper : Query条件封装
    • UpdateWrapper : Update条件封装
    • AbstractLambdaWrapper : 使用Lambda语法
      • LambdaQueryWrapper :基于Lambda语法使用的查询Wrapper
      • LambdaUpdateWrapper : 基于Lambda语法使用的更新Wrapper

官方文档:https://baomidou.com/pages/10c804/

3.3.1 Wrapper的基本方法

AbstractWrapper是其他常用Wrapper的父类,用于生成 sql 的 where 条件

1)方法介绍

AbstractWrapper提供了很多公有的方法,其子类全部具备这些方法,方法列表如下:

方法名解释示例
eq等于 =eq(“name”, “老王”)**--->**
name = ‘老王’
ne不等于 <>ne(“name”, “老王”)**--->**
name <> ‘老王’
gt大于 >gt(“age”, 18)**--->**
age > 18
ge大于等于 >=ge(“age”, 18)**--->**
age >= 18
lt小于 <lt(“age”, 18)**--->**
age < 18
le小于等于 <=le(“age”, 18)**--->**
age <= 18
betweenbetween 值1 and 值2between(“age”, 18, 30)**--->**
age between 18 and 30
notBetweennot between 值1 and 值2notBetween(“age”, 18, 30)**--->**
age not between 18 and 30
likeLIKE ‘%值%’like(“name”, “王”)**--->**
name like ‘%王%’
notLikeNOT LIKE ‘%值%’notLike(“name”, “王”)**--->**
name not like ‘%王%’
likeLeftLIKE ‘%值’likeLeft(“name”, “王”)**--->**
name like ‘%王’
likeRightLIKE ‘值%’likeRight(“name”, “王”)**--->**
name like ‘王%’
isNull字段 IS NULLisNull(“name”)**--->**
name is null
isNotNull字段 IS NOT NULLisNotNull(“name”)**--->**
name is not null
in字段 IN (v0, v1, …)in(“age”, 1, 2, 3)**--->**
age in (1,2,3)
notIn字段 NOT IN (v0, v1, …)notIn(“age”, 1, 2, 3)**--->**
age not in (1,2,3)
inSql字段 IN ( sql语句 )inSql(“id”, “select id from table where id < 3”)**--->**
id in (select id from table where id < 3)
notInSql字段 NOT IN ( sql语句 )notInSql(“id”, “select id from table where id < 3”)**--->**
id not in (select id from table where id < 3)
groupBy分组:GROUP BY 字段, …groupBy(“id”, “name”)**--->**
group by id,name
orderByAsc排序:ORDER BY 字段, … ASCorderByAsc(“id”, “name”)**--->**
order by id ASC,name ASC
orderByDesc排序:ORDER BY 字段, … DESCorderByDesc(“id”, “name”)**--->**
order by id DESC,name DESC
orderBy排序:ORDER BY 字段, …orderBy(true, true, “id”, “name”)**--->**
order by id ASC,name ASC
havingHAVING ( sql语句 )例1:having(“sum(age) > 10”)**--->**
having sum(age) > 10
例2:having(“sum(age) > {0}”, 11)
**--->**
having sum(age) > 11
func主要解决条件拼接func(i -> if(true) {i.eq(“id”, 1)} else {i.ne(“id”, 1)})
or拼接 OReq(“id”,1).or().eq(“name”,“老王”)**--->**
id = 1 or name = ‘老王’
andAND 嵌套and(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))**--->**
and (name = ‘李白’ and status <> ‘活着’)
nested用于多条件拼接时nested(i -> i.eq(“name”, “李白”).ne(“status”, “活着”))**--->**
(name = ‘李白’ and status <> ‘活着’)
apply用于拼接SQL语句例1:apply(“id = 1”)**--->**
id = 1
例2:apply(“id = {0}”,1)
**--->**
id = 1
例3:apply(“name like {0} and age > {1}”,“%J%”,18) **--->**
name like ‘%J%’ and age > 18
last无视优化规则直接拼接到 sql 的最后last(“limit 1”) **--->**
在SQL语句最后面拼接:limit 1
exists拼接 EXISTS ( sql语句 )exists(“select id from table where age = 1”)**--->**
exists (select id from table where age = 1)
notExists拼接 NOT EXISTS ( sql语句 )notExists(“select id from table where age = 1”)**--->**
not exists (select id from table where age = 1)

创建Wrapper对象:

  • Wrappers静态方法:
    • public static <T> QueryWrapper<T> query()
  • 通过QueryWrapper对象的构造方法:
    • public QueryWrapper()

代码示例:

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo02_Wrapper基本方法 {

    @Autowired
    private UserMapper userMapper;

    /**
     * QueryMapper的创建
     * SELECT id,name,age,email FROM user
     */
    @Test
    public void test1() {
        // 创建QueryMapper,默认情况下查询所有数据
        QueryWrapper<User> wrapper = Wrappers.query();
        QueryWrapper<User> wrapper2 = new QueryWrapper<>();

        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
}

2)基本方法的使用

官网案例:https://baomidou.com/pages/10c804/#abstractwrapper

  • 使用示例:
@Test
public void test2() {
    QueryWrapper<User> wrapper = Wrappers.query();
    // name ='Jack'
//        wrapper.eq("name","Jack");

    /*
    	参数1: 是否要进行name条件的拼接
    */
    String name = "Jack";
    wrapper.eq(name != null, "name", name);
    
    // name != 'Jack'
//        wrapper.ne("name","Jack");

    // age > 20
//        wrapper.gt("age",20);

    // age < 20
//        wrapper.lt("age",20);

    // age=20
//        wrapper.lt("age",20);

    // age between 20 and 24
//        wrapper.between("age",20,24);

    // age not between 20 and 24
//        wrapper.notBetween("age",20,24);

    // name like "%J%"          自动拼接左右的%
//        wrapper.like("name","J");

    // name not like "%J%"
//        wrapper.notLike("name","J");

    // name like "%J"
//        wrapper.likeLeft("name","J");

    // name like 'J%'
//        wrapper.likeRight("name","J");

    // name is null
//        wrapper.isNull("name");

    // name is not null
//        wrapper.isNotNull("name");

    // name in ('Jack','Tom','Jone')
//        wrapper.in("name","Jack","Tom","Jone");

    // name not in ('Jack','Tom','Jone')
//        wrapper.notIn("name","Jack","Tom","Jone");
    
    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

3)子查询
  • 示例代码:
/**
 * 子查询
 */
@Test
public void test3() {

    //  创建wrapper对象
    QueryWrapper<User> wrapper = Wrappers.query();

    // 相当于: name in (select name from user where age > 21)
    wrapper.inSql("name","select name from user where age>21");

    // 相当于: name not in (select name from user where age > 21)
//        wrapper.notInSql("name","select name from user where age>21");

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

4)分组与排序
  • 1)分组:

通过Wrapper.query()构建的查询字段默认是表中的所有字段,因此在这种情况下分组是没有意义的,分组具体的用法我们后面再详细介绍;

/**
 * 分组
 */
@Test
public void test4() {


    //  创建wrapper对象
    QueryWrapper<User> wrapper = Wrappers.query();

    /*
        相当于 select * from user where gropu sex
         这是一个没有意义的分组,分组必须结合查询的字段来体现,后续学QueryWrapper的select方法再介绍
     */
    wrapper.groupBy("sex");

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
  • 2)having操作
/**
 * having操作
 */
@Test
public void test5() {

    //  创建wrapper对象
    QueryWrapper<User> wrapper = Wrappers.query();

    // group by sex having sex = 0
    wrapper.groupBy("sex");
    wrapper.having("sex", "0");

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
  • 3)排序:
/**
 * 排序
 */
@Test
public void test6() {
    //  创建wrapper对象
    QueryWrapper<User> wrapper = Wrappers.query();

    /**
     * 参数1: 是否是Asc排序(升序), true : asc排序, false: desc排序
     * 参数2: 排序的字段
     */
//        wrapper.orderByAsc("age");		// order by age asc
    
//        wrapper.orderByDesc("age");		// order by age desc

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

5)多条件的拼接

Wrapper对象在调用每一个方法时都会返回当前对象(Wrapper),这样可以很好的方便我们链式编程;另外MyBatis Plus在拼接多个条件时默认使用and拼接,如果需要使用or,那么需要显示的调用or()方法;

  • 1)and拼接条件:
/**
 * and拼接条件
 */
@Test
public void test7() {
    //  创建wrapper对象
    QueryWrapper<User> wrapper = Wrappers.query();

    /*
        默认情况下,多条件是以and拼接
        SQL语句: name LIKE "%a%" AND age > 20 AND sex = 0
     */
    wrapper.like("name", "%a%")
            .lt("age", 20)
            .eq("sex", 0);

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
  • 2)or拼接条件:
/**
 * or拼接条件
 */
@Test
public void test8() {
    //  创建wrapper对象
    QueryWrapper<User> wrapper = Wrappers.query();

    /*
        默认情况下,多条件是以and拼接
        SQL语句: name LIKE "%a%" OR age > 20 AND sex = 0
     */
    wrapper.like("name", "%a%")
            .or()
            .lt("age", 20)
            .eq("sex", 0);          // 该条件仍然以AND拼接

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

6)Wrapper的其他方法
  • 1)and方法:用于拼接一个其他的整体条件;示例如下:
/**
 * and方法
 */
@Test
public void test1() {
    QueryWrapper<User> wrapper = Wrappers.query();

    // 生成的SQL为: (age < ? AND (sex = ? OR name LIKE ?))
    /*
    wrapper.lt("age", 20);
    wrapper.and(new Consumer<QueryWrapper<User>>() {
        @Override
        public void accept(QueryWrapper<User> userQueryWrapper) {
            userQueryWrapper.eq("sex", 0)
                    .or()
                    .like("name", "J");
        }
    });
    */

    // 生成的SQL为:  (age < ? AND sex = ? OR name LIKE ?)
    wrapper.lt("age", 20)
            .eq("sex", 0)
            .or()
            .like("name", "J");

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
  • 2)func方法:用于多条件的拼接,直接使用之前的方法也可以做到这个功能;示例如下:
/**
 * func方法
 */
@Test
public void test2() {
    QueryWrapper<User> wrapper = Wrappers.query();

    // 生成的SQL语句条件: (age < ? AND name LIKE ? AND sex = ?)
    wrapper.lt("age", 20);
    wrapper.func(new Consumer<QueryWrapper<User>>() {
        @Override
        public void accept(QueryWrapper<User> userQueryWrapper) {

            userQueryWrapper.like("name", "a");
            userQueryWrapper.eq("sex", "0");
        }
    });
    // 等价于:
//        wrapper.lt("age", 20).like("name", "a").eq("sex", "0");

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
  • 3)nested方法:功能等价于and方法
/**
 * nested方法
 */
@Test
public void test3() {
    QueryWrapper<User> wrapper = Wrappers.query();

    // 生成的SQL语句条件为: (id = ? AND (name LIKE ? OR age > ?))
    // nested()等价于and方法()
    wrapper.eq("id", 1);
    wrapper.nested(new Consumer<QueryWrapper<User>>() {
        @Override
        public void accept(QueryWrapper<User> userQueryWrapper) {

            // 默认情况下是用and来拼接多个条件
            userQueryWrapper
                    .like("name", "a")
                    .or()
                    .gt("age", 20);
        }
    });

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
  • 4)apply方法:可以使用占位符进行参数传参;示例如下:
/**
 * apply方法
 */
@Test
public void test4() {
    QueryWrapper<User> wrapper = Wrappers.query();

    // SQL: (name like ? and age > ?)
//        wrapper.apply("name like {0} and age > {1}", "%J%", 18);

    // SQL: (date_format(birthday, '%Y-%m-%d') = ?)
    wrapper.apply("date_format(birthday, '%Y-%m-%d') = {0}", "2001-10-04");

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
  • 4)last方法:无视优化规则直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准 有sql注入的风险

Tips:apply方法可以防止SQL注入,但last方法不能;

@Test
public void test13() {
    QueryWrapper<User> wrapper = Wrappers.query();

    // 无视优化规则直接拼接到 sql 的最后,只能调用一次,多次调用以最后一次为准 有sql注入的风险

    // SQL: name = 'Jone'
//        String name = "Jone";
//        wrapper.last("where name = '" + name + "'");

    // SQL: SELECT id,name,sex,age FROM user where name ='' or 1=1; -- '
    String name = "' or 1=1; -- ";
    wrapper.last("where name ='" + name + "'");         // 出现SQL注入问题

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
  • 5)exists方法:用于exists语句
/**
 * exists方法
 */
@Test
public void test6() {
    QueryWrapper<User> wrapper = Wrappers.query();

    // SQL: (EXISTS (select 1))
    wrapper.exists("select 1");

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}
  • 6)notExists方法:
/**
 * notExists方法
 */
@Test
public void test7() {
    QueryWrapper<User> wrapper = Wrappers.query();

    // SQL: (NOT EXISTS (select 1))
    wrapper.notExists("select 1");

    List<User> users = userMapper.selectList(wrapper);
    users.forEach(System.out::println);
}

3.3.2 QueryMapper

QueryMapper是AbstractWrapper的子类,主要用于查询指定字段,方法列表如下:

方法名解释示例
select(String… sqlSelect)设置查询字段例1:select(“id”, “name”, “age”)
例2:select(i -> i.getProperty().startsWith(“test”))
  • 示例代码:
package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo04_QueryWrapper {

    @Autowired
    private UserMapper userMapper;

    // 选择查询的字段
    @Test
    public void test1() throws Exception {
        QueryWrapper<User> wrapper = Wrappers.query();

        // 确定要查询的字段
        wrapper.select("id", "name", "sex");

        // in
        wrapper.in("id","1","2");

        // SQL: SELECT id,name,sex FROM user WHERE (id IN (?,?))
        List<User> userList = userMapper.selectList(wrapper);

        userList.forEach(System.out::println);
    }


    @Test
    public void test2() throws Exception {

        User user = new User();
        user.setId(1L);

        // user当做查询的条件
        QueryWrapper<User> wrapper = Wrappers.query(user);
        
        // 指定查询的列
        wrapper.select("id", "name", "sex");

        // SQL: SELECT id,name,sex FROM user WHERE id=?
        List<User> userList = userMapper.selectList(wrapper);

        userList.forEach(System.out::println);
    }
}

3.3.3 UpdateWrapper

UpdateWrapper也是AbstractWrapper的子类,因此UpdateWrapper也具备之前的那些查询方法,不同的是,UpdateMapper在那些方法基础之上还提供了很多有关于更新操作的方法;

  • 方法如下:
    | 方法名 | 解释 | 示例 |
    | — | — | — |
    | set(String column, Object val) | 设置查询字段 | 例1:set("name", "老李头")
    例2:set("name", "")
    —>数据库字段值变为空字符串例3:set("name", null)
    —>数据库字段值变为null |
    | setSql(String sql) | 设置set子句的部分SQL | 例1:setSql("name = '老李头'")
    例2:setSql("name = '老李头',age=20 where id=1") |

示例代码:

package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo05_UpdateWrapper {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void test1() throws Exception {
        UpdateWrapper<User> wrapper = Wrappers.update();

        // UpdateWrapper也是AbstractWrapper的子类,因此也具备一些基本的查询方法
        wrapper.like("name", "J");

        List<User> userList = userMapper.selectList(wrapper);

        for (User user : userList) {
            System.out.println(user);
        }
    }


    /**
     * 第一种方法: 使用wrapper来修改,并且指定查询条件
     *
     * @throws Exception
     */
    @Test
    public void test2() throws Exception {
        UpdateWrapper<User> wrapper = Wrappers.update();
        wrapper.set("name", "Jackson");
        wrapper.set("age", "16");
        wrapper.set("sex", "1");
        wrapper.eq("id", 2L);

        // SQL: UPDATE user SET name=?, sex=?, age=? WHERE (id = ?)
        userMapper.update(null, wrapper);
    }


    /**
     * 第二种方法: 使用wrapper来封装条件,使用entity来封装修改的数据
     *
     * @throws Exception
     */
    @Test
    public void test3() throws Exception {
        UpdateWrapper<User> wrapper = Wrappers.update();
        wrapper.eq("id", 2L);

        User user = new User(null, "Jack", "0", 28);

        // SQL: UPDATE user SET name=?, sex=?, age=? WHERE (id = ?)
        userMapper.update(user, wrapper);

    }

    /**
     * 第三种方法: Wrappers.update(user)传递查询的条件,使用wrapper来修改
     *
     * @throws Exception
     */
    @Test
    public void test4() throws Exception {
        User user = new User();
        user.setId(1L);

        // user当做查询条件
        UpdateWrapper<User> wrapper = Wrappers.update(user);
        wrapper.set("name", "xiaohui");
        wrapper.set("sex", "0");
        wrapper.set("age", "22");

        // SQL : UPDATE user SET name=?,sex=?,age=? WHERE id=?
        userMapper.update(null, wrapper);
    }

    /**
     * setSql方法
     *
     * @throws Exception
     */
    @Test
    public void test5() throws Exception {
        UpdateWrapper<User> wrapper = Wrappers.update();
        wrapper.setSql("name='abc',sex='0',age=18 where id=1");

        // SQL: UPDATE user SET name='abc',sex='0',age=18 where id=1
        userMapper.update(null, wrapper);
    }
}

3.3.4 LambdaQueryWrapper

LambdaQueryWrapper是QueryWrapper的子类,具备QueryWrapper的所有方法,QueryWrapper的方法上提供了一系列有关于方法引的操作;

  • 使用示例:
package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo06_LambdaQueryMapper {

    @Autowired
    private UserMapper userMapper;

    /**
     * 使用QueryWrapper
     * @throws Exception
     */
    @Test
    public void test1() throws Exception {

        QueryWrapper<User> wrapper = Wrappers.query();
        wrapper.eq("id","1");

        List<User> userList = userMapper.selectList(wrapper);
        for (User user : userList) {
            System.out.println(user);
        }
    }

    /**
     * 使用LambdaQueryWrapper
     * @throws Exception
     */
    @Test
    public void test2() throws Exception {

        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
        
        // id=1
//        wrapper.eq(User::getId,1);

        // select id,name,age from user where id in (1,2,3) and name like "%a%" 
        wrapper.in(User::getId,"1","2","3")
                .like(User::getName,"a")
                .select(User::getId,User::getName,User::getAge);

        List<User> userList = userMapper.selectList(wrapper);
        for (User user : userList) {
            System.out.println(user);
        }
    }
}

3.3.5 LambdaUpdateMapper

LambdaUpdateMapper同样是UpdateMapper的子类,具备UpdateMapper的所有方法,UpdateMapper的方法上提供了一系列有关于方法引的操作;

  • 示例代码:
package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo07_LambdaUpdateWrapper {

    @Autowired
    private UserMapper userMapper;

    /**
     * 使用UpdateWrapper
     *
     * @throws Exception
     */
    @Test
    public void test1() throws Exception {

        UpdateWrapper<User> wrapper = Wrappers.update();
        wrapper.eq("id", "1");
        wrapper.set("name", "xiaohui");
        wrapper.set("age", 20);

        userMapper.update(null, wrapper);
    }

    /**
     * 使用LambdaUpdateWrapper
     *
     * @throws Exception
     */
    @Test
    public void test2() throws Exception {

        LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate();

        wrapper.eq(User::getId,"1");
        wrapper.set(User::getName,"xiaolan");
        wrapper.set(User::getAge,18);

        userMapper.update(null,wrapper);
    }
}

3.3 Mapper的分页查询

3.3.1 Mapper分页查询配置

在MyBatis中提供有Page对象来帮助我们实现分页查询,在Page对象中有如下成员:

成员变量说明
List getRecords()当前页数据
public long getTotal()总记录数
public long getSize()页大小
public long getCurrent()当前页
default long getPages()总页数
public boolean hasNext()是否有上一页
public boolean hasPrevious()是否有上一页

MyBatisPlus的分页逻辑底层是通过分页插件来完成的,因此我们首先要配置MyBatisPlus的分页插件;

  • 配置分页插件:
package com.dfbz.config;

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

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Configuration
@MapperScan("com.dfbz.mapper")          // mapper接口的所在位置
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new
                PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

3.3.2 Mapper完成分页查询

在BaseMapper中主要提供有如下方法来完成分页查询:

  • <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper)
    • 参数1:分页配置类
    • 参数2:分页查询条件
    • 解释:根据分页配置和分页查询条件来完成分页查询,当前页数据为指定类型
  • <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper)
    • 参数1:分页配置类
    • 参数2:分页查询条件
    • 解释:根据分页配置和分页查询条件来完成分页查询,当前页数据为Map类型

  • 1)无条件分页查询:
package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo06_BaseMapper的分页查询 {

    @Autowired
    private UserMapper userMapper;

    /**
     * 无条件分页查询
     * @throws Exception
     */
    @Test
    public void test1() throws Exception {

        // 封装分页信息
        Page<User> page = new Page<>(1,3);

        /*
         执行分页查询,并将结果封装到page中
            参数1: 分页配置
            参数2: 查询条件
         */
        userMapper.selectPage(page, null);

        // 当前页数据
        List<User> pageData = page.getRecords();
        for (User user : pageData) {
            System.out.println(user);
        }

        System.out.println("------------");

        System.out.println("当前页:" + page.getCurrent());
        System.out.println("每页显示的条数:" + page.getSize());
        System.out.println("总记录数:" + page.getTotal());
        System.out.println("总页数:" + page.getPages());
        System.out.println("是否有上一页:" + page.hasPrevious());
        System.out.println("是否有下一页:" + page.hasNext());
    }
}
  • 查询结果:
# 首先查询总记录数
==>  Preparing: SELECT COUNT(*) FROM user
==> Parameters: 
<==    Columns: COUNT(*)
<==        Row: 10
<==      Total: 1

# 再查询分页
==>  Preparing: SELECT id,name,sex,age FROM user LIMIT ?
==> Parameters: 3(Long)
<==    Columns: id, name, sex, age
<==        Row: 1, Jone, 1, 27
<==        Row: 2, Jack, 0, 20
<==        Row: 3, Tom, 1, 28
<==      Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@226eba67]
User(id=1, name=Jone, sex=1, age=27)
User(id=2, name=Jack, sex=0, age=20)
User(id=3, name=Tom, sex=1, age=28)
------------
当前页:1
每页显示的条数:3
总记录数:10
总页数:4
是否有上一页:false
是否有下一页:true
2022-11-15 15:28:16.946  INFO 8724 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-11-15 15:28:16.948  INFO 8724 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Process finished with exit code 0
  • 带条件分页查询:
/**
 * 带条件分页查询
 *
 * @throws Exception
 */
@Test
public void test2() throws Exception {

    QueryWrapper<User> wrapper = Wrappers.query();
    wrapper.like("name", "a");

    // 封装分页信息
    Page<User> page = new Page<>(1, 3);

    /*
     执行分页查询,并将结果封装到page中
        参数1: 分页配置
        参数2: 查询条件
     */
    userMapper.selectPage(page, wrapper);

    // 当前页数据
    List<User> pageData = page.getRecords();
    for (User user : pageData) {
        System.out.println(user);
    }

    System.out.println("------------");

    System.out.println("当前页:" + page.getCurrent());
    System.out.println("每页显示的条数:" + page.getSize());
    System.out.println("总记录数:" + page.getTotal());
    System.out.println("总页数:" + page.getPages());
    System.out.println("是否有上一页:" + page.hasPrevious());
    System.out.println("是否有下一页:" + page.hasNext());
}
  • 查询结果:
==>  Preparing: SELECT COUNT(*) FROM user WHERE (name LIKE ?)
==> Parameters: %a%(String)
<==    Columns: COUNT(*)
<==        Row: 5
<==      Total: 1
==>  Preparing: SELECT id,name,sex,age FROM user WHERE (name LIKE ?) LIMIT ?
==> Parameters: %a%(String), 3(Long)
<==    Columns: id, name, sex, age
<==        Row: 2, Jack, 0, 20
<==        Row: 4, Sandy, 1, 21
<==        Row: 6, Jackson, 0, 18
<==      Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@58a2b4c]
User(id=2, name=Jack, sex=0, age=20)
User(id=4, name=Sandy, sex=1, age=21)
User(id=6, name=Jackson, sex=0, age=18)
------------
当前页:1
每页显示的条数:3
总记录数:5
总页数:2
是否有上一页:false
是否有下一页:true
  • 3)将分页数据的查询结果以Map类型返回
/**
 * 查询结果以Map返回
 *
 * @throws Exception
 */
@Test
public void test3() throws Exception {

    // 封装分页信息
    Page page = new Page<>(1, 3);

    userMapper.selectMapsPage(page, null);

    // 每一条记录都是一个HashMap
    List<HashMap<String,Object>> pageData = page.getRecords();
    for (HashMap userMap : pageData) {
        System.out.println(userMap);
    }

    System.out.println("------------");

    System.out.println("当前页:" + page.getCurrent());
    System.out.println("每页显示的条数:" + page.getSize());
    System.out.println("总记录数:" + page.getTotal());
    System.out.println("总页数:" + page.getPages());
    System.out.println("是否有上一页:" + page.hasPrevious());
    System.out.println("是否有下一页:" + page.hasNext());
}
  • 查询结果:
==>  Preparing: SELECT COUNT(*) FROM user
==> Parameters: 
<==    Columns: COUNT(*)
<==        Row: 10
<==      Total: 1
==>  Preparing: SELECT id,name,sex,age FROM user LIMIT ?
==> Parameters: 3(Long)
<==    Columns: id, name, sex, age
<==        Row: 1, Jone, 1, 27
<==        Row: 2, Jack, 0, 20
<==        Row: 3, Tom, 1, 28
<==      Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7123be6c]
{sex=1, name=Jone, id=1, age=27}
{sex=0, name=Jack, id=2, age=20}
{sex=1, name=Tom, id=3, age=28}
------------
当前页:1
每页显示的条数:3
总记录数:10
总页数:4
是否有上一页:false
是否有下一页:true
2022-11-15 15:50:37.905  INFO 20980 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-11-15 15:50:37.905  INFO 20980 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

Process finished with exit code 0

3.4 MyBatis Plus的Service查询

3.4.1 通用Service简介

通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行、remove删除、list 查询集合、page查询分页

官网案例:https://baomidou.com/pages/49cc81/

使用步骤:

  • 1)定义一个UserService接口继承与MyBatisPlus提供的IService接口:
package com.dfbz.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.dfbz.entity.User;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public interface IUserService extends IService<User> {
}
  • 2)定义一个UserService的实现类,并且继承与MyBatisPlus提供的ServiceImpl:
package com.dfbz.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import com.dfbz.service.IUserService;
import org.springframework.stereotype.Service;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Service("userService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

}

3.4.2 通用service常用方法

  • 新增:
    • default boolean save(T entity):新增记录
    • boolean saveBatch(Collection<T> entityList):批量插入
    • saveBatch(Collection<T> entityList, int batchSize):一次性批量插入batchSize条记录
  • 删除:
    • boolean removeById(Serializable id):根据id删除
    • boolean removeByMap(Map<String, Object> columnMap):根据条件删除
    • boolean remove(Wrapper<T> queryWrapper):使用Wrapper封装条件删除
    • boolean removeByIds(Collection<? extends Serializable> idList):删除一批
  • 修改:
    • boolean updateById(T entity):修改
    • boolean update(Wrapper<T> updateWrapper):根据Wrapper修改
    • boolean update(T entity, Wrapper<T> updateWrapper):使用Wrapper查询出结果,修改为entity
    • boolean updateBatchById(Collection<T> entityList):批量修改
    • updateBatchById(Collection<T> entityList, int batchSize):一次性批量修改batchSize条记录
    • boolean saveOrUpdate(T entity):如果id存在则修改,如果id不存在则新增
  • 查询:
    • T getById(Serializable id):根据id查询
    • List<T> listByIds(Collection<? extends Serializable> idList):根据一批id查询多条记录
    • List<T> listByMap(Map<String, Object> columnMap):根据条件查询多条记录
    • T getOne(Wrapper<T> queryWrapper):根据Wrapper查询一条记录,如果查询到多条则抛出异常
    • T getOne(Wrapper<T> queryWrapper, boolean throwEx):根据Wrapper查询一条记录,通过throwEx决定是否抛出异常
    • int count():查询总记录数
    • int count(Wrapper<T> queryWrapper):根据条件查询总记录数
  • 分页:
    • <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper):带条件分页查询,当前页数据为T类型
    • <E extends IPage<T>> E page(E page):无条件分页
    • List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper):带条件分页查询,当前页数据为HashMap类型
    • List<Map<String, Object>> listMaps():无条件分页

  • 测试代码:
package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dfbz.entity.User;
import com.dfbz.service.IUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.List;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo07_Service查询 {

    @Autowired
    private IUserService userService;

    /**
     * 新增
     *
     * @throws Exception
     */
    @Test
    public void test1() throws Exception {
        User user = new User(null, "xiaohui", "0", 20);
        userService.save(user);
    }

    /**
     * 如果id存在则修改,不存在则新增
     *
     * @throws Exception
     */
    @Test
    public void test2() throws Exception {
        User user = new User(1L, "xiaohui", "0", 20);
        userService.saveOrUpdate(user);
    }

    /**
     * 根据id删除
     *
     * @throws Exception
     */
    @Test
    public void test3() throws Exception {
        userService.removeById(1L);
    }

    /**
     * 根据id修改
     *
     * @throws Exception
     */
    @Test
    public void test4() throws Exception {

        User user = new User(1L, "xiaolan", "1", 18);
        userService.updateById(user);
    }

    /**
     * 根据id查询
     *
     * @throws Exception
     */
    @Test
    public void test5() throws Exception {
        User user = userService.getById(1L);
        System.out.println(user);
    }

    /**
     * 查询列表
     *
     * @throws Exception
     */
    @Test
    public void test6() throws Exception {

        QueryWrapper<User> wrapper = Wrappers.query();
        wrapper.in("id", "1", "2");

        // 查询所有
//        List<User> userList = userService.list();

        // 通过wrapper查询
        List<User> userList = userService.list(wrapper);

        for (User user : userList) {
            System.out.println(user);
        }
    }


    /**
     * 查询总记录数
     *
     * @throws Exception
     */
    @Test
    public void test7() throws Exception {

        QueryWrapper<User> wrapper = Wrappers.query();
        wrapper.like("name", "a");

        // 查询总记录数
//        int count = userService.count();

        // 根据条件查询总记录数
        int count = userService.count(wrapper);

        System.out.println(count);
    }

    /**
     * 分页查询(当前页类型为指定类型)
     *
     * @throws Exception
     */
    @Test
    public void test8() throws Exception {

        Page<User> page = new Page<>(1, 3);

        userService.page(page);

        // 当前页数据
        List<User> pageData = page.getRecords();
        for (User user : pageData) {
            System.out.println(user);
        }

        System.out.println("------------");

        System.out.println("当前页:" + page.getCurrent());
        System.out.println("每页显示的条数:" + page.getSize());
        System.out.println("总记录数:" + page.getTotal());
        System.out.println("总页数:" + page.getPages());
        System.out.println("是否有上一页:" + page.hasPrevious());
        System.out.println("是否有下一页:" + page.hasNext());
    }


    /**
     * 分页查询(当前页结果为HashMap类型)
     *
     * @throws Exception
     */
    @Test
    public void test9() throws Exception {

        Page page = new Page<>(1, 3);

        userService.pageMaps(page);

        // 当前页数据
        List<HashMap<String, Object>> pageData = page.getRecords();
        for (HashMap userMap : pageData) {
            System.out.println(userMap);
        }

        System.out.println("------------");

        System.out.println("当前页:" + page.getCurrent());
        System.out.println("每页显示的条数:" + page.getSize());
        System.out.println("总记录数:" + page.getTotal());
        System.out.println("总页数:" + page.getPages());
        System.out.println("是否有上一页:" + page.hasPrevious());
        System.out.println("是否有下一页:" + page.hasNext());
    }


    /**
     * 链式查询
     *
     * @throws Exception
     */
    @Test
    public void test10() throws Exception {

        QueryChainWrapper<User> chainWrapper = userService.query();

        // SQL: SELECT id,name,age FROM user WHERE (id IN (?,?,?) AND name LIKE ?)
        List<User> userList = chainWrapper.select("id", "name", "age")
                .in("id", "1", "2", "3")
                .like("name", "a")
                .list();

        for (User user : userList) {
            System.out.println(user);
        }
    }


    /**
     * 链式修改
     *
     * @throws Exception
     */
    @Test
    public void test11() throws Exception {

        UpdateChainWrapper<User> chainWrapper = userService.update();

        // SQL: UPDATE user SET age=? WHERE (id IN (?,?) OR sex = ?)
        chainWrapper.in("id","1","2")
                .or()
                .eq("sex","0")
                .set("age",20).
                update();
    }
}

3.5 MyBatisPlus的常用注解

3.5.1 @TableName

操作数据库表时,Mapper接口继承BaseMapper,泛型名和数据库表名对应,如果数据表名为t_user,而BaseMapper的泛型为实体类User,导致找不到数据库的表。

  • 1)解决方法1:实体类使用@TableName注解,value值为表名
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor

@TableName("t_user")
public class User {
    private Long id;
    private String name;
    private String sex;
    private Integer age;
}
  • 2)解决方法2:如果多张表的表名为t_user/t_cat/t_xxx,不需要为每一个实体类添加@TableName注解,在MyBatis全局配置即可,为所有表名添加前缀
mybatis-plus:
  global-config: 	# MyBatisPlus全局配置
    db-config:  	# 配置数据库
      table-prefix: t_  #配置表名前缀为t_

3.5.2 @TableId

MyBatisPlus在实现CRUD默认会将Id作为主键,在插入数据时,如果主键不叫Id则添加功能会失败

解决方案:@TableId注解标识属性,将此属性对应的字段指定为主键

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor

@TableName("t_user")
public class User {

    // 将当前属性所对应的字段作为主键
    @TableId
    private Long id;
    private String name;
    private String sex;
    private Integer age;
}

1)value属性

问题:实体类中被标识为主键的属性名为id,而数据库的主键为uid,则id属性不会对应uid字段上

解决方案:使用@TableId的value属性设置当前主键字段的字段名为uid

package com.dfbz.entity;
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @TableId(value = "uid")
    private Long id;
    private String name;
    private String sex;
    private Integer age;
}

2)主键策略

MybatisPlus为我们支持了许多种的主键策略;

主键策略是指Mybatis-plus可以自动生成主键的策略,不需要手动插入主键,由MybatisPlus的主键策略帮我们自动生成主键

官网资料:https://baomidou.com/pages/e131bd/#spring-boot)

在枚举类IdType中定制有如下几种注解策略:

public enum IdType {
    
    // 数据自增(该类型请确保数据库设置了 ID自增 否则无效)
    AUTO(0),
    
    // 采用雪花算法生成ID
    NONE(1),
    
    // 交由用户自己分配ID(必须分配,不分配则出现异常,除非数据库表本身设置了自增)
    INPUT(2),

    // 采用雪花算法((主键类型为number或string)
    // 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
    ASSIGN_ID(3),
    
    // 分配UUID (主键类型为 string)
    // 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
    ASSIGN_UUID(4),
    
    // 不推荐使用,推荐使用ASSIGN_ID
    @Deprecated
    ID_WORKER(3),
    
    // 不推荐使用,推荐使用ASSIGN_ID
    @Deprecated
    ID_WORKER_STR(3),
    
    // 不推荐使用,推荐使用ASSIGN_UUID
    @Deprecated
    UUID(4);

    private final int key;

    IdType(int key) {
        this.key = key;
    }
}
  • 实体类:
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    // 使用数据库自增策略
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private String sex;
    private Integer age;
}
  • 全局配置自增策略:
mybatis-plus:
  global-config: 	# MyBatisPlus全局配置
    db-config:  	# 配置数据库
      id-type: auto # 统一设置id生成策略

测试代码:

package com.dfbz;

import com.dfbz.entity.User;
import com.dfbz.service.IUserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo08_常用注解 {

    @Autowired
    private IUserService userService;

    @Test
    public void test1() throws Exception {
        User user = new User(111L, "xiaohui", "0", 20);
        userService.save(user);
    }
}

3.5.3 @TableField

如果实体类的普通属性名,和数据库非主键的字段名不一致解决方案:@TableField设置对应字段名

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    @TableId(type = IdType.AUTO)
    private Long id;
    
    @TableField("user_name")
    private String name;
    
    @TableField("user_sex")
    private String sex;
    
    @TableField("user_age")
    private Integer age;
}

3.5.4 @TableLogic

在实际开发中,我们删除一条记录可能只是修改其状态,并不是真正的从数据库中删除掉;这样的删除成为逻辑删除;

  • 逻辑删除:表中设置字段为删除状态 比如删除为1 未删除为0 则查询时,只会查到状态为0的数据(可以进行数据恢复)。
  • 物理删除:从表中删除。

准备一张数据表:

DROP TABLE IF EXISTS `emp`;
CREATE TABLE `emp`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `is_delete` int(11) NULL DEFAULT NULL COMMENT '是否删除 0:未删除 1:删除',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of emp
-- ----------------------------
INSERT INTO `emp` VALUES (1, 'xiaohui', 0);
INSERT INTO `emp` VALUES (2, 'xiaolan', 0);

实体类:

package com.dfbz.entity;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Emp {

    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;

    /*
        表示该列为逻辑删除字段
        0: 表示未删除
        1: 表示已删除
     */
    @TableLogic
    private Integer isDelete;
}

测试代码:

@Autowired
private EmpMapper empMapper;

@Test
public void test2() throws Exception {
    
    // 只是逻辑删除(把id为1的记录的is_delete改为1)
    empMapper.deleteById(1L);
}

执行代码,观察日志:

97.png

Tips:本质上就是执行了一个update

当实体类中有标注逻辑删除字段时,在查询时是不会查询被逻辑删除的记录的,示例代码:

@Test
public void test3() throws Exception {

    // 不会查询is_delete为1的记录
    List<Emp> empList = empMapper.selectList(null);
    for (Emp emp : empList) {
        System.out.println(emp);
    }
}

执行结果:

96.png

3.5.5 @EnumValue

在数据库中,经常会有一些字段符合枚举类型;

例如:

  • sex:0代表男,1代表女
  • status:0代表未开始,1代表进行中,2代表已结束
  • is_secret:0代表私密,1代表公开等
  • is_mark:0代表未签收,1代表拒签,2代表已签收

我们通常都是将以上字段设置为char或者int类型,然后通过对应的字符/数字代表对应的含义,然后到Java代码中我们通常都需要做类似于如下的判断:

<span>${sex==0?'男':'女'}</span>

<if test=${status==0}>未开始</if>
<if test=${status==1}>进行中</if>
<if test=${status==2}>已结束</if>

if(status=0){
	// 未开始....
}else if(status==1){
	// 进行中
} else if(status==2){
	已结束
}

以上代码比较繁琐,并且可读性不是很高;

在MyBatisPlus中支持我们定义一个枚举与数据库中的字段对应起来,然后在枚举类中,使用@EnumValue注解标注真实的值(与数据库的字段对应),然后定义一个String类型的字段表示这个枚举项代表的字符串含义;

  • 准备一张数据表:
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `sex` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别 0:男 1:女',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, '小灰', '0');
INSERT INTO `student` VALUES (2, '小蓝', '1');
  • 定义性别枚举:
package com.dfbz.enmu;

import com.baomidou.mybatisplus.annotation.EnumValue;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
public enum SexEnum {
    MAN(0, "男"),
    WOMAN(1, "女");

    // 这一列的值和数据库表映射
    @EnumValue      
    private Integer sexValue;
    
    // 这个字段就是这个枚举项的字符串含义
    private String sexName;

    private SexEnum(Integer sexValue, String sexName) {
        this.sexValue = sexValue;
        this.sexName = sexName;
    }
}
  • 扫描枚举包:
#配置日志
mybatis-plus:
  # 扫描枚举包
  type-enums-package: com.dfbz.enum_
  • 定义实体类:
package com.dfbz.entity;

import com.dfbz.enmu.SexEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private Integer id;
    private String name;
    
    /*
        使用枚举类型
            当从数据库中查询到了0则赋值SexEnum.MAN给sex变量
            当从数据库中查询到了1则赋值SexEnum.WOMAN给sex变量
     */
    private SexEnum sex;
}
  • 定义Mapper接口:
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Repository
public interface StudentMapper extends BaseMapper<Student> {
}
  • 测试代码:
@Autowired
private StudentMapper studentMapper;

@Test
public void test4() throws Exception {
    System.out.println(studentMapper.selectById(1L));
    System.out.println(studentMapper.selectById(2L));
}

95.png

虽然sex字段为变为了枚举类型,但是将搜索条件设置为sex=0依旧可以搜索到符合条件的记录:

@Test
public void test5() throws Exception {

    QueryWrapper<Student> wrapper = Wrappers.query();
    // 搜索条件为0依旧可以搜索出来
    wrapper.eq("sex","0");

    List<Student> stuList = studentMapper.selectList(wrapper);
    for (Student student : stuList) {
        System.out.println(student);
    }
}

94.png

3.5.6 @Version

1)乐观锁与悲观锁

在多个客户端(线程/进程)操作同一个资源并发生写的操作时,我们就需要保证数据的安全性了。

  • 如图所示:

当我们在购买车票时,首先会进行票数的查询,例如A1在购买车票时查询到票还有100张,准备购买第100张票,与此同时,A2也查询到票数为100,也将购买第100张票;

92.png

Tips:上述图中,两个窗口在买票之前分别查询了票,发现票数余额为100,然后都卖了第100张票,出现多卖情况

在并发修改某一资源时,我们必须保证线程安全的问题。在操作之前先加锁,这种方式就是采用悲观锁的方式;

  • 悲观锁的概念:悲观锁简单的理解就是程序处于悲观状态,在操作任何资源时都认为其他程序会来修改当前资源,自己不放心,因此在操作资源时加锁。

91.png

悲观锁虽然保证了程序的安全性,同时效率也降低了很多,在一个客户端操作时,其他客户端均不可操作,降低了系统的并发性能。

  • 乐观锁概念:乐观锁简单理解就是程序一直处于乐观状态,在操作任何资源时认为其他程序不会来修改当前资源,整个过程不加锁,不加锁效率是可以保证,但不是又回到了我们最初的那个状态吗?即线程安全问题

我们可以在每条记录中分配一个_version字段,每当我们对记录进行更新时,此版本号都会自增。我们可以借助该字段帮我们完成乐观锁。保证线程安全问题。

90.png

实现方式:

-- 要修改数据之前,先查该数据上一次修改的时间戳
select version from table_ where id=1; 

-- 修改数据时,更新版本号
update table_ set goods_name='小苹果', version=version+1 where version=${version};

2)@Version实现乐观锁
  • 建立数据表:
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `count` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of goods
-- ----------------------------
INSERT INTO `goods` VALUES (1, '手机', 100);
INSERT INTO `goods` VALUES (2, '电脑', 100);
  • 实体类:
package com.dfbz.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer count;

    @Version            // 乐观锁字段
    private Integer version;
}
  • Mapper接口:
package com.dfbz.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dfbz.entity.Goods;
import org.springframework.stereotype.Repository;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Repository
public interface GoodsMapper extends BaseMapper<Goods> {
}
  • 测试代码:
@Autowired
private GoodsMapper goodsMapper;

@Test
public void test6() throws Exception {
    Goods goods = goodsMapper.selectById(1L);
    goods.setCount(goods.getCount() - 1);

    goodsMapper.updateById(goods);      // 修改完毕后,version在本次version的基础上+1
}

93.png

Tips:修改的时候version自动在原来的基础上+1;

3.6 MyBatis Plus绑定Mapper.xml

MyBatisPlus本质上也是MyBatis,因此也支持我们自定义SQL语句,编写Mapper.xml与Mapper接口进行绑定;

  • 1)在application.yml中扫描Mapper.xml:
#配置日志
mybatis-plus:
  # 扫描mapper.xml文件
  mapper-locations: classpath:com/dfbz/mapper/*.xml
  # 配置包别名
  type-aliases-package: com.dfbz.entity
  • 2)在Mapper接口中扩展方法:
/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Repository
public interface UserMapper extends BaseMapper<User> {
    List<User> findAll();
}
  • 3)编写Mapper.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace 名称空间,指定对哪个接口进行映射-->
<mapper namespace="com.dfbz.mapper.UserMapper">
    <select id="findAll" resultType="user">
        select * from user
    </select>
</mapper>

3.7 ActiveRecord的使用

3.7.1 ActiveRecord简介

ActiveRecord也属于ORM(对象关系映射)层,由Rails最早提出,遵循标准的ORM模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。

ActiveRecord的主要思想是:

  • 1)每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录,通常表的每个字段在类中都有相应的Field;
  • 2)ActiveRecord同时负责把自己持久化,在ActiveRecord中封装了对数据库的访问,即CURD;
  • 3)ActiveRecord是一种领域模型(Domain Model),封装了部分业务逻辑。

简而言之,AR建立了Java对象与数据库表逻辑上的直接映射,方便了程序的编写。而在MyBatisPlus中使用AR非常简单,只需让JavaBean继承Model类即可。

3.7.2 Model类的使用

在MyBatisPlus中,只要将我们的JavaBean继承与Model类即可获取AR功能,即JavaBean自身实现自身的CURD;

  • 让JavaBean继承Model:
package com.dfbz.entity;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")      
public class User extends Model<User> {         // 继承Model

    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private String sex;
    private Integer age;
}
  • 测试代码:
package com.dfbz;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo12_ActiveRecord {

    @Autowired
    private UserMapper userMapper;

    /**
     * 新增
     * @throws Exception
     */
    @Test
    public void test1() throws Exception {
        User user = new User(100L,"xiaoming","0",20);
        user.insert();
    }

    /**
     * 删除
     * @throws Exception
     */
    @Test
    public void test2() throws Exception {
        User user = new User();
        user.setId(100L);
        user.deleteById();
    }

    /**
     * 修改
     * @throws Exception
     */
    @Test
    public void test3() throws Exception {
        User user = new User(1L,"xiaohui","1",22);
        user.updateById();
    }

    /**
     * 根据id查询
     * @throws Exception
     */
    @Test
    public void test4() throws Exception {
        User user = new User();
        user.setId(100L);
        User dbUser = user.selectById();
        System.out.println(dbUser);
    }


    /**
     * 查询全部
     * @throws Exception
     */
    @Test
    public void test5() throws Exception {
        User user = new User();
        List<User> userList = user.selectAll();
        for (User dbUser : userList) {
            System.out.println(dbUser);
        }
    }

    /**
     * 根据Wrapper条件查询
     * @throws Exception
     */
    @Test
    public void test6() throws Exception {

        QueryWrapper<User> wrapper = Wrappers.query();
        wrapper.like("name","a");

        User user = new User();
        
        // 根据条件查询
        List<User> userList = user.selectList(wrapper);
        for (User dbUser : userList) {
            System.out.println(dbUser);
        }
    }

    /**
     * 分页查询
     * @throws Exception
     */
    @Test
    public void test7() throws Exception {

        QueryWrapper<User> wrapper = Wrappers.query();
        wrapper.like("name","a");

        Page<User> page=new Page<>(1,3);
        User user = new User();
       
        // 分页查询
        user.selectPage(page,wrapper);

        List<User> records = page.getRecords();
        for (User record : records) {
            System.out.println(record);
        }
    }
}

3.8 多数据源配置

  • 1)引入多数据源依赖:
<!--MyBatisPlus多数据源依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
    <version>3.5.1</version>
</dependency>
  • 2)准备两个数据库
DROP database IF EXISTS test1;
create database test1;

use test1;

DROP TABLE IF EXISTS user;
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `sex` char(1) DEFAULT NULL COMMENT '性别 0:男 1:女',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `birthday` date DEFAULT NULL COMMENT '生日',
  PRIMARY KEY (`id`)
);

INSERT INTO `user` VALUES (1, 'Jone', '1', 27, '2001-10-04');
INSERT INTO `user` VALUES (2, 'Jack', '0', 20, '1999-10-04');
INSERT INTO `user` VALUES (3, 'Tom', '1', 28, '1996-08-12');

-- -----------------------------------------------

DROP database IF EXISTS test2;
create database test2;

use test2;

DROP TABLE IF EXISTS user;
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL COMMENT '主键ID',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `sex` char(1) DEFAULT NULL COMMENT '性别 0:男 1:女',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `birthday` date DEFAULT NULL COMMENT '生日',
  PRIMARY KEY (`id`)
);

INSERT INTO `user` VALUES (4, 'Sandy', '1', 21, '2001-10-04');
INSERT INTO `user` VALUES (5, 'Billie', '0', 24, '1992-09-07');
INSERT INTO `user` VALUES (6, 'Jackson', '0', 18, '1996-08-12');
  • 3)多数据源配置:
spring:
  datasource:
    dynamic:
      # 没有匹配的数据源时默认匹配的数据源
      primary: master
      # 当没有匹配的数据源是是否采用默认的数据源,
      # true: 严格匹配,如果没有匹配到数据源则抛出异常
      # false(默认值):  如果没有匹配到数据源则使用默认的数据源
      strict: false
      datasource:
        # 数据源的名称(名字任意)
        master:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/test1?serverTimezone=GMT%2b8
          username: root
          password: admin
        # 数据源的名称(名字任意)
        slave:
          driver-class-name: com.mysql.jdbc.Driver
          url: jdbc:mysql://localhost:3306/test2?serverTimezone=GMT%2b8
          username: root
          password: admin
#配置日志
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 4)启动类:
package com.dfbz;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootApplication
@MapperScan("com.dfbz.mapper")
public class MyBatisPlusApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyBatisPlusApplication.class, args);
    }
}
  • 5)实体类:
package com.dfbz.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("user")
public class User extends Model<User> {

    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private String sex;
    private Integer age;
}
  • 6)编写两个Mapper:
package com.dfbz.mapper;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dfbz.entity.User;
import org.springframework.stereotype.Repository;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@DS("master")           // 指定数据源的名称(若指定的数据源不存在则使用默认的)
@Repository
public interface UserMapperMaster extends BaseMapper<User> {
}
package com.dfbz.mapper;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.dfbz.entity.User;
import org.springframework.stereotype.Repository;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@DS("slave")                    // 指定数据源的名称(若指定的数据源不存在则使用默认的)
@Repository
public interface UserMapperSlave extends BaseMapper<User> {
}
  • 7)测试类:
package com.dfbz;

import com.dfbz.entity.User;
import com.dfbz.mapper.UserMapperMaster;
import com.dfbz.mapper.UserMapperSlave;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

/**
 * @author lscl
 * @version 1.0
 * @intro:
 */
@SpringBootTest(classes = MyBatisPlusApplication.class)
@RunWith(SpringRunner.class)
public class Demo01_测试多数据源 {

    @Autowired
    private UserMapperMaster userMapperMaster;

    @Autowired
    private UserMapperSlave userMapperSlave;


    @Test
    public void test1() {

        // 使用的是master数据源
        List<User> userList = userMapperMaster.selectList(null);
        for (User user : userList) {
            System.out.println(user);
        }
    }
    @Test
    public void test2() {
        // 使用slave数据源
        List<User> userList = userMapperSlave.selectList(null);
        for (User user : userList) {
            System.out.println(user);
        }
    }
}
  • 24
    点赞
  • 136
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

緑水長流*z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值