1. Mybatis-Plus概念

1.1 Mybatis-Plus介绍

官⽹: https://mybatis.plus/https://mp.baomidou.com/

Mybatis-Plus介绍

MyBatis-Plus(简称 MP)是⼀个 MyBatis 的增强⼯具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提⾼效率⽽⽣。

1.2 特性

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

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

1.4 框架结构

framework

2. Mybatis-Plus快速⼊⻔

2.1 安装

全新的 MyBatis-Plus 3.0 版本基于 JDK8,提供了 lambda 形式的调⽤,所以安装集成 MP3.0 要求如下:

  • JDK 8+
  • Maven or Gradle
  1. Spring Boot添加依赖

    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.0</version>
    </dependency>
  2. Spring添加依赖

    <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>3.4.0</version>
    </dependency>

引入MyBatis-Plus依赖后,不要再次引入MyBatis和MyBatis-Spring依赖,以避免因版本差异导致的问题。

对于Mybatis整合MP有常常有三种⽤法,分别是Mybatis+MyBatis-Plus、Spring+Mybatis+MyBatis-Plus、Spring
Boot+Mybatis+MyBatis-Plus。

2.2 创建数据库和表

-- 创建测试表
DROP TABLE IF EXISTS tb_user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
-- 插⼊测试数据
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');

2.3 创建Maven工程

<?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.lemon</groupId>
<artifactId>mybatis-plus</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>mybatis-plus-simple</module>
</modules>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
<!-- mybatis-plus插件依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.4.0</version>
</dependency>

<!--Mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>

<!--连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.11</version>
</dependency>

<!--简化bean代码的工具包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>

</project>

2.4 Mybatis + MyBatis-Plus

下⾯演示,通过纯Mybatis与Mybatis-Plus整合。

1. 创建⼦Module

pom.xml:

<?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">
<parent>
<artifactId>mybatis-plus</artifactId>
<groupId>com.lemon</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>mybatis-plus-simple</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

</project>

log4j.properties:

log4j.rootLogger=DEBUG,A1  

log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n

2. Mybatis实现查询User

  1. 编写mybatis-config.xml⽂件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">

    <configuration>

    <properties resource="jdbc.properties"></properties>

    <!--environments: 运行环境-->
    <environments default="development">
    <environment id="development">
    <!--当前的事务事务管理器是JDBC-->
    <transactionManager type="JDBC"></transactionManager>
    <!--数据源信息 POOLED:使用mybatis的连接池-->
    <dataSource type="POOLED">
    <property name="driver" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    </dataSource>
    </environment>
    </environments>

    <!--引入映射配置文件-->
    <mappers>
    <package name="com.lemon.mapper"/>
    </mappers>


    </configuration>
  2. 编写User实体对象

    @Data // getter setter toString
    @NoArgsConstructor //生成无参构造
    @AllArgsConstructor // 生成全参构造
    @TableName("user")
    public class User {

    private Long id;
    private String name;
    private Integer age;
    private String email;

    }
  3. 编写UserMapper接⼝

    public interface UserMapper {
    List<User> findAll();
    }
  4. 编写UserMapper.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">

    <mapper namespace="com.lemon.mapper.UserMapper">
    <insert id="insert" parameterType="com.lemon.pojo.User">
    insert into ....
    </insert>

    <!--查询所有用户信息-->
    <select id="findAll" resultType="com.lemon.pojo.User">
    select * from user
    </select>

    </mapper>
  5. 编写TestMybatis测试⽤例

    public class MPTest {

    @Test
    public void mybatisTest() throws Exception {
    InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);

    SqlSession sqlSession = sqlSessionFactory.openSession();

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = userMapper.findAll();

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

    }

测试结果:

User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)

注:如果实体类名称和表名称不⼀致,可以在实体类上添加注解@TableName("指定数据库表名")

3. Mybatis+MyBatis-Plus实现查询User

  1. 将UserMapper继承BaseMapper,将拥有了BaseMapper中的所有⽅法

    public interface UserMapper extends BaseMapper<User> {
    }
  2. 使⽤MP中的MybatisSqlSessionFactoryBuilder进程构建

    @Test  
    public void mybatisTest() throws Exception {
    InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");

    SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);

    SqlSession sqlSession = sqlSessionFactory.openSession();

    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    // List<User> all = userMapper.findAll();
    List<User> userList = userMapper.selectList(null);
    for (User user : userList) {
    System.out.println(user);
    }
    }

测试:

User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)

注:如果实体类名称和表名称不⼀致,可以在实体类上添加注解@TableName(“指定数据库表名”)
简单说明:
由于使⽤了 MybatisSqlSessionFactoryBuilder进⾏了构建,继承的BaseMapper中的⽅法就载⼊到了SqlSession中,所以就可以直接使⽤相关的⽅法;

如图:

2.5 Spring + Mybatis + MyBatis-Plus

引⼊了Spring框架,数据源、构建等⼯作就交给了Spring管理。

1. 创建⼦Module

<?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">
<parent>
<artifactId>mybatis-plus</artifactId>
<groupId>com.lemon</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>mybatis-plus-spring</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring.version>5.1.6.RELEASE</spring.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>

2. 实现查询User

  1. 编写jdbc.properties

    jdbc.driver=com.mysql.jdbc.Driver  
    jdbc.url=jdbc:mysql://127.0.0.1:3306/mp?serverTimezone=GMT%2B8&useSSL=false
    jdbc.username=root
    jdbc.password=root
  2. 编写applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 引入properties文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--1. 将sqlSessionFactory对象的创建交给spring-->
    <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="configLocation" value="classpath:sqlMapConfig.xml"/>
    </bean>

    <!--2. mapper映射扫描-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.lemon.mapper"/>
    </bean>

    </beans>
  3. 编写User对象以及UserMapper接⼝

