Mybatis-Plus 介绍 & CRUD & 条件构造器 & 自动填充 & ActiveRecord


Mybatis-Plus 介绍

简介

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

image

愿景

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


特性

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作。
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求。
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错。
  • 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库。
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题。
  • 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动。
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作。
  • 支持自定义全局通用操作:支持全局通用方法注入(Write once, use anywhere )。
  • 支持关键词自动转义:支持数据库关键词(order、key、...)自动转义,还可自定义关键词。
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等你来使用。
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询。
  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询。
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作。
  • 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击。

架构

image


作者

Mybatis-Plus 是由 baomidou(苞米豆)组织开发并且开源的,目前该组织大概有 30 人左右。

码云地址:https://gitee.com/organizations/baomidou

image


快速入门

对于 Mybatis 整合 MP,有三种常用用法,分别是:

  1. Mybatis + MP
  2. Spring + Mybatis + MP
  3. Spring Boot + Mybatis + MP

表数据准备

-- 创建测试表
CREATE TABLE `tb_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user_name` varchar(20) NOT NULL COMMENT '用户名',
  `password` varchar(20) NOT NULL COMMENT '密码',
  `name` varchar(30) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `email` varchar(50) DEFAULT NULL COMMENT '邮箱',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

-- 插入测试数据
INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('1', 'zhangsan', '123456', '张三', '18', 'test1@itcast.cn');
INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('2', 'lisi', '123456', '李四', '20', 'test2@itcast.cn');
INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('3', 'wangwu', '123456', '王五', '28', 'test3@itcast.cn');
INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('4', 'zhaoliu', '123456', '赵六', '21', 'test4@itcast.cn');
INSERT INTO `tb_user` (`id`, `user_name`, `password`, `name`, `age`, `email`) VALUES ('5', 'sunqi', '123456', '孙七', '24', 'test5@itcast.cn');

Mybatis 整合 MP

导入依赖

    <dependencies>
        <!-- mybatis-plus插件依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</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>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.11</version>
        </dependency>
        <!--简化bean代码的工具包-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
            <version>1.18.4</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.4</version>
        </dependency>
    </dependencies>

JDBC 配置文件

jdbc.properties

jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/world?serverTimezone=UTC
jdbc.username=root
jdbc.password=admin

Mybatis 配置文件

Mybatis-config.xml

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

<!-- 根标签 -->
<configuration>

    <!-- 引入数据库连接的配置文件 -->
    <properties resource="jdbc.properties"/>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driverClassName}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>

</configuration>

User 实体类

package entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data  // 代替getter、setter方法
@NoArgsConstructor  // 代替无参构造方法
@AllArgsConstructor  // 代替全参构造方法
@TableName("tb_user")  // 映射数据表名
public class User {

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

}

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="mapper.UserMapper">

<!--    <select id="findAll" resultType="entity.User">-->
<!--        select * from tb_user-->
<!--    </select>-->

</mapper>

测试类

import com.baomidou.mybatisplus.core.MybatisSqlSessionFactoryBuilder;
import entity.User;
import mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MybatisPlusTest {

    @Test
    void testDemo1() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 这里使用的是MP中的MybatisSqlSessionFactoryBuilder
        SqlSessionFactory sqlSessionFactory;
        sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        // 可以调用BaseMapper中定义的方法
        List<User> list = userMapper.selectList(null);
        for (User user : list) {
            System.out.println(user);
        }
    }
}

执行结果

...
[main] [mapper.UserMapper.selectList]-[DEBUG] ==>  Preparing: SELECT id,user_name,password,name,age,email FROM tb_user 
[main] [mapper.UserMapper.selectList]-[DEBUG] ==> Parameters: 
[main] [mapper.UserMapper.selectList]-[DEBUG] <==      Total: 5
User(id=1, userName=zhangsan, password=123456, name=张三, age=18, email=test1@itcast.cn)
User(id=2, userName=lisi, password=123456, name=李四, age=20, email=test2@itcast.cn)
User(id=3, userName=wangwu, password=123456, name=王五, age=28, email=test3@itcast.cn)
User(id=4, userName=zhaoliu, password=123456, name=赵六, age=21, email=test4@itcast.cn)
User(id=5, userName=sunqi, password=123456, name=孙七, age=24, email=test5@itcast.cn)