@Data // getter setter toString
@NoArgsConstructor //生成无参构造
@AllArgsConstructor // 生成全参构造
@TableName("user")
public class User {

private Long id;
private String name;
private Integer age;
private String email;

}
public interface UserMapper extends BaseMapper<User> {
List<User> findAll();
}
  1. 编写测试⽤例
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:applicationContext.xml")
    public class TestSpringMP {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void test() {

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

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

    }

2.6 SpringBoot + Mybatis + MyBatis-Plus

使⽤SpringBoot将进⼀步的简化MyBatis-Plus的整合,需要注意的是,由于使⽤SpringBoot需要继承parent,所以需要重新创建⼯程,并不是创建⼦Module。

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lemon</groupId>
<artifactId>mybatis-plus-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis-plus-springboot</name>
<description>mybatis-plus-springboot</description>
<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!--简化代码的工具包-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus的springboot支持-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

2. 编写application.properties

spring.datasource.driver-class-name=com.mysql.jdbc.Driver  
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456

3. 编写pojo

@Data // getter setter toString
@NoArgsConstructor //生成无参构造
@AllArgsConstructor // 生成全参构造
@TableName("user")
public class User {

private Long id;
private String name;
private Integer age;
private String email;

}

4. 编写mapper

public interface UserMapper extends BaseMapper<User> {
}

5. 编写启动类

@MapperScan("com.lemon.mapper")
@SpringBootApplication
public class MybatisPlusSpringbootApplication {

public static void main(String[] args) {
SpringApplication.run(MybatisPlusSpringbootApplication.class, args);
}

}

6. 编写测试⽤例

@RunWith(SpringRunner.class)
@SpringBootTest
class MybatisPlusSpringbootApplicationTests {

@Autowired
private UserMapper userMapper;

@Test
void testSelect() {
List<User> userList = userMapper.selectList(null);
for (User user : userList) {
System.out.println(user);
}
}

}

测试结果:

User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)

3. 通⽤CRUD

通过前⾯的学习,我们了解到通过继承BaseMapper就可以获取到各种各样的单表操作,接下来我们将详细讲解这些操作。

3.1 插⼊操作

1. ⽅法定义

/**
* 插⼊⼀条记录
*
* @param entity 实体对象.
*/
int insert(T entity);

2. 测试⽤例

@Test
public void testInsert() {
User user = new User();
user.setAge(18);
user.setName("kobe");
user.setEmail("123@qq.com");

// 返回影响的行数
int result = userMapper.insert(user);
System.out.println(result);
// 由MyBatis-plus生成id并回填
System.out.println("id值为" + user.getId());
}

测试结果:

可以看到,数据已经写⼊到了数据库,但是,id的值不正确,我们期望的是数据库⾃增⻓,实际是MyBatis-Plus⽣成了id的值写⼊到了数据库。

如何设置id的⽣成策略呢?

MyBatis-Plus⽀持的id策略:

// ⽣成ID类型枚举类
@Getter
public enum IdType {
/**
* 数据库ID自增
*/
AUTO(0),
/**
* 该类型为未设置主键类型
*/
NONE(1),
/**
* 用户输入ID
* <p>该类型可以通过自己注册自动填充插件进行填充</p>
*/
INPUT(2),

/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 全局唯一ID (idWorker)
*/
ID_WORKER(3),
/**
* 全局唯一ID (UUID)
*/
UUID(4),
/**
* 字符串全局唯一ID (idWorker 的字符串表示)
*/
ID_WORKER_STR(5);

private final int key;

IdType(int key) {
this.key = key;
}
}

修改User对象:

@Data // getter setter toString
@NoArgsConstructor //生成无参构造
@AllArgsConstructor // 生成全参构造
@TableName("user")
public class User {

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

}

3. @TableField

在MP中通过@TableField注解可以指定字段的⼀些属性,常常解决的问题有2个:

  1. 对象中的属性名和字段名不⼀致的问题(⾮驼峰)
  2. 对象中的属性字段在表中不存在的问题
@Data // getter setter toString
@NoArgsConstructor //生成无参构造
@AllArgsConstructor // 生成全参构造
@TableName("user")
public class User {

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

@TableField(value = "email") // 解决字段名不一致问题
private String mail;

}

其他⽤法,如⼤字段不加⼊查询字段:

@Data // getter setter toString
@NoArgsConstructor //生成无参构造
@AllArgsConstructor // 生成全参构造
@TableName("user")
public class User {

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

@TableField(select = false) ) //查询的时候,不返回该字段的值
private String name;
private Integer age;

@TableField(value = "email") // 解决字段名不一致问题
private String mail;

}

效果:

还有数据表中不存在的字段以及版本号

@Data // getter setter toString
@NoArgsConstructor //生成无参构造
@AllArgsConstructor // 生成全参构造
@TableName("user")
public class User {

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

@TableField(select = true) //查询的时候,不返回该字段的值
private String name;
private Integer age;

@TableField(value = "email") // 解决字段名不一致问题
private String mail;

@TableField(exist = false) // 该字段在数据库表中不存在
private String address;

@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;

}

3.2 更新操作

在MyBatis-Plus中,更新操作有2种,⼀种是根据id更新,另⼀种是根据条件更新。

1. 根据id更新

⽅法定义:

/**
* 根据 ID 修改
*
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);

测试:

@Test
public void testUpdateById() {
User user = new User();
user.setId(6L);
user.setAge(24);

int i = userMapper.updateById(user);
System.out.println(i);
}

结果:

2. 根据条件更新

⽅法定义:

/**
* 根据 whereEntity 条件,更新记录
*
* @param entity
实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,⾥⾯的 entity ⽤于⽣成
where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER)
Wrapper<T> updateWrapper);

测试⽤例:

/*
测试根据条件进行修改
*/
@Test
public void testUpate(){

// 1. 更新的字段
User user = new User();
user.setAge(35);

// 2.更新的条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name", "kobe");

int i = userMapper.update(user, queryWrapper);
System.out.println(i);
}

或者,通过UpdateWrapper进⾏更新:

@Test
public void testUpate2(){

UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", 6).set("age", 40);

int i = userMapper.update(null, updateWrapper);
System.out.println(i);
}

测试结果:

3.3 删除操作

1. deleteById

⽅法定义:

/**
* 根据 ID 删除
*
* @param id 主键ID
*/
int deleteById(Serializable id);

测试⽤例:

/*
根据ID进行删除
*/
@Test
public void testDeleteById() {

int i = userMapper.deleteById(6L);
System.out.println(i);

}

测试结果:

2. deleteByMap

⽅法定义:

/**
* 根据 columnMap 条件,删除记录
*
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object>
columnMap);

测试⽤例:

/*
根据columnMap进行删除
*/
@Test
public void testDeleteByMap(){

HashMap<String, Object> map = new HashMap<>();
map.put("name", "kobe");
map.put("age", 18);

// 将columnMap中的元素设置为删除的条件,多个条件是and的关系
int i = userMapper.deleteByMap(map);
System.out.println(i);
}

测试结果:

3. delete

⽅法定义:

/**
* 根据 entity 条件,删除记录
*
* @param wrapper 实体对象封装操作类(可以为 null)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

测试⽤例:

/*
调用delete进行删除
*/
@Test
public void testDelete(){

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name","kobe1").eq("age",18);

int i = userMapper.delete(queryWrapper);
System.out.println(i);
}

或者如下:(两个是等效的)

/*
调用delete进行删除
*/
@Test
public void testDelete(){

User user = new User();
user.setName("kobe2");
user.setAge(18);

QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
// queryWrapper.eq("name","kobe1").eq("age",18);

int i = userMapper.delete(queryWrapper);
System.out.println(i);
}

测试结果:

deleteBatchIds

⽅法定义:

/**
* 删除(根据ID 批量删除)
*
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);

测试⽤例:

/*
调用deleteBatchIds进行批量删除
*/
@Test
public void testDeleteBatchIds(){

int i = userMapper.deleteBatchIds(Arrays.asList(6l, 7l, 8l, 9l));
System.out.println(i);
}

测试结果:

3.4 查询操作

MyBatis-Plus提供了多种查询操作,包括根据id查询、批量查询、查询单条数据、查询列表、分⻚查询等操作。

1. selectById

⽅法定义:

/**
* 根据 ID 查询
*
* @param id 主键ID
*/
T selectById(Serializable id);

测试⽤例:

/*
根据ID进行查询
*/
@Test
public void testSelectById(){
User user = userMapper.selectById(2L);
System.out.println(user);
}

测试结果:

result = User(id=2, name=Jack, age=20, email=test2@baomidou.com)

2. selectBatchIds

⽅法定义:

/**
* 查询(根据ID 批量查询)
*
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends
Serializable> idList);

测试⽤例:

/*
根据ID进行批量查询
*/
@Test
public void testSelectBatchIds(){

List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2l, 3l, 4l, 5l));
for (User user : users) {
System.out.println(user);
}
}

测试结果:

3. selectOne

⽅法定义:

/**
* 根据 entity 条件,查询⼀条记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

测试⽤例:

/*
测试selectOne
*/
@Test
public void testSelectOne(){

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("name","Jack");

// 根据条件查询一条记录,如果查询结果超过一条,会报错
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);

}

测试结果:

User(id=2, name=Jack, age=20, email=test2@baomidou.com)

4. selectCount

⽅法定义:

/**
* 根据 Wrapper 条件,查询总记录数
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

测试⽤例:

/*
根据wrapper条件,查询总记录数
*/
@Test
public void testSelectCount(){

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",18); // 查询年龄大于18的

// 根据条件查询一条记录,如果查询结果超过一条,会报错
Integer count = userMapper.selectCount(queryWrapper);

System.out.println(count);

}

测试结果:

5. selectList

⽅法定义:

/**
* 根据 entity 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

测试用例:

@Test
public void testSelectList() {
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.gt("age", 18); //年龄⼤于18

//根据条件查询数据
List<User> users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println("user = " + user);
}
}

测试结果:

6. selectPage

⽅法定义:

/**
* 根据 entity 条件,查询全部记录(并翻⻚)
*
* @param page
分⻚查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T>
queryWrapper);

配置分⻚插件:

@Configuration
public class MybatisPlusConfig {

/*
分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}

}

测试⽤例:

/*
分页查询
*/
@Test
public void testSelectPage() {

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", 18); // 查询年龄大于18的

// 第一个参数:当前页 第二个参数:每页显示条数
Page<User> page = new Page<>(1, 2);

IPage<User> userIPage = userMapper.selectPage(page, queryWrapper);
System.out.println("总条数" + userIPage.getTotal());
System.out.println("总页数" + userIPage.getPages());

System.out.println("分页数据" + userIPage.getRecords());

}

测试结果:

3.5 SQL注⼊的原理

前⾯我们已经知道,MP在启动后会将BaseMapper中的⼀系列的⽅法注册到meppedStatements中,那么究竟是如何注⼊的呢?流程⼜是怎么样的?下⾯我们将⼀起来分析下。

在MP中,ISqlInjector负责SQL的注⼊⼯作,它是⼀个接⼝,AbstractSqlInjector是它的实现类。

在AbstractSqlInjector中,主要是由inspectInject()⽅法进⾏注⼊的,如下:

public abstract class AbstractSqlInjector implements ISqlInjector {

@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
Class<?> modelClass = extractModelClass(mapperClass);
if (modelClass != null) {
String className = mapperClass.toString();
Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
if (!mapperRegistryCache.contains(className)) {
List<AbstractMethod> methodList = this.getMethodList();
if (CollectionUtils.isNotEmpty(methodList)) {
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
// 循环注入自定义方法
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
} else {
logger.debug(mapperClass.toString() + ", No effective injection method was found.");
}
mapperRegistryCache.add(className);
}
}
}
}

在实现⽅法中,methodList.forEach(m -> m.inject(builderAssistant, mapperClass,modelClass, tableInfo));是关键,循环遍历⽅法,进⾏注⼊。

最终调⽤抽象⽅法injectMappedStatement进⾏真正的注⼊:

/**
* 注⼊⾃定义 MappedStatement
*
* @param mapperClass mapper 接⼝
* @param modelClass mapper 泛型
* @param tableInfo 数据库表反射信息
* @return MappedStatement
*/
public abstract MappedStatement injectMappedStatement(Class<?>
mapperClass, Class<?> modelClass, TableInfo tableInfo);

查看该⽅法的实现:

以SelectById为例查看:

/**
* 根据ID 查询一条数据
*
* @author hubin
* @since 2018-04-06
*/
public class SelectById extends AbstractMethod {

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.LOGIC_SELECT_BY_ID;
SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(),
sqlSelectColumns(tableInfo, false),
tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
tableInfo.getLogicDeleteSql(true, false)), Object.class);
return this.addSelectMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource, modelClass, tableInfo);
}
}

可以看到,⽣成了SqlSource对象,再将SQL通过addSelectMappedStatement⽅法添加到meppedStatements中。

4. 配置

在MP中有⼤量的配置,其中有⼀部分是Mybatis原⽣的配置,另⼀部分是MP的配置,详情:使用配置 | MyBatis-Plus (baomidou.com)

下⾯我们对常⽤的配置做讲解。

4.1 基本配置

1. configLocation

  • 类型:String
  • 默认值:null

MyBatis 配置文件位置,如果您有单独的 MyBatis 配置,请将其路径配置到 configLocation 中.MyBatis Configuration 的具体内容请参考MyBatis 官方文档(opens new window)

  • Spring Boot:

    mybatis-plus.config-location = classpath:mybatis-config.xml
  • Spring MVC:

    <bean id="sqlSessionFactory"
    class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"
    >
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

2. mapperLocations

  • 类型:String[]
  • 默认值:["classpath*:/mapper/**/*.xml"]

MyBatis Mapper 所对应的 XML 文件位置,如果您在 Mapper 中有自定义方法(XML 中有自定义实现),需要进行该配置,告诉 Mapper 所对应的 XML 文件位置