SpringBoot + Mybatis + MP

使用 SpringBoot 可以进一步地简化 MP 的整合。

导入依赖

        <!-- springboot相关 -->
        <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.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </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>8.0.11</version>
        </dependency>

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

application.properties

spring.application.name = mp-springboot

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/world?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=admin

实体类

package com.example.apiweb.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {

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

}

mapper 接口

package com.example.apiweb.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.apiweb.entity.User;

public interface UserMapper extends BaseMapper<User> {

}

启动类

package com.example.apiweb;

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

@MapperScan("com.example.apiweb.mapper")  // 设置mapper接口的扫描包
@SpringBootApplication
public class ApiWebApplication {

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

}

测试类

package com.example.apiweb;

import com.example.apiweb.entity.User;
import com.example.apiweb.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
class ApiWebApplicationTests {

    @Autowired
    private UserMapper userMapper;

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

}

测试结果

User(id=1, userName=zhangsan, password=123456, name=张三, age=18, email=test1@itcast.cn)
User(id=2, userName=lisi, password=123456, name=李四, age=20, email=test2@itcast.cn)
User(id=3, userName=wangwu, password=123456, name=王五, age=28, email=test3@itcast.cn)
User(id=4, userName=zhaoliu, password=123456, name=赵六, age=21, email=test4@itcast.cn)
User(id=5, userName=sunqi, password=123456, name=孙七, age=24, email=test5@itcast.cn)

通用 CRUD

插入操作

方法定义

    /**
     * 插入一条记录
     *
     * @param entity 实体对象
     */
    int insert(T entity);

MP 支持的 id 自增策略

package com.baomidou.mybatisplus.annotation;

import lombok.Getter;

/**
 * 生成ID类型枚举类
 *
 * @author hubin
 * @since 2015-11-10
 */
@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;
    }
}

实体类配置 id 自增策略

package com.example.apiweb.entity;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {

    @TableId(type= IdType.AUTO)  // 数据库主键自增
    private Long id;
    private String userName;
    private String password;
    private String name;
    private Integer age;
    private String email;

}

测试类

    @Test
    void testInsert() {
        User user = new User();
        user.setName("小明");
        user.setAge(18);
        user.setUserName("xiaoming");
        user.setPassword("123456");
        user.setEmail("231@qq.com");
        int resultRowCount = userMapper.insert(user);  // 返回受影响的行数
        System.out.println("result row count => " + resultRowCount);
        // 获取自增长后的id(会回填给User实体)
        System.out.println("new id => " + user.getId());
    }

测试结果

result row count => 1
new id => 6

@TableField

在 MP 中,可以通过 @TableField 注解来指定字段的一些属性。常用场景有以下两点:

  1. 对象中的属性名和字段名不一致的问题(非驼峰)。

  2. 对象中的属性字段在表中不存在的问题。

代码示例

package com.example.apiweb.entity;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {

    @TableId(type= IdType.AUTO)  // 设置主键增长策略
    private Long id;
    private String userName;
    @TableField(select = false)  // 查询时不返回该字段的值
    private String password;
    private String name;
    private Integer age;
    @TableField(value = "email")  // 解决属性名与数据库字段名不一致的问题
    private String mail;
    @TableField(exist = false)  // 解决数据库中不存在该字段的问题
    private String address;

}

测试效果:

image


更新操作

在 MP 中,更新操作有两种方式:一种是根据 id 更新,另一种是根据条件更新。


根据 id 更新

方法定义:

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

测试:

    @Test
    void testUpdateById() {
        User user = new User();
        user.setId(6L);  // 主键(6)
        user.setAge(21);  // 更新的字段

        // 根据id更新,更新不为 null 的字段
        int resultRowCount = userMapper.updateById(user);
        System.out.println("result row count =>" + resultRowCount);
    }

根据条件更新

方法定义:

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