注意
Maven 多模块项目的扫描路径需以 classpath*: 开头 (即加载多个 jar 包下的 XML 文件)

  • Spring Boot:

    mybatis-plus.mapper-locations = classpath*:mybatis/*.xml
  • Spring MVC:

    <bean id="sqlSessionFactory"
    class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"
    >
    <property name="mapperLocations" value="classpath*:mybatis/*.xml"/>
    </bean>

3. typeAliasesPackage

  • 类型:String
  • 默认值:null

MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使用类名,而不用使用全限定的类名(即 XML 中调用的时候不用包含包名)

  • Spring Boot:

    mybatis-plus.type-aliases-package = com.lemon.pojo
  • Spring MVC:

    <bean id="sqlSessionFactory"
    class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"
    >
    <property name="typeAliasesPackage"
    value="com.baomidou.mybatisplus.samples.quickstart.entity"/>
    </bean>

4.2 进阶配置

本部分(Configuration)的配置大都为 MyBatis 原生支持的配置,这意味着您可以通过 MyBatis XML 配置文件的形式进行配置。

1. mapUnderscoreToCamelCase

  • 类型:boolean
  • 默认值:true

是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射。

注意
此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body
如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名

示例(SpringBoot)

#关闭⾃动驼峰映射,该参数不能和mybatis-plus.config-location同时存在
mybatis-plus.configuration.map-underscore-to-camel-case=false

2. cacheEnabled

  • 类型:boolean
  • 默认值:true

开启 Mybatis 二级缓存,默认为 true。

SpringBoot:

mybatis-plus.configuration.cache-enabled=false

4.3 DB 策略配置

1. idType

  • 类型:com.baomidou.mybatisplus.annotation.IdType
  • 默认值:ASSIGN_ID

全局默认主键类型

  • SpringBoot:

    // 设置全局主键的生成策略-自增
    mybatis-plus.global-config.db-config.id-type=auto
  • SpringMVC:

    <!--这⾥使⽤MP提供的sqlSessionFactory,完成了Spring与MP的整合-->
    <bean id="sqlSessionFactory"
    class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"
    >
    <property name="dataSource" ref="dataSource"/>
    <property name="globalConfig">
    <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
    <property name="dbConfig">
    <bean
    class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
    <property name="idType" value="AUTO"/>
    </bean>
    </property>
    </bean>
    </property>
    </bean>

2. tablePrefix

  • 类型:String
  • 默认值:null

表名前缀

  • SpringBoot:

    mybatis-plus.global-config.db-config.table-prefix=tb_
  • SpringMVC:

    <bean id="sqlSessionFactory"
    class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"
    >
    <property name="dataSource" ref="dataSource"/>
    <property name="globalConfig">
    <bean class="com.baomidou.mybatisplus.core.config.GlobalConfig">
    <property name="dbConfig">
    <bean
    class="com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig">
    <property name="idType" value="AUTO"/>
    <property name="tablePrefix" value="tb_"/>
    </bean>
    </property>
    </bean>
    </property>
    </bean>

5. 条件构造器

在MyBatis-Plus中,Wrapper接⼝的实现类关系如下:

可以看到,AbstractWrapperAbstractChainWrapper是重点实现,接下来我们重点学习AbstractWrapper以及其⼦类。

说明:
QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的⽗类⽤于⽣成 sql 的 where 条件, entity 属性也⽤于⽣成 sql 的 where 条件。

注意: entity ⽣成的 where 条件与 使⽤各个 api ⽣成的 where 条件没有任何关联⾏为

5.1 allEq

allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
  • 全部eq(或个别isNull) 个别参数说明: params : key为数据库字段名,value为字段值
    null2IsNull : 为true则在mapvaluenull时调用 isNull 方法,为false时则忽略valuenull
  • 例1: allEq({id:1,name:"老王",age:null})—>id = 1 and name = '老王' and age is null
  • 例2: allEq({id:1,name:"老王",age:null}, false)—>id = 1 and name = '老王'
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)

个别参数说明:

filter : 过滤函数,是否允许字段传入比对条件中
paramsnull2IsNull : 同上

  • 例1: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})—>name = '老王' and age is null
  • 例2: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)—>name = '老王'

测试用例:

/*
测试条件构建器 allEq
*/
@Test
public void testAllEq() {

QueryWrapper<User> queryWrapper = new QueryWrapper<>();

// 构建map
Map<String, Object> map = new HashMap<>();
map.put("name", "jack");
map.put("age", null);

// WHERE name = ? AND age IS NULL
// queryWrapper.allEq(map);

// WHERE name = ?
// queryWrapper.allEq(map, false);

// SELECT id,name,age,email AS mail,user_name FROM tb_user
// queryWrapper.allEq(false, map, true);

// WHERE age IS NULL
queryWrapper.allEq((k,v) -> k.equals("name"),map);

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

}

5.2 基本比较操作

1. eq

eq(R column, Object val)
eq(boolean condition, R column, Object val)
  • 等于 =
  • 例: eq("name", "老王")—>name = '老王'

2. ne

ne(R column, Object val)
ne(boolean condition, R column, Object val)
  • 不等于 <>
  • 例: ne("name", "老王")—>name <> '老王'

3. gt

gt(R column, Object val)
gt(boolean condition, R column, Object val)
  • 大于 >
  • 例: gt("age", 18)—>age > 18

4. ge

ge(R column, Object val)
ge(boolean condition, R column, Object val)
  • 大于等于 >=
  • 例: ge("age", 18)—>age >= 18

5. lt

lt(R column, Object val)
lt(boolean condition, R column, Object val)
  • 小于 <
  • 例: lt("age", 18)—>age < 18

6. le

le(R column, Object val)
le(boolean condition, R column, Object val)
  • 小于等于 <=
  • 例: le("age", 18)—>age <= 18

7. between

between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
  • BETWEEN 值1 AND 值2
  • 例: between("age", 18, 30)—>age between 18 and 30

8. notBetween

notBetween(R column, Object val1, Object val2)
notBetween(boolean condition, R column, Object val1, Object val2)
  • NOT BETWEEN 值1 AND 值2
  • 例: notBetween("age", 18, 30)—>age not between 18 and 30

9. in

in(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)
  • 字段 IN (value.get(0), value.get(1), …)
  • 例: in("age",{1,2,3})—>age in (1,2,3)
in(R column, Object... values)
in(boolean condition, R column, Object... values)
  • 字段 IN (v0, v1, …)
  • 例: in("age", 1, 2, 3)—>age in (1,2,3)

10. notIn

notIn(R column, Collection<?> value)
notIn(boolean condition, R column, Collection<?> value)
  • 字段 NOT IN (value.get(0), value.get(1), …)
  • 例: notIn("age",{1,2,3})—>age not in (1,2,3)
notIn(R column, Object... values)
notIn(boolean condition, R column, Object... values)
  • 字段 NOT IN (v0, v1, …)
  • 例: notIn("age", 1, 2, 3)—>age not in (1,2,3)

5.3 模糊查询

1. like

like(R column, Object val)
like(boolean condition, R column, Object val)
  • LIKE ‘%值%’
  • 例: like("name", "王")—>name like '%王%'

2. notLike

notLike(R column, Object val)
notLike(boolean condition, R column, Object val)
  • NOT LIKE ‘%值%’
  • 例: notLike("name", "王")—>name not like '%王%'

3. likeLeft

likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)
  • LIKE ‘%值’
  • 例: likeLeft("name", "王")—>name like '%王'

4. likeRight

likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)
  • LIKE ‘值%’
  • 例: likeRight("name", "王")—>name like '王%'

测试用例:

/*
模糊查询
*/
@Test
public void testWrapperLike(){

QueryWrapper<User> queryWrapper = new QueryWrapper<>();

queryWrapper.like("name","m");

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

}

5.4 排序

1. orderBy

orderBy(boolean condition, boolean isAsc, R... columns)
  • 排序:ORDER BY 字段, …
  • 例: orderBy(true, true, "id", "name")—>order by id ASC,name ASC

2. orderByAsc

orderByAsc(R... columns)
orderByAsc(boolean condition, R... columns)
  • 排序:ORDER BY 字段, … ASC
  • 例: orderByAsc("id", "name")—>order by id ASC,name ASC

3. orderByDesc

orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)
  • 排序:ORDER BY 字段, … DESC
  • 例: orderByDesc("id", "name")—>order by id DESC,name DESC

测试用例:

/*
排序查询、逻辑查询、select
*/
@Test
public void testWrapper2(){

QueryWrapper<User> queryWrapper = new QueryWrapper<>();

queryWrapper.orderByDesc("age");

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

}

5.5 逻辑查询

1. or

or()
or(boolean condition)
  • 拼接 OR

    注意事项:
    主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)

  • 例: eq("id",1).or().eq("name","老王")—>id = 1 or name = '老王'
or(Consumer<Param> consumer)
or(boolean condition, Consumer<Param> consumer)
  • OR 嵌套
  • 例: or(i -> i.eq("name", "李白").ne("status", "活着"))—>or (name = '李白' and status <> '活着')

2. and

and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)
  • AND 嵌套
  • 例: and(i -> i.eq("name", "李白").ne("status", "活着"))—>and (name = '李白' and status <> '活着')

测试用例:

@Test
public void testWrapper2(){

QueryWrapper<User> queryWrapper = new QueryWrapper<>();

// queryWrapper.orderByDesc("age");

queryWrapper.eq("name","jack").or().eq("age",28);


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

}

5.7 select

select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
  • 设置查询字段

    说明:
    以上方法分为两类.
    第二类方法为:过滤查询字段(主键除外),入参不包含 class 的调用前需要wrapper内的entity属性有值! 这两类方法重复调用以最后一次为准

  • 例: select("id", "name", "age")
  • 例: select(i -> i.getProperty().startsWith("test"))

6. ActiveRecord 模式

ActiveRecord(简称AR)⼀直⼴受动态语⾔( PHP 、 Ruby 等)的喜爱,⽽ Java 作为准静态语⾔,对于 ActiveRecord 往往只能感叹其优雅,所以我们也在 AR 道路上进⾏了⼀定的探索,希望⼤家能够喜欢。

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

ActiveRecord的主要思想是:

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

6.1 操作步骤

说明:

  • 实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 需要项目中已注入对应实体的BaseMapper

1. 继承 Model

class User extends Model<User>{
// fields...
}

2. 调用CRUD方法

User user = new User();
user.insert();
user.selectAll();
user.updateById();
user.deleteById();
// ...
@Data // getter setter toString
@NoArgsConstructor //生成无参构造
@AllArgsConstructor // 生成全参构造
@TableName("user")
public class User extends Model<User> {

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

@TableField(select = true) //查询的时候,不返回该字段的值
private String name;
private Integer age;

@TableField(value = "email") // 解决字段名不一致问题
private String mail;

@TableField(exist = false) // 该字段在数据库表中不存在
private String address;

@Version
@TableField(fill = FieldFill.INSERT)
private Integer version;

}

6.2 根据主键查询

/*
在AR模式下,完成根据主键查询
*/
@Test
public void testARSelectById(){

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

User user1 = user.selectById();
System.out.println(user1);

}

6.3 新增数据

/*
在AR模式下,完成添加操作
*/
@Test
public void testARInsert(){

User user = new User();
user.setName("墨竹");
user.setAge(18);
user.setMail("mozhu@lemon.com");

boolean insert = user.insert();
System.out.println(insert);

}

测试结果:

6.4 更新操作

/*
在AR模式下,完成更新操作
*/
@Test
public void testARUpate(){


User user = new User();
User user1 = user.selectById(1L);

user.setId(6L);
user.setName("青梅");
user.setVersion(user1.getVersion());

boolean insert = user.updateById();
System.out.println(insert);

}

结果:

6.5 删除操作

/*
在AR模式下,完成删除操作
*/
@Test
public void testARDelete(){

User user = new User();
//user.setId(6L);

boolean b = user.deleteById(6L);
System.out.println(b);
}

6.6 根据条件查询

/*
在AR模式下,根据条件进行查询
*/
@Test
public void testARFindByWrapper(){

QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age","20");

User user = new User();
List<User> users = user.selectList(queryWrapper);
for (User user1 : users) {
System.out.println(user1);
}

}

7. 插件

7.1 MyBatis的插件机制

MyBatis 允许你在已映射语句执⾏过程中的某⼀点进⾏拦截调⽤。默认情况下,MyBatis 允许使⽤插件来拦截的⽅法调⽤包括:

  1. Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  2. ParameterHandler (getParameterObject, setParameters)
  3. ResultSetHandler (handleResultSets, handleOutputParameters)
  4. StatementHandler (prepare, parameterize, batch, update, query)

我们看到了可以拦截Executor接⼝的部分⽅法,⽐如update,query,commit,rollback等⽅法,还有
其他接⼝的⼀些⽅法等。
总体概括为:

  1. 拦截执⾏器的⽅法
  2. 拦截参数的处理
  3. 拦截结果集的处理
  4. 拦截Sql语法构建的处理