测试:

    // 方式一:通过 QueryWrapper
    @Test
    void testUpdateByQueryWrapper() {
        User user = new User();
        // 更新的字段
        user.setAge(23);
        // 更新的条件:主键为 6
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("id", 6);  // 此处的字段使用的是数据库的字段名,而不是实体类的属性名
        // 执行更新操作
        int resultRowCount = userMapper.update(user, wrapper);
        System.out.println("result row count => " + resultRowCount);

    }

    // 方式二:通过 UpdateWrapper
    @Test
    void testUpdateByUpdateWrapper() {
        // 更新的条件以及字段
        UpdateWrapper<User> wrapper = new UpdateWrapper<>();
        wrapper.eq("id", 6).set("age", 24).set("email", "123@qq.com");

        // 执行更新操作
        int resultRowCount = userMapper.update(null, wrapper);
        System.out.println("result row count => " + resultRowCount);
    }

删除操作

根据主键删除

方法定义:

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

测试:

    @Test
    void testDelete() {
        // 根据主键删除
        int resultRowCount = this.userMapper.deleteById(6L);
        System.out.println("result row count => " + resultRowCount);
    }

根据主键集合批量删除

方法定义:

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

测试:

    @Test
    public void testDeleteByMap() {
        // 根据id集合批量删除
        int resultRowCount = this.userMapper.deleteBatchIds(Arrays.asList(1L, 2L, 3L));
        System.out.println("result row count => " + resultRowCount);
    }

根据数据库字段删除

方法定义:

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

测试:

    @Test
    public void testDeleteByMap() {
        Map<String, Object> columnMap = new HashMap<>();
        columnMap.put("age", 20);  // 删除条件1
        columnMap.put("name", "张三");  // 删除条件2

        // 将columnMap中的元素设置为删除的条件,多个之间为and关系
        int result = this.userMapper.deleteByMap(columnMap);
        System.out.println("result = " + result);
    }

根据实体属性删除

方法定义:

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

测试:

    @Test
    public void testDeleteByMap() {
        User user = new User();
        user.setAge(20);
        user.setName("张三");

        // 将实体对象进行包装,包装为操作条件
        QueryWrapper<User> wrapper = new QueryWrapper<>(user);

        int result = this.userMapper.delete(wrapper);
        System.out.println("result = " + result);
    }

查询操作

MP 提供了多种查询操作,比如根据 id 查询、批量查询、查询单条数据、查询列表、分页查询等。


根据主键查询

方法定义:

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

测试:

    @Test
    public void testSelectById() {
        //根据id查询数据
        User user = this.userMapper.selectById(2L);
        System.out.println("result = " + user);
    }

根据主键集合批量查询

方法定义:

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

测试:

    @Test
    public void testSelectBatchIds() {
        // 根据id集合批量查询
        List<User> users = this.userMapper.selectBatchIds(Arrays.asList(2L, 3L, 10L));
        for (User user : users) {
            System.out.println(user);
        }
    }

根据实体属性查询一条数据

方法定义:

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