拦截器示例

@Intercepts({//注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这个拦截器
@Signature(type = StatementHandler.class, //这是指拦截哪个接口
method = "prepare",//这个接口内的哪个方法名,不要拼错了
args = {Connection.class, Integer.class}),// 这是拦截的方法的入参,按顺序写到这,不要多也不要少,如果方法重载,可是要通过方法名和入参来确定唯一的
})
public class MyPlugin implements Interceptor {
private final Logger logger = LoggerFactory.getLogger(this.getClass()); //这里是每次执行操作的时候,都会进行这个拦截器的方法内

/**
* 拦截方法:只要被拦截的目标对象的目标方法被执行时,每次都会执行 intercept 方法
*/
@Override
public Object intercept(Invocation invocation) throws Throwable { //增强逻辑
System.out.println("对方法进行了增强....");
return invocation.proceed(); //执行原方法
}

/**
* 主要是为了把这个拦截器生成一个代理放到拦截器链中
*/
@Override
public Object plugin(Object target) {
System.out.println("将要包装的目标对象:" + target);
return Plugin.wrap(target, this);
}

/**
* 获取配置文件的参数
* 插件初始化的时候调用,也只调用一次,插件配置的属性从这里设置进来
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的初始化参数:" + properties);
}
}

注⼊到Spring容器:

/**
* ⾃定义拦截器
*/
@Bean
public MyInterceptor myInterceptor(){
return new MyInterceptor();
}

或者通过xml配置,mybatis-config.xml:

<!--拦截器-->
<plugins>
<plugin interceptor="com.lemon.plugin.MyPlugin">
<!--配置参数-->
<property name="name" value="syj"/>
</plugin>
</plugins>

7.2 性能分析插件

在MP中提供了对SQL执⾏的分析的插件,可⽤作阻断全表更新、删除的操作,注意:该插件仅适⽤于开发环境,不适⽤于⽣产环境。

SpringBoot配置Bean:

/*
性能分析插件
*/
@Bean
public PerformanceInterceptor performanceInterceptor(){

PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
// 设置sql语句的最大执行时间
performanceInterceptor.setMaxTime(100);
// 设置sql是否格式化显示
performanceInterceptor.setFormat(true);

return performanceInterceptor;
}

xml⽅式

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<plugins>
<!-- SQL 执⾏性能分析,开发环境使⽤,线上不推荐。 maxTime 指的是 sql 最⼤执⾏时
⻓ -->
<plugin
interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor
">
<property name="maxTime" value="100" />
<!--SQL是否格式化 默认false-->
<property name="format" value="true" />
</plugin>
</plugins>
</configuration>

执⾏结果:

可以看到,执⾏时间为35ms。如果将maxTime设置为1,那么,该操作会抛出异常。

Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: The
SQL execution time is too large, please optimize !
at
com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:4
9)
at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38)
................

7.3 乐观锁插件

1. 主要适⽤场景

意图:

当要更新⼀条记录的时候,希望这条记录没有被别⼈更新。

乐观锁实现⽅式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执⾏更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

2. 插件配置

spring xml:

<bean
class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"
/>

spring boot:

@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}

3. 注解实体字段

需要为实体字段添加@Version注解。

第⼀步,为表添加version字段,并且设置初始值为1;

ALTER TABLE `tb_user`
ADD COLUMN `version` int(10) NULL AFTER `email`;
UPDATE `tb_user` SET `version`='1';

第⼆步,为User实体对象添加version字段,并且添加@Version注解:

@Version
private Integer version;

4. 测试

测试用例:

@Test
public void testUpdateById() {
User user = new User();
user.setId(6L);
user.setAge(24);
user.setVersion(1); //获取到version为1

int i = userMapper.updateById(user);
System.out.println(i);
}

测试结果:

可以看到,更新的条件中有version条件,并且更新的version为2。

如果再次执⾏,更新则不成功。这样就避免了多⼈同时更新时导致数据的不⼀致。

5. 特别说明

  • ⽀持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity 中
  • 仅⽀持 updateById(id)update(entity, wrapper) ⽅法
  • 在 update(entity, wrapper) ⽅法下, wrapper 不能复⽤!!!

8. SQL注入器

我们已经知道,在MP中,通过AbstractSqlInjector将BaseMapper中的⽅法注⼊到了Mybatis容器,这样这些⽅法才可以正常执⾏。

那么,如果我们需要扩充BaseMapper中的⽅法,⼜该如何实现呢?

下⾯我们以扩展findAll⽅法为例进⾏学习。

8.1 编写MyBaseMapper

/*
通用mapper接口,以后创建其他mapper接口时,不再继承BaseMapper,而是继承MyBaseMapper
*/
public interface MyBaseMapper<T> extends BaseMapper<T> {

public List<T> findAll();

}

其他的Mapper都可以继承该Mapper,这样实现了统⼀的扩展。

如:

public interface UserMapper extends MyBaseMapper<User> {
User findById(Long id);
}

8.2 编写MySqlInjector

如果直接继承AbstractSqlInjector的话,原有的BaseMapper中的⽅法将失效,所以我们选择继承DefaultSqlInjector进⾏扩展。

/*
自定义sql注入器
*/
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList() {

List<AbstractMethod> methodList = super.getMethodList();
// 扩充自定义方法
methodList.add(new FindAll());
return methodList;
}
}

8.3 编写FindAll

public class FindAll extends AbstractMethod {

@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {

String sql = "select * from " + tableInfo.getTableName();

SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return this.addSelectMappedStatement(mapperClass, "findAll", sqlSource, modelClass, tableInfo);
}
}

8.4 注册到Spring容器

/*
自定义的sql注入器
*/
@Bean
public MySqlInjector mySqlInjector(){
return new MySqlInjector();
}

8.5 测试

测试用例:

@Test
public void testFindAll(){

List<User> all = userMapper.findAll();
for (User user : all) {
System.out.println(user);
}
}

测试结果:

⾄此,我们实现了全局扩展SQL注⼊器。

9. ⾃动填充功能

有些时候我们可能会有这样的需求,插⼊或者更新数据时,希望有些字段可以⾃动填充数据,⽐如密码、version等。在MP中提供了这样的功能,可以实现⾃动填充。

9.1 添加@TableField注解

@TableField(fill = FieldFill.INSERT) //插⼊数据时进⾏填充
private String version;

为email添加⾃动填充功能,在新增数据时有效。

FieldFill提供了多种模式选择:

9.2 编写MyMetaObjectHandler

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
Object version = getFieldValByName("version", metaObject);
if (version == null) {
// 该属性为空,可以进行填充
setFieldValByName("version", 1, metaObject);
}
}

@Override
public void updateFill(MetaObject metaObject) {

}
}

9.3 测试

@Test
public void testInsert() {
User user = new User();
user.setAge(18);
user.setName("kobe");
user.setMail("123@qq.com");

// 返回影响的行数
int result = userMapper.insert(user);
System.out.println(result);
// 由MyBatis-plus生成id并回填
System.out.println("id值为" + user.getId());
}

测试结果:

注意事项:

  • 填充原理是直接给entity的属性设置值!!!
  • 注解则是指定该属性在对应情况下必有值,如果无值则入库会是null
  • MetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充
  • 字段必须声明TableField注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段
  • 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component@Bean注入
  • 要想根据注解FieldFill.xxx字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法
  • 不需要根据任何来区分可以使用父类的fillStrategy方法
  • update(T t,Wrapper updateWrapper)时t不能为空,否则自动填充失效

10. 逻辑删除

开发系统时,有时候在实现功能时,删除操作需要实现逻辑删除,所谓逻辑删除就是将数据标记为删除,⽽并⾮真正的物理删除(⾮DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查询到。这样做的⽬的就是避免数据被真正的删除。

MP就提供了这样的功能,⽅便我们使⽤,接下来我们⼀起学习下。

说明:

只对自动注入的 sql 起效:

  • 插入: 不作限制
  • 查找: 追加 where 条件过滤掉已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
  • 更新: 追加 where 条件防止更新到已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段
  • 删除: 转变为 更新

例如:

  • 删除: update user set deleted=1 where id = 1 and deleted=0
  • 查找: select id,name,deleted from user where deleted=0

字段类型支持说明:

  • 支持所有数据类型(推荐使用 Integer,Boolean,LocalDateTime)
  • 如果数据库字段使用datetime,逻辑未删除值和已删除值支持配置为字符串null,另一个值支持配置为函数来获取值如now()

附录:

  • 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
  • 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。

10.1 修改表结构

为tb_user表增加deleted字段,⽤于表示数据是否被删除,1代表删除,0代表未删除。

ALTER TABLE `tb_user`
ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '1代表删除,0代表未删除'
AFTER `version`;

同时,也修改User实体,增加deleted属性并且添加@TableLogic注解:

@TableLogic
private Integer deleted;

10.2 配置

application.properties:

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

10.3 测试

@Test
public void testDeleteById() {

int i = userMapper.deleteById(6L);
System.out.println(i);

}

测试结果:

测试查询:

@Test
public void testSelectById() {
User user = userMapper.selectById(6);
System.out.println(user);
}

执⾏的SQL:

可⻅,已经实现了逻辑删除。

11. 代码生成器

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

11.1 创建⼯程

pom.xml

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lemon</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis-plus-generator</name>
<description>mybatis-plus-generator</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!--mybatis-plus的springboot支持-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

11.2 生成代码

/**
* <p>
* mysql 代码生成器演示例子
* </p>
*/
public class MysqlGenerator {

/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}

/**
* RUN THIS
*/
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();

// 全局配置
GlobalConfig gc = new GlobalConfig();
final String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("zimu");
gc.setOpen(false);
mpg.setGlobalConfig(gc);

// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&useSSL=false&characterEncoding=utf8");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);

// 包配置
final PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模块名"));
pc.setParent("com.lemon.mp.generator");
mpg.setPackageInfo(pc);

// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
List<FileOutConfig> focList = new ArrayList<FileOutConfig>();
focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输入文件名称
return projectPath + "/lemon-mp-generator/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
mpg.setTemplate(new TemplateConfig().setXml(null));

// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// strategy.setSuperEntityClass("com.baomidou.mybatisplus.samples.generator.common.BaseEntity");
strategy.setEntityLombokModel(true);
// strategy.setSuperControllerClass("com.baomidou.mybatisplus.samples.generator.common.BaseController");
strategy.setInclude(scanner("表名"));
strategy.setSuperEntityColumns("id");
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
// 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}

}

11.3 测试

代码已经生成:

12. MybatisX 快速开发插件

MybatisX 是⼀款基于 IDEA 的快速开发插件,为效率⽽⽣。

安装⽅法:打开 IDEA,进⼊ File -> Settings -> Plugins -> Browse Repositories,输⼊ mybatisx 搜索并安装。

功能:

  • Java 与 XML 调回跳转
  • Mapper ⽅法⾃动⽣成 XML