测试:

    @Test
    public void testSelectOne() {
        QueryWrapper<User> wrapper = new QueryWrapper<User>();
        wrapper.eq("name", "李四");

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

根据实体属性查询多条数据

方法定义:

    /**
     * 根据 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", 23);  // 年龄大于23岁

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

查询总数

方法定义:

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

测试:

    @Test
    public void testSelectCount() {
        QueryWrapper<User> wrapper = new QueryWrapper<User>();
        wrapper.gt("age", 23);  // 年龄大于23岁

        // 根据条件查询数据条数
        Integer count = this.userMapper.selectCount(wrapper);
        System.out.println("count = " + count);
    }

翻页查询

方法定义:

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

配置分页插件:

package com.example.apiweb.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.example.apiweb.mapper")  // 设置mapper接口的扫描包
public class PageConfig {
    /**
     * 分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }
}

测试:

    @Test
    void testSelectPage() {
        QueryWrapper<User> wrapper = new QueryWrapper<User>();
        wrapper.gt("age", 20);  // 年龄大于20岁

        Page<User> page = new Page<>(1,1);  // 查询第一页,每页一条数据

        // 根据条件查询数据
        IPage<User> iPage = this.userMapper.selectPage(page, wrapper);
        System.out.println("数据总条数:" + iPage.getTotal());  // 3
        System.out.println("总页数:" + iPage.getPages());  // 3
        System.out.println("当前页数:" + iPage.getCurrent());  // 1
        // 遍历当前页的数据
        List<User> users = iPage.getRecords();
        for (User user : users) {
            System.out.println("user = " + user);
        }
    }

条件构造器

在 MP 中,Wrapper 接口的实现类关系如下:

image

可以看到,AbstractWrapper 和 AbstractChainWrapper 是重点实现,接下来主要介绍 AbstractWrapper 及其子类。

说明:

QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类
用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件。
而 entity 生成的 where 条件与 使用各个 api 生成的 where 条件没有任何关联行为

官网文档地址:https://mybatis.plus/guide/wrapper.html


allEq

方法定义一:

allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)

个别参数说明:
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 = '老王'

测试:

    @Test
    public void testWrapper() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();

        // 设置条件
        Map<String,Object> params = new HashMap<>();
        params.put("name", "曹操");
        params.put("age", "20");
        params.put("password", null);

        // wrapper.allEq(params);  // 表示:SELECT * FROM tb_user WHERE password IS NULL AND name = ? AND age = ?
        // wrapper.allEq(params, false);  // 表示:SELECT * FROM tb_user WHERE name = ? AND age = ?

        // wrapper.allEq((k, v) -> (k.equals("name") || k.equals("age")) ,params);  // 表示:SELECT * FROM tb_user WHERE name = ? AND age = ?

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

    }

基本比较操作

方法名 说明
eq 等于 =
ne 不等于 <>
gt 大于 >
ge 大于等于 >=
lt 小于 <
le 小于等于 <=
between BETWEEN 值1 AND 值2
notBetween NOT BETWEEN 值1 AND 值2
in 字段 IN (v0, v1, ...)
notIn 字段 NOT IN (v0, v1, ...)

测试:

    @Test
    public void testEq() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();

        // SELECT id,user_name,password,name,age,email FROM tb_user WHERE password = ? AND age >= ? AND name IN (?,?,?)
        wrapper.eq("password", "123456")
               .ge("age", 20)
               .in("name", "李四", "王五", "赵六");

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

    }

模糊查询

方法名 说明 示例
like LIKE '%值%' like("name", "王")表示name like '%王%'
notLike NOT LIKE '%值%' notLike("name", "王")表示name not like '%王%'
likeLeft LIKE '%值' likeLeft("name", "王")表示name like '%王'
likeRight LIKE '值%' likeRight("name", "王")表示name like '王%'

测试:

    @Test
    public void testWrapper() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();

        // SELECT id,user_name,password,name,age,email FROM tb_user WHERE name LIKE ?
        // Parameters: %曹%(String)
        wrapper.like("name", "曹");

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

    }

排序

方法名 说明 示例
orderBy ORDER BY 字段, ... orderBy(true, true, "id", "name")表示order by id ASC,name ASC
orderByAsc ORDER BY 字段, ... ASC orderByAsc("id", "name")表示order by id ASC,name ASC
orderByDesc ORDER BY 字段, ... DESC orderByDesc("id", "name")表示order by id DESC,name DESC

测试:

    @Test
    public void testWrapper() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();

        // SELECT id,user_name,password,name,age,email FROM tb_user ORDER BY age DESC
        wrapper.orderByDesc("age");

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

    }

逻辑查询

方法:

  • or
    • 拼接 OR
    • 主动调用or表示紧接着下一个方法不是用and连接(不调用or则默认为使用and连接)。
  • and
    • AND 嵌套
    • 例:and(i -> i.eq("name", "李白").ne("status", "活着"))表示and (name = '李白' and status <> '活着')

测试:

    @Test
    public void testWrapper() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();

        // SELECT id,user_name,password,name,age,email FROM tb_user WHERE name = ? OR age = ?
        wrapper.eq("name","李四").or().eq("age", 24);

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

    }

select

在 MP 中默认是查询所有的字段,如果有需要也可以通过 select 方法进行指定字段。

    @Test
    public void testWrapper() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();

        // SELECT id,name,age FROM tb_user WHERE name = ? OR age = ?
        wrapper.eq("name", "李四")
                .or()
                .eq("age", 24)
                .select("id", "name", "age");

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

    }

自动填充

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

添加 @TableField 注解

@TableField(fill = FieldFill.INSERT)  // 插入数据时进行填充
private String password;

FieldFill 提供了多种模式选择:

public enum FieldFill {
    /**
    * 默认不处理
    */
    DEFAULT,
    
    /**
    * 插入时填充字段
    */
    INSERT,
    
    /**
    * 更新时填充字段
    */
    UPDATE,
    
    /**
    * 插入和更新时填充字段
    */
    INSERT_UPDATE
}

编写 MyMetaObjectHandler

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {

    // 插入时自动填充
    @Override
    public void insertFill(MetaObject metaObject) {
        Object password = getFieldValByName("password", metaObject);
        //字段为空时进行填充
        if(null == password){
            setFieldValByName("password", "123456", metaObject);
        }
    } 
    
    // 更新时自动填充
    @Override
    public void updateFill(MetaObject metaObject) {
    }
}

测试

@Test
public void testInsert(){
    User user = new User();
    user.setName("关羽");
    user.setUserName("guanyu");
    user.setAge(30);
    user.setEmail("guanyu@itast.cn");
    user.setVersion(1);
    
    int result = this.userMapper.insert(user);
    System.out.println("result = " + result);
}

测试结果:

image


ActiveRecord

ActiveRecord 介绍

ActiveRecord(简称 AR)一直广受动态语言( PHP 、 Ruby 等)的喜爱,而 Java 作为准静态语言,对于
ActiveRecord 往往只能感叹其优雅。

什么是 ActiveRecord ?

  • ActiveRecord 也属于 ORM(对象关系映射)层,由 Rails 最早提出,遵循标准的 ORM 模型:表映射到记录,记录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。
  • ActiveRecord 的主要思想:
    • 每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录。通常表的每个字段
      在类中都有相应的 Field 。
    • ActiveRecord 同时负责把自己持久化,在 ActiveRecord 中封装了对数据库的访问,即 CURD 。
    • ActiveRecord 是一种领域模型(Domain Model),封装了部分业务逻辑。

开启 AR

在 MP 中开启 AR 非常简单,只需要将实体对象继承 Model 即可(注意:UserMapper 还是需要保留的)。

package com.example.apiweb.entity;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User extends Model<User> {

    @TableId(type= IdType.AUTO)  // 设置主键增长策略
    private Long id;
    private String userName;
    private String password;
    private String name;
    private Integer age;
    private String email;

}

根据主键查询

    @Test
    void testSelectById() {
        User user = new User();
        user.setId(2L);
        User resultUser = user.selectById();
        System.out.println(resultUser);
    }

新增操作

    @Test
    public void testAR() {
        User user = new User();
        user.setName("刘备");
        user.setAge(30);
        user.setPassword("123456");
        user.setUserName("liubei");
        user.setEmail("liubei@itcast.cn");
        boolean insert = user.insert();
        System.out.println(insert);
    }

根据主键更新

    @Test
    public void testAR() {
        User user = new User();
        user.setId(8L);
        user.setAge(35);
        boolean update = user.updateById();
        System.out.println(update);
    }

根据主键删除

    @Test
    public void testAR() {
        User user = new User();
        user.setId(7L);
        boolean delete = user.deleteById();
        System.out.println(delete);
    }

根据条件查询

    @Test
    public void testAR() {
        User user = new User();
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.le("age", "20");
        // 根据条件更新、删除等操作的用法相同
        List<User> users = user.selectList(userQueryWrapper);
        
        for (User user1 : users) {
        	System.out.println(user1);
        }
    }

posted @ 2022-03-01 13:46  Juno3550  阅读(321)  评论(0编辑  收藏  举报