MyBatisPlus文档

一、MyBatis框架回顾

使用springboot整合Mybatis,实现Mybatis框架的搭建

1、创建示例项目

(1)、创建工程

新建工程

创建空工程

创建模块

创建springboot模块

选择SpringBoot版本

(2)、引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!--mysql驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.4.0</version>
        </dependency>

        <!--mybatis起步依赖-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>3.0.4</version>
        </dependency>

        <!--web环境起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.4.4</version>
        </dependency>

        <!--lombok插件依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
            <scope>provided</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>3.4.4</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

(3)、编写实体类

创建domain包,编写实体类,实体类的数据类型和表字段类型一致

Lombok可以极大的省略类的代码量,使代码更加的简洁。

Lombok相关常用注解

@Data 注解在类上,提供类的get、set、equals、hashCode、toString等方法

@AllArgsConstructor 注解在类上,提供满参构造

@NoArgsConstructor 注解在类上,提供空参构造

java 复制代码
package com.longdidi.mybatis001.domain;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    //主键
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

(4)、创建mapper包

创建mapper包,编写Mapper接口

@Mapper作用在接口上,扫描到该注解后,会根据接口创建该接口的实现类对象

java 复制代码
package com.longdidi.mybatis001.mapper;

import com.longdidi.mybatis001.domain.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {
    List<User> selectAll();
}

(5)、创建service包

  • 编写Service

    java 复制代码
    package com.longdidi.mybatis001.service;
    
    import com.longdidi.mybatis001.domain.User;
    
    import java.util.List;
    
    public interface UserService {
        List<User> selectAll();
    }
  • 编写ServiceImpl

    java 复制代码
    package com.longdidi.mybatis001.service.impl;
    
    import com.longdidi.mybatis001.domain.User;
    import com.longdidi.mybatis001.mapper.UserMapper;
    import com.longdidi.mybatis001.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public List<User> selectAll() {
            return userMapper.selectAll();
        }
    }

(6)、编写Controller

java 复制代码
package com.longdidi.mybatis001.controller;

import com.longdidi.mybatis001.domain.User;
import com.longdidi.mybatis001.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/selectAll")
    public List<User> selectAll(){
        List<User> users = userService.selectAll();
        return users;
    }
}

(7)、编写映射文件

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

    <select id="selectAll" resultType="com.longdidi.mybatis001.domain.User">
        select *
        from user
    </select>

</mapper>

(8)、编写配置文件

将配置文件改为yml格式,编写配置文件

yml 复制代码
spring:
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.1.6:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false

(9)、创建数据库/表

  1. 创建数据库mybatis
  1. 创建表
sql 复制代码
DROP TABLE IF EXISTS `user`;

CREATE TABLE `user`
(
    id BIGINT NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);
  1. 插入数据
sql 复制代码
DELETE FROM `user`;

INSERT INTO `user` (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');

(10)、测试结果

请求controller接口,发现响应了数据,至此Mybatis环境开发完毕

http://localhost:8080/selectAll

2、问题分析

开发效率也就是我们使用这款框架开发的速度快不快,是否简单好用易上手

从这个角度思考,每当需要编写一个SQL需求的时候需要做几步

【1】Mapper接口提供一个抽象方法

【2】Mapper接口对应的映射配置文件提供对应的标签和SQL语句

【3】在Service中依赖Mapper实例对象

【4】调用Mapper实例中的方法

【5】在Controller中依赖Service实例对象

【6】调用Service实例中的方法

通过上面的分析发现,对于一个SQL需求,无论是单表还是多表,都是需要完成如上几步才能实现SQL需求的开发

但是在开发中,有一些操作是通用逻辑,这些通用逻辑是可以被简化的,例如:

【1】对于dao,是否可以由框架帮我们提供好单表的Mapper抽象方法和对应的SQL实现,不需要程序员去实现这些

【2】对于service,使用可以有框架直接帮我们提供好一些service的抽象方法和对应的实现,不需要程序员去实现这些

【3】一些其他的企业开发中所需要的操作

分析到这里发现其实核心框架并没有发生变化,依然还是Mybatis,只不过我们希望对于Mybatis进行一些封装和进化,让它更加的好用,更加的易用

二、MybatisPlus简介

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

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

1、相关网址

(1)、官网地址

(2)、文档地址

https://mybatis.plus/guide/

(3)、源码地址

https://github.com/baomidou/mybatis-plus

(4)、码云地址

https://gitee.com/organizations/baomidou

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 操作智能分析阻断,也可自定义拦截规则,预防误操作

3、支持数据库

任何能使用 MyBatis 进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下

  • mysql 、mariadb 、oracle 、db2 、h2 、hsql 、sqlite 、postgresql 、sqlserver 、presto 、Gauss 、Firebird
  • Phoenix 、clickhouse 、Sybase ASE 、 OceanBase 、达梦数据库 、虚谷数据库 、人大金仓数据库 、南大通用数据库 、

4、框架结构

三、MyBatisPlus集成方式

1、Mybatis+MP

下面演示通过纯Mybatis与Mybatis-Plus整合

(1)、创建模块

创建测试模块MyBatisAndMP001

(2)、引入依赖

xml 复制代码
    <dependencies>
        <!--mysql驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.4.0</version>
        </dependency>

        <!--mybatis-plus起步依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.5.11</version>
        </dependency>

        <!--lombok插件依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
            <scope>provided</scope>
        </dependency>

        <!--单元测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>3.4.4</version>
            <scope>test</scope>
        </dependency>

        <!-- 连接池 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.24</version>
        </dependency>
        <!--日志依赖-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-reload4j</artifactId>
            <version>2.1.0-alpha1</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

(3)、添加日志配置

log4j.properties

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

(4)、编写MyBatis配置文件

编写mybatis-config.xml文件

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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.1.6:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;allowMultiQueries=true&amp;useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

(5)、编写User实体对象

java 复制代码
package com.longdidi.mybatisandmp001.domain;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    //主键
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

(6)、编写UserMapper接口

java 复制代码
package com.longdidi.mybatisandmp001.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.longdidi.mybatisandmp001.domain.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper extends BaseMapper<User> {
    List<User> selectAll();
}

(7)、编写UserMapper.xml文件

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

    <select id="selectAll" resultType="com.longdidi.mybatisandmp001.domain.User">
        select *
        from user
    </select>

</mapper>

(8)、编写测试用例

java 复制代码
package com.longdidi.mybatisandmp001;

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

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

public class MyBatisPlusTests {
    /**
     * 使用Mybatis方式查询
     */
    @Test
    public void testMyBatisSelectAll() {
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        SqlSessionFactory sqlSessionFactory = new
                SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = userMapper.selectAll();
        for (User user : list) {
            System.out.println(user);
        }
    }

    /**
     * 使用MybatisPlus方式查询
     */
    @Test
    public void testMyBatisPlusSelectAll() throws IOException {
        String config = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(config);
        SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(inputStream);

        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //测试查询
        List<User> users = userMapper.selectList(null);
        for (User user : users) {
            System.out.println(user);
        }
    }
}

2、Spring+Mybatis+MP

引入了Spring框架,数据源、构建等工作就交给了Spring管理

(1)、创建模块

创建测试模块MyBatisSpringMP001

(2)、引入依赖

xml 复制代码
    <dependencies>
        <!--mysql驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.4.0</version>
        </dependency>

        <!--mybatis-plus起步依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.5.11</version>
        </dependency>

        <!--lombok插件依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
            <scope>provided</scope>
        </dependency>

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

        <!--日志依赖-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-reload4j</artifactId>
            <version>2.1.0-alpha1</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>7.0.0-M3</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>7.0.0-M3</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.4</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>7.0.0-M3</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

(3)、编写jdbc.properties

properties 复制代码
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.1.6:3306/mybatis?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
jdbc.username=root
jdbc.password=root

(4)、编写applicationContext.xml

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">

    <context:property-placeholder location="classpath:*.properties"/>

    <!-- 定义数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          destroy-method="close">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="maxActive" value="10"/>
        <property name="minIdle" value="5"/>
    </bean>

    <!--这里使用MP提供的sqlSessionFactory,完成了Spring与MP的整合-->
    <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <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>

    <!--扫描mapper接口,使用的依然是Mybatis原生的扫描器-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.longdidi.mapper"/>
    </bean>

</beans>

(5)、编写User对象

java 复制代码
package com.longdidi.domain;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    //主键
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

(6)、编写UserMapper接口

java 复制代码
package com.longdidi.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.longdidi.domain.User;
import org.apache.ibatis.annotations.Mapper;

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

(7)、编写日志配置

编写log4j.properties

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

(8)、添加mybatis-config.xml

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>
</configuration>

(9)、编写测试方法

java 复制代码
package com.longdidi;

import com.longdidi.domain.User;
import com.longdidi.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class MyBatisSpringMp001ApplicationTests {
    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectList(){
        List<User> users = this.userMapper.selectList(null);
        for (User user : users) {
            System.out.println(user);
        }
    }

}

(10)、测试结果

3、SpringBoot+Mybatis+MP

集成步骤

  1. 引入mybatisplus起步依赖
  2. 自定义的Mapper继承BaseMapper接口

(1)、创建模块

创建测试模块MyBatisSpringBootMP001

(2)、引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.4.0</version>
        </dependency>
        <!--mybatis-plus起步依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.11</version>
        </dependency>
        <!--<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.11</version>
            <exclusions>
                <exclusion>
                    <artifactId>mybatis-spring</artifactId>
                    <groupId>org.mybatis</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.4</version>
        </dependency>-->
        <!--日志依赖-->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-reload4j</artifactId>
            <version>2.1.0-alpha1</version>
            <scope>compile</scope>
        </dependency>
        <!--lombok插件依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
            <scope>provided</scope>
        </dependency>
        <!--引入springboot测试依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

注意:如果在springboot3中只引入mybatis-plus-boot-starter依赖

xml 复制代码
<!--mybatis-plus起步依赖-->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.11</version>
</dependency>

会抛出Caused by: java.lang.IllegalArgumentException: Invalid value type for attribute 'factoryBeanObjectType': java.lang.String异常

这是因为在 Spring Boot 3.0后的 版本中FactoryBeanRegistrySupport#getTypeForFactoryBeanFromAttributes方法已变更,如果 factoryBeanObjectType 不是 ResolvableType 或 Class 类型会抛出 IllegalArgumentException 异常,此时因为 factoryBeanObjectType 是 String 类型,不符合条件而抛出异常

项目中使用的 mybatis-plus-boot-starter 是当前最新版本 3.5.11,但 mybatis-spring 为2.1.2

但版本已不兼容

MyBatis-Spring-Boot-Starter、MyBatis-Spring、Spring Boot、Java 版本兼容对照如下表

解决方式

  • 第一步:

    排除 mybatis-plus-boot-starter 中的旧版本 mybatis-spring

    xml 复制代码
    <!--mybatis-plus起步依赖-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.11</version>
        <exclusions>
            <exclusion>
                <artifactId>mybatis-spring</artifactId>
                <groupId>org.mybatis</groupId>
            </exclusion>
        </exclusions>
    </dependency>
  • 第二步:引入 mybatis-spring 3.0.4版本

    xml 复制代码
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>3.0.4</version>
    </dependency>

(3)、配置日志信息

添加log4j.properties

properties 复制代码
log4j.rootLogger=info,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

(4)、配置application.properties

properties 复制代码
spring.application.name=MyBatisSpringBootMP001
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.1.6:3306/mybatis?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

(5)、添加实体类

java 复制代码
package com.longdidi.domain;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    //主键
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

(4)、添加测试mapper

java 复制代码
package com.longdidi.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.longdidi.domain.User;
import org.apache.ibatis.annotations.Mapper;

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

(7)、添加测试类

java 复制代码
package com.longdidi;

import com.longdidi.domain.User;
import com.longdidi.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 MyBatisSpringBootMp001ApplicationTests {

    @Autowired
    private UserMapper userMapper;

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

}

四、基础语法详解

1、搭建测试项目

(1)、创建测试模块

模块名MyBatisPlus001

(2)、引入依赖

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>8.4.0</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.11</version>
        </dependency>
        <!--mybatis-plus起步依赖-->
        <!--<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.11</version>
            <exclusions>
                <exclusion>
                    <artifactId>mybatis-spring</artifactId>
                    <groupId>org.mybatis</groupId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.4</version>
        </dependency>-->
        <!--lombok插件依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

(3)、配置文件

properties 复制代码
spring:
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.1.6:3306/mybatisplus?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false

(4)、添加测试接口

java 复制代码
package com.longdidi.controller;

import com.longdidi.domain.User;
import com.longdidi.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {
    @Autowired
    UserService userService;

    @RequestMapping("/selectList")
    public String selectList() {
        List<User> all;
        all = userService.selectList();
        return all.toString();
    }
}

(5)、添加实体类

java 复制代码
package com.longdidi.domain;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    //主键
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

(6)、添加service及实现类

  • UserService

    java 复制代码
    package com.longdidi.service;
    
    import com.longdidi.domain.User;
    
    import java.util.List;
    
    public interface UserService {
        List<User> selectList();
    }
  • UserServiceImpl

    java 复制代码
    package com.longdidi.service.impl;
    
    import com.longdidi.domain.User;
    import com.longdidi.mapper.UserMapper;
    import com.longdidi.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        UserMapper userMapper;
    
        public List<User> selectList() {
            return userMapper.selectList(null);
        }
    
    }

(7)、添加mapper

java 复制代码
package com.longdidi.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.longdidi.domain.User;
import org.apache.ibatis.annotations.Mapper;

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

(8)、添加单元测试

java 复制代码
package com.longdidi;

import com.longdidi.domain.User;
import com.longdidi.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 MyBatisPlus001ApplicationTests {

    @Autowired
    private UserMapper userMapper;

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

}

(9)、添加库表

  1. 创建数据库

    创建mybatisplus数据库

  2. 创建数据表

    复制代码
    DROP TABLE IF EXISTS `user`;
    
    CREATE TABLE `user`
    (
        id BIGINT NOT NULL COMMENT '主键ID',
        name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
        age INT NULL DEFAULT NULL COMMENT '年龄',
        email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
        PRIMARY KEY (id)
    );
  3. 添加测试数据

    复制代码
    DELETE FROM `user`;
    
    INSERT INTO `user` (id, name, age, email) VALUES
    (1, 'Jone', 18, '[email protected]'),
    (2, 'Jack', 20, '[email protected]'),
    (3, 'Tom', 28, '[email protected]'),
    (4, 'Sandy', 21, '[email protected]'),
    (5, 'Billie', 24, '[email protected]');

(10)、精简相关日志

  • 去除mybatisplus的logo

    复制代码
    # 去除mybatisplus的logo
    mybatis-plus:
      global-config:
        banner: false
  • 去除springboot的logo

    yml 复制代码
    # 去除springboot的logo
    spring:
      main:
        banner-mode: off

(11)、MybatisPlus的执行日志

yml 复制代码
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

(12)、执行单元测试

2、BaseMapper语法

4.2.1、BaseMapper简介

BaseMapper接口提供了很多单表的增删改查相关的操作方法

自己编写的Mapper接口继承自BaseMapper接口之后就可以使用这些方法

4.2.2、使用方法

  1. 定义mapper继承BaseMapper
  2. 给BaseMapper提供一个泛型类,泛型类用于表明该Mapper将要操作哪张表(默认情况下操作的是和类名对应的数据库表)

【示例】下面示例表示将要操作user表

java 复制代码
public interface UserMapper extends BaseMapper<User> {
}

4.2.3、方法详解

4.2.3.1、新增方法

新增方法三个

(1)、insert(实体)

这个方法用于插入一条记录。它接受一个实体对象作为参数,这个实体对象包含了要插入到数据库中的数据。在底层,它会根据实体对象的属性以及相关的映射关系(如通过注解指定的表名、字段名等)来构建插入语句并执行。

测试单条新增

java 复制代码
@SpringBootTest
public class MapperInsertTest {
    @Autowired
    private UserMapper userMapper;

    /**
     * 测试单条新增
     */
    @Test
    void insertOne() {
        User user = new User();
        user.setId(6L);
        user.setName("Nike");
        user.setAge(33);
        user.setEmail("[email protected]");
        userMapper.insert(user);
    }
}

测试完毕删除测试数据

sql 复制代码
delete from user where id>5;
(2)、insert(集合)

insert(Collection entityList)和insert(Collection entityList, int batchSize) 这两个方法用于批量插入。它们接受一个实体对象集合作为参数,可以指定插入的批次数量。

在实现上,通过 MybatisBatchUtils 等工具来实现批量插入的功能,有效地提高了插入大量数据的效率。

在实现时需要开启批处理rewriteBatchedStatements=true,在jdbc连接后面加上 rewriteBatchedStatements=true ,加上后才是真正的批量插入

测试批量新增

java 复制代码
package com.longdidi;

import com.longdidi.domain.User;
import com.longdidi.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.ArrayList;
import java.util.List;

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

    /**
     * 测试批量新增
     */
    @Test
    void insertCollection() {
        List<User> usersList = getUserList();
        long start = System.currentTimeMillis();
        userMapper.insert(usersList);
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start));
    }

    public List<User> getUserList() {
        List<User> userList = new ArrayList<>(10000);

        for (int i = 6; i < 10000; i++) {
            User user = new User();
            user.setId(Long.valueOf(i));
            user.setName("Nike" + i);
            user.setAge(33);
            user.setEmail("test" + i + "@longdidi.com");
            userList.add(user);
        }
        return userList;

    }

}

耗时61秒,测试发现批量添加效率并不高

测试完毕删除测试数据

sql 复制代码
delete from user where id>5;

开启批处理,在jdbc连接后面加上 rewriteBatchedStatements=true

再次执行上面的测试批量方法查看耗时8秒

4.2.3.2、删除方法

删除方法有7个

(1)、deleteById
  • deleteById(Serializable id)

    deleteById(Serializable id)是一个简单的根据 ID 删除的方法

    java 复制代码
        @Test
        void deleteById() {
            userMapper.deleteById(6L);
        }
  • deleteById(Object obj, boolean useFill)

    它会判断传入的对象类型

    如果是实体类且需要填充逻辑删除相关信息(在逻辑删除且有更新填充的情况下),会先进行相应处理后再执行删除

  • deleteById(T entity)

    直接根据实体对象(其中包含主键信息)来删除记录

    java 复制代码
        @Test
        void deleteByEntityId() {
            User user = new User();
            user.setId(7L);
            userMapper.deleteById(user);
        }
(2)、deleteByMap

根据传入的表字段map对象作为条件来删除记录

它通过构建Wrapper对象来生成相应的删除语句

java 复制代码
    @Test
	public void deleteByMap() {
        Map<String, Object> map = new HashMap<>();
        map.put("id", 11L);
        map.put("name", "Nike");
        userMapper.deleteByMap(map);
    }
(3)、delete

根据Wrapper条件对象来删除记录

Wrapper可以灵活地定义各种复杂的删除条件,如多条件组合等

java 复制代码
    @Test
    public void deleteByIdsWithQueryWrapper() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", 10L);
        userMapper.delete(queryWrapper);
    }
(4)、deleteByIds

用于批量删除

  • deleteByIds(Collection<?> idList)

    可以根据主键 ID 列表或实体列表进行删除

  • deleteByIds(Collection<?> collections, boolean useFill)

    在逻辑删除且需要填充的情况下,deleteByIds(Collection<?> collections, boolean useFill)会处理相关的填充逻辑。

java 复制代码
    @Test
    public void deleteByIds() {
        List<Long> ids = new ArrayList<>();
        ids.add(8L);
        ids.add(9L);
        userMapper.deleteByIds(ids);
    }
4.2.3.3、修改方法
(1)、updateById(实体)

根据传入的实体对象来更新记录,以主键为依据,将实体对象中的属性值更新到对应的数据库记录中

【示例】此示例根据id5L的条件更新用户名字

java 复制代码
@SpringBootTest
public class MapperUpdateTest {
    @Autowired
    private UserMapper userMapper;

    /**
     * 测试单条更新
     */
    @Test
    void update() {
        User user = new User();
        user.setId(5L);
        user.setName("Updated Name1");
        int result = userMapper.updateById(user);
        System.out.println("更新结果: " + result);
    }
}
(2)、updateById(集合)
  • updateById(Collection entityList)
  • updateById(Collection entityList, int batchSize)

用于批量根据主键更新实体对象集合中的数据,可以指定批次数量

java 复制代码
@SpringBootTest
public class MapperUpdateTest {
    @Autowired
    private UserMapper userMapper;

    /**
     * 根据集合更新
     */
    @Test
    void updateByEntity() {
        List<User> updateUserList = new ArrayList<>();
        // 添加多个要更新的 User 对象到 updateUserList
        User user = new User();
        user.setId(5L);
        user.setName("Updated Name2");
        updateUserList.add(user);
        userMapper.updateById(updateUserList);
    }
}
(3)、update(条件)
  • update(T entity, Wrapper updateWrapper)

    根据实体对象和Wrapper条件来更新记录,实体对象用于设置更新的值,Wrapper用于指定更新的条件

  • update(Wrapper updateWrapper)

    在只给定Wrapper条件的情况下更新记录,这种情况下无法进行自动填充。

java 复制代码
@SpringBootTest
public class MapperUpdateTest {
    @Autowired
    private UserMapper userMapper;

    /**
     * 根据条件更新
     */
    @Test
    void updateByWrapper() {
        User user = new User();
        user.setName("Updated Name3");
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", 5L);
        userMapper.update(user, queryWrapper);
    }
}
4.2.3.4、新增/修改方法
(1)、insertOrUpdate(实体)

这个方法实现了主键存在则更新记录,主键不存在则插入一条记录的功能。

它通过获取实体类的表信息,判断主键值是否存在或对应记录是否存在来决定是执行插入还是更新操作

java 复制代码
@SpringBootTest
public class MapperInsertOrUpdateTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    void insertOrUpdate1() {
        User newOrUpdatedUser = new User();
        // 假设设置了合适的属性,包括主键或不包括主键
        newOrUpdatedUser.setId(5L);
        newOrUpdatedUser.setEmail("[email protected]");
        boolean result = userMapper.insertOrUpdate(newOrUpdatedUser);
        if (result) {
            System.out.println("插入或更新成功");
        }
    }
}
(2)、insertOrUpdate(集合)
  • insertOrUpdate(Collection entityList)
  • insertOrUpdate(Collection entityList, BiPredicate<BatchSqlSession, T> insertPredicate)
  • insertOrUpdate(Collection entityList, BiPredicate<BatchSqlSession, T> insertPredicate, int batchSize)

这些方法用于批量的修改或插入操作

通过传入的BiPredicate等参数来判断每个实体对象是应该插入还是更新,并可以指定批次数量

java 复制代码
@SpringBootTest
public class MapperInsertOrUpdateTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    void insertOrUpdate2() {
        // 假设设置了合适的属性,包括主键或不包括主键
        List<User> userBatchList = new ArrayList<>();
        // 添加多个 User 对象到 userBatchList
        User user = new User();
        user.setId(5L);
        user.setName("Updated");
        userBatchList.add(user);
        userMapper.insertOrUpdate(userBatchList);
    }
}
4.2.3.5、查询方法

查询方法如下

(1)、selectById()

根据主键 ID 查询一条记录,返回对应的实体对象

java 复制代码
    @Test
    void selectById() {
        long userId = 5L;
        User user = userMapper.selectById(userId);
        System.out.println("查询到的用户: " + user);
    }
(2)、selectByIds()

selectByIds(Collection<? extends Serializable> idList)用于根据主键 ID 列表批量查询记录,返回数据列表

java 复制代码
    @Test
    void selectByIds() {
        List<Long> ids = new ArrayList<>();
        ids.add(5L);
        List<User> users = userMapper.selectByIds(ids);
        System.out.println("查询到的用户: " + users.toString());
    }
(3)、selectByMap()

根据传入的表字段map对象作为条件查询记录,返回结果列表

【示例】此示例根据年龄条件查询用户列表

java 复制代码
    @Test
    void selectByMap() {
        Map<String, Object> columnMap = new HashMap<>();
        columnMap.put("age", 18);
        List<User> userListByAge = userMapper.selectByMap(columnMap);
        for (User u : userListByAge) {
            System.out.println("根据年龄条件查询到的用户: " + u);
        }

    }
(4)、selectOne()
  • selectOne(Wrapper queryWrapper)
  • selectOne(Wrapper queryWrapper, boolean throwEx)

根据Wrapper条件查询一条记录。如果查询到多条记录且throwEx为true,则会抛出异常;如果throwEx为false,则返回第一条记录。

【示例】这里根据名字条件查询一个用户,如果有多个符合条件的用户且throwExtrue会抛出异常

java 复制代码
    @Test
    void selectOne() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "Unique Name");
        User user = userMapper.selectOne(queryWrapper);
        System.out.println("查询到的唯一用户: " + user);

    }
(5)、exists()

根据Wrapper条件判断是否存在记录,返回布尔值

【示例】此示例通过exists方法判断是否存在名为Test User的用户记录

java 复制代码
    @Test
    void exists() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "Test User");
        boolean exists = userMapper.exists(queryWrapper);
        if (exists) {
            System.out.println("存在符合条件的用户记录");
        } else {
            System.out.println("不存在符合条件的用户记录");
        }
    }
(6)、selectCount()

根据Wrapper条件查询总记录数

【示例】这里查询年龄大于20的用户记录数

java 复制代码
    @Test
    void selectCount() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.gt("age", 20);
        Long count = userMapper.selectCount(queryWrapper);
        System.out.println("年龄大于20的用户数量: " + count);
    }
(7)、selectList()
  • selectList(Wrapper queryWrapper)
  • selectList(IPage page, Wrapper queryWrapper)
  • selectList(Wrapper queryWrapper, ResultHandler resultHandler)
  • selectList(IPage page, Wrapper queryWrapper, ResultHandler resultHandler)

这些方法用于根据Wrapper条件查询全部记录,可以是不分页的简单查询,也可以是结合分页条件IPage的分页查询,还可以指定结果处理器ResultHandler

【示例】

java 复制代码
@SpringBootTest
public class MapperSelectTest {
    @Autowired
    private UserMapper userMapper;

    /**
     * 简单查询
     */
    @Test
    void selectList1() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.like("name", "a");
        List<User> userList = userMapper.selectList(queryWrapper);
        for (User u : userList) {
            System.out.println("查询到的用户: " + u);
        }

    }

    /**
     * 分页查询
     */
    @Test
    void selectListPage() {
        IPage<User> page = new Page<>(1, 10);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        List<User> pageUserList = userMapper.selectList(page, queryWrapper);
        page.setRecords(pageUserList);
        System.out.println("第1页,每页10条记录的用户列表,总页数: " + page.getPages());
    }

    /**
     * 使用结果处理器
     */
    @Test
    void selectListResult() {
        ResultHandler<User> resultHandler = new ResultHandler<User>() {
            @Override
            public void handleResult(ResultContext<? extends User> resultContext) {
                User user = resultContext.getResultObject();
                System.out.println("处理结果 - 用户: " + user);
            }
        };
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        userMapper.selectList(queryWrapper, resultHandler);
    }
}
(8)、selectMaps()
  • selectMaps(Wrapper queryWrapper)
  • selectMaps(IPage<? extends Map<String, Object>> page, Wrapper queryWrapper)
  • selectMaps(Wrapper queryWrapper, ResultHandler<Map<String, Object>> resultHandler)
  • selectMaps(IPage<? extends Map<String, Object>> page, Wrapper queryWrapper, ResultHandler<Map<String, Object>> resultHandler)

这些方法与selectList类似,但返回的结果是Map类型,用于更灵活地处理查询结果

【示例】

java 复制代码
@SpringBootTest
public class MapperSelectTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    void selectMaps() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("age", 18);
        List<Map<String, Object>> userMapList = userMapper.selectMaps(queryWrapper);
        for (Map<String, Object> map : userMapList) {
            System.out.println("查询到的用户信息 Map: " + map);
        }
    }

    @Test
    void selectMapsResult() {
        IPage<Map<String, Object>> page = new Page<>(1, 5);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        ResultHandler<Map<String, Object>> resultHandler = new ResultHandler<Map<String, Object>>() {
            @Override
            public void handleResult(ResultContext<? extends Map<String, Object>> resultContext) {
                Map<String, Object> map = resultContext.getResultObject();
                System.out.println("处理结果 - 用户信息 Map: " + map);
            }
        };
        userMapper.selectMaps(page, queryWrapper, resultHandler);
    }
}
(9)、selectObjs()
  • selectObjs(Wrapper queryWrapper)
  • selectObjs(Wrapper queryWrapper, ResultHandler resultHandler)

根据Wrapper条件查询全部记录,但只返回第一个字段的值,可以指定结果处理器

【示例】查询所有用户的名字(只返回名字字段的值)

java 复制代码
    @Test
    void selectObjs() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.select("name");
        List<String> names = userMapper.selectObjs(queryWrapper);
        for (String name : names) {
            System.out.println("查询到的用户名字: " + name);
        }

    }
(10)、selectPage
  • selectPage(P page, Wrapper queryWrapper)
  • selectMapsPage(P page, Wrapper queryWrapper)

这两个方法用于分页查询,selectPage返回的是实体对象分页结果,selectMapsPage返回的是Map类型的分页结果,它们都会自动设置分页查询的记录数等信息

【示例】

java 复制代码
@SpringBootTest
public class MapperSelectTest {
    @Autowired
    private UserMapper userMapper;

    /**
     * 获取实体对象分页结果
     */
    @Test
    void selectPage() {
        IPage<User> page = new Page<>(2, 5);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        IPage<User> resultPage = userMapper.selectPage(page, queryWrapper);
        List<User> userList = resultPage.getRecords();
        for (User u : userList) {
            System.out.println("第2页的用户: " + u);
        }
        System.out.println("总页数: " + resultPage.getPages());
    }

    /**
     * 获取Map类型分页结果
     */
    @Test
    void selectMapsPage() {
        IPage<Map<String, Object>> page = new Page<>(1, 3);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        IPage<Map<String, Object>> resultPage = userMapper.selectMapsPage(page, queryWrapper);
        List<Map<String, Object>> mapList = resultPage.getRecords();
        for (Map<String, Object> map : mapList) {
            System.out.println("第1页的用户信息 Map: " + map);
        }
        System.out.println("总页数: " + resultPage.getPages());
    }

}

4.2.4、使用总结

这里测试了通过接口提供的基本增删改查的实现,可以感受到,将来有这些增删改查需求的时候,直接找到对应的方法调用,由Mapper接口的代理对象就会直接给我们拼接好指定的SQL语句,完成查询。

3、通用Service接口

4.3.1、简介

除了Mapper接口,MybatisPlus还提供了IService接口和对应的实现类ServiceImpl,该实现类已经提供好了一些对应的Service相关的方法

在某些场景下可以直接使用ServiceImpl提供的方法,实现对应的功能

  • IService接口

    IService接口中包含了service相关的一些增删改查方法

  • ServiceImpl实现类

    ServiceImpl实现类提供了service相关的增删改查方法的实现

4.3.2、使用方式

  1. 编写Service接口继承IService接口并提供泛型类

    java 复制代码
    public interface UserService extends IService<User> {
    }
  2. 编写ServiceImpl继承ServiceImpl接口并且实现自定义的Service

    继承ServiceImpl时需要指定使用的Mapper接口以及泛型类,用来表明操作的是哪个表

    java 复制代码
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
    }

4.3.3、结构梳理

4.4.4、相关方法

创建测试模块MyBatisPlus002

4.4.4.1、新增方法
(1)、单条新增
java 复制代码
@SpringBootTest
public class ServiceInsertTest {
    @Autowired
    private UserService userService;

    /**
     * 测试单条新增
     */
    //UserService添加
    @Test
    void insertService() {
        User user = new User();
        user.setId(7L);
        user.setName("zhangsan");
        user.setAge(35);
        user.setEmail("[email protected]");

        userService.save(user);
    }
}
(2)、批量新增
  • 批量单条新增

    这种效率较低,因此每添加一条 数据就有一次网络IO请求及数据库开销

    java 复制代码
        /**
         * 批量单条新增
         */
        @Test
        void insertServiceBatch() {
            long start = System.currentTimeMillis();
            for (int i = 6; i < 100000; i++) {
                User user = new User();
                user.setId(Long.valueOf(i));
                user.setName("Nike" + i);
                user.setAge(33);
                user.setEmail("test" + i + "@longdidi.com");
                userService.save(user);
            }
            long end = System.currentTimeMillis();
            System.out.println("耗时:+" + (end - start));
        }
  • 分批添加

    saveBatch(Collection entityList, int batchSize)方法是分批提交

    batchSize参数表示将批量提交的数据分批次提交,比如插入100条数据,参数设置成5,那么底层便不是一条一条数据插入,而是整理成5条sql,也就是5条如下SQL,这样只需5次网络请求数据库开销即可

    sql 复制代码
    insert into user(id,name)values(1,'zhangsan'),(2,'lisi'),(3,'wangwu');

    【测试】

    java 复制代码
    	/**
         * 批量添加数据
         */
        @Test
        void insertServiceCollectionBatch() {
            long start = System.currentTimeMillis();
            List<User> userList = getUserListBatch();
            userService.saveBatch(userList, 5);
            long end = System.currentTimeMillis();
            System.out.println("耗时:+" + (end - start));
        }
    
        public List<User> getUserListBatch() {
            List<User> userList = new ArrayList<>(20);
    
            for (int i = 6; i < 20; i++) {
                User user = new User();
                user.setId(Long.valueOf(i));
                user.setName("Nike" + i);
                user.setAge(33);
                user.setEmail("test" + i + "@longdidi.com");
                userList.add(user);
            }
            return userList;
    
        }
  • 批量集合新增

    JDBC 中的 PreparedStatement 有预编译功能,预编译之后会缓存起来,发送请求时只发送一次网络请求,节省了很多网络时间

    • 如果不开启JDBC批处理,插入时一条一条插入的,效率也不是很高

      java 复制代码
          /**
           * 使用集合批量新增
           */
          @Test
          void insertServiceCollection() {
              long start = System.currentTimeMillis();
              List<User> userList = getUserList();
              userService.saveBatch(userList);
              long end = System.currentTimeMillis();
              System.out.println("耗时:+" + (end - start));
          }
      
          public List<User> getUserList() {
              List<User> userList = new ArrayList<>(10000);
      
              for (int i = 6; i < 100000; i++) {
                  User user = new User();
                  user.setId(Long.valueOf(i));
                  user.setName("Nike" + i);
                  user.setAge(33);
                  user.setEmail("test" + i + "@longdidi.com");
                  userList.add(user);
              }
              return userList;
      
          }

      查看耗时

    • 开启批处理

      这种方式需要开启JDBC的批处理,在jdbc连接后面加上 rewriteBatchedStatements=true ,这个是mysql的参数,不是mybatisplus的参数,加上后才是真正的批量插入

      开启后实际上是将所有数据编译成了一条SQL语句,即如下形式

      sql 复制代码
      insert into user(id,name)values(1,'zhangsan'),(2,'lisi'),(3,'wangwu');

      查看耗时,性能提升了10倍

4.4.4.2、修改方法

提供的方法如下,基本和BaseMapper一样

【示例】

java 复制代码
@SpringBootTest
public class ServiceUpdateTest {
    @Autowired
    private UserService userService;

    //根据ID更新
    @Test
    void updateService1() {
        User user = new User();
        user.setId(6L);
        user.setName("test");
        userService.updateById(user);
    }

    //批量更新
    @Test
    void updateService2() {
        List<User> userList = new ArrayList<>();
        User user = new User();
        user.setId(6L);
        user.setName("test");
        userList.add(user);
        userService.updateBatchById(userList);
    }

    //批量更新
    @Test
    void updateService3() {
        List<User> userList = new ArrayList<>();
        User user = new User();
        user.setId(6L);
        user.setName("test");
        userList.add(user);
        userService.updateBatchById(userList, 2);
    }

    //批量更新
    @Test
    void updateService4() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.gt("id", 3L);
        User user = new User();
        user.setName("test");
        userService.update(user, queryWrapper);
    }
}
4.4.4.3、新增/修改方法

提供的方法如下(如果实体中有ID则更新,没有ID则新增)

【示例】

java 复制代码
@SpringBootTest
public class ServiceInsertOrUpdateTest {
    @Autowired
    private UserService userService;

    // 单条新增或者更新
    @Test
    void saveOrUpdateService1() {
        User user = new User();
        user.setId(6L);
        user.setName("test");
        userService.saveOrUpdate(user);
    }

    // 批量新增或者更新
    @Test
    void saveOrUpdateService2() {
        List<User> userList = new ArrayList<>();
        User user = new User();
        user.setId(6L);
        user.setName("test");
        userList.add(user);
        userService.saveBatch(userList);
    }

    // 批量新增或者更新
    @Test
    void saveOrUpdateService3() {
        List<User> userList = new ArrayList<>();
        User user = new User();
        user.setId(6L);
        user.setName("test");
        userList.add(user);
        userService.saveBatch(userList, 2);
    }
}
4.4.4.4、删除方法

提供的删除方法如下,基本和BaseMapper中的使用方式一样

【示例】

java 复制代码
@SpringBootTest
public class ServiceDeleteTest {
    @Autowired
    private UserService userService;

    //根据ID删除
    @Test
    void deleteService1() {
        userService.removeById(7L);
    }

    //根据ID删除
    @Test
    void deleteService2() {
        User user = new User();
        user.setId(7L);
        userService.removeById(user);
    }

    //根据ID列表删除
    @Test
    void deleteService3() {
        List<Long> ids = new ArrayList<>();
        userService.removeByIds(ids);
    }

    // 根据MAP删除
    @Test
    void deleteService4() {
        Map<String, Object> map = new HashMap<>();
        map.put("id", 6L);
        map.put("name", "test");
        userService.removeByMap(map);
    }

    //根据条件删除
    @Test
    void deleteService5() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("name", "test");
        userService.remove(queryWrapper);
    }
}
4.4.4.5、查询方法

提供的查询方法如下

(1)、getById()

根据ID查询单个对象

java 复制代码
@SpringBootTest
public class ServiceSelectTest {
    @Autowired
    private UserService userService;

    @Test
    void getById() {
        User user = userService.getById(5L);
        System.out.println("查询到的用户: " + user);
    }
}
(2)、getMap()

根据条件查询数据,返回Map集合

java 复制代码
@SpringBootTest
public class ServiceSelectTest {
    @Autowired
    private UserService userService;

    @Test
    void getMap() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", 5L);

        Map<String, Object> map = userService.getMap(queryWrapper);
        System.out.println("查询到的用户: " + map.toString());
    }
}
(3)、getOne()
  • getOne(Wrapper queryWrapper)
  • getOne(Wrapper queryWrapper, boolean throwEx)

根据Wrapper条件查询一条记录

如果查询到多条记录且throwEx为true,则会抛出异常;如果throwEx为false,则返回第一条记录

java 复制代码
@SpringBootTest
public class ServiceSelectTest {
    @Autowired
    private UserService userService;

    @Test
    void getOne() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", 5L);

        User user = userService.getOne(queryWrapper);
        System.out.println("查询到的用户: " + user.toString());

    }
}
(4)、getOneOpt()
  • getOneOpt(Wrapper queryWrapper)
  • getOneOpt(Wrapper queryWrapper, boolean throwEx)

根据Wrapper条件查询一条记录

如果查询到多条记录且throwEx为true,则会抛出异常;如果throwEx为false,则返回第一条记录

java 复制代码
    @Test
    void getOneOpt() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", 5L);

        Optional<User> user = userService.getOneOpt(queryWrapper);
        System.out.println("查询到的用户: " + user.toString());

    }
(5)、getOptById()

根据ID查询数据,返回Option对象

java 复制代码
    @Test
    void getOptById() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", 5L);

        Optional<User> user = userService.getOptById(queryWrapper);
        System.out.println("查询到的用户: " + user.toString());

    }

4.4.5、使用总结

通过继承MybatisPlus提供的Service接口,既可以拓展自己的service方法,也可以使用现有的一些service方法

4、自定义接口方法

MybatisPlus除了给我们提供了这些丰富的接口方法以外,对于我们自己的需求,也可以编写自定义的接口方法,我们通过自己编写SQL语句的形式,实现想要的SQL需求

4.4.1、创建测试模块

创建测试模块MyBatisPlus003

4.4.2、自定义Mapper接口

mapper接口中添加测试方法

java 复制代码
@Mapper
public interface UserMapper extends BaseMapper<User> {
    List<User> selectAllUser();
}

4.4.3、添加映射文件

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

    <select id="selectAllUser" resultType="com.longdidi.domain.User">
        select * from user
    </select>

</mapper>

4.4.4、定义测试方法

java 复制代码
@SpringBootTest
public class UserTest {
    @Autowired
    private UserMapper userMapper;

    @Test
    void insertService() {
        userMapper.selectAllUser();
    }
}

4.4.5、定义实体类

java 复制代码
package com.longdidi.domain;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    //主键
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

4.4.6、配置文件

yml 复制代码
spring:
  datasource:
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.1.6:3306/mybatisplus?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false&rewriteBatchedStatements=true
    #url: jdbc:mysql://192.168.1.6:3306/mybatisplus?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
  # 关闭springboot 的logo
  main:
    banner-mode: off
# 关闭mybatisplus 的logo
mybatis-plus:
  global-config:
    banner: false
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

4.4.7测试结果

五、映射

Mybatis框架之所以能够简化数据库操作,是因为他内部的映射机制,通过自动映射,进行数据的封装,只要符合映射规则就可以快速高效的完成SQL操作的实现。

既然MybatisPlus是基于Mybatis的增强工具,所以也具有这样的映射规则

创建测试模块MyBatisPlus004

1、自动映射规则

  1. 表名和实体类名映射

    自动将实体类名转成数据库的表名

    比如:表名user - > 实体类名User

  2. 字段名和实体类属性名映射

    自动将实体类中的属性转成数据库的字段名(注意是将属性转成字段,而不是将成员变量转成字段,属性是getXxx()方法去掉get后,将首字母转成小写的那么名字)

    比如:字段名name 实体类属性名name

  3. 字段名下划线命名方式和实体类属性小驼峰命名方式映射

    字段名 user_email 实体类属性名 userEmail

    MybatisPlus支持这种映射规则,可以通过配置来设置

    yml 复制代码
    mybatis-plus:
      configuration:
        # 打印sql日志
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
        # true 表示支持下划线到驼峰的映射
        # false 表示不支持下划线到驼峰的映射
        map-underscore-to-camel-case: true

【示例】

在user表中添加一个字段user_address,并更新该字段的值

sql 复制代码
alter table user add(user_address VARCHAR(255));
update user set user_address='北京';

在User类中添加字段

java 复制代码
private String userAddress;

单元测试

java 复制代码
@SpringBootTest
public class UserTest {
    @Autowired
    private UserMapper userMapper;

    // 测试字段下划线转驼峰
    @Test
    void insertService() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id",5L);
        User user = userMapper.selectOne(queryWrapper);
        System.out.println(user);
    }
}

测试结果

2、表映射

5.2.1、局部映射

通过@TableName()注解指定映射的数据库表名,就会按照指定的表名进行映射

如:此时将数据库的表名改为tb_user,要完成表名和实体类名的映射,需要将实体类名也要指定为tb_user,此时就可以通过@TableName()注解设置映射的表名

  1. 创建测试表
sql 复制代码
DROP TABLE IF EXISTS `tb_student`;

CREATE TABLE `tb_student`
(
    id BIGINT NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
    age INT NULL DEFAULT NULL COMMENT '年龄',
    email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
    PRIMARY KEY (id)
);

DELETE FROM `tb_student`;

INSERT INTO `tb_student` (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
  1. 添加实体类
java 复制代码
package com.longdidi.domain;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_student")
public class Student {
    //主键
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}
  1. 添加测试Mapper
java 复制代码
package com.longdidi.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.longdidi.domain.Student;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface StudentMapper extends BaseMapper<Student> {
}
  1. 添加单元测试
java 复制代码
package com.longdidi;

import com.longdidi.domain.Student;
import com.longdidi.mapper.StudentMapper;
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
public class StudentTest {
    @Autowired
    StudentMapper studentMapper;

    @Test
    void getStudent() {
        List<Student> students = studentMapper.selectList(null);
        for (Student student : students) {
            System.out.println(student);
        }
    }
}
  1. 测试结果

5.2.2、全局映射

如果有很多实体类,对应到数据库中的很多表,我们不需要每个依次配置,只需要配置一个全局的设置,它都会给每个实体类名前面添加指定的前缀

这种方式会给所有的表都添加前缀,如果只有个别表需要添加前缀,则使用@TableName()注解

springboot配置如下

yml 复制代码
mybatis-plus:
  global-config:
    db-config:
      table-prefix: tb_

springmvc配置如下

测试过程如下

  1. 注释掉实体类上的@TableName("tb_student")

  2. 配置文件中添加配置

    yml 复制代码
    mybatis-plus:
      global-config:
        db-config:
          table-prefix: tb_
  3. 再次执行上面的单元测试查看测试结果

3、字段映射

5.3.1、字段和属性不一致

当数据库字段和表实体类的属性不一致时可以使用@TableField()注解改变字段和属性的映射,让注解中的名称和表字段保持一致

如:此时将数据库字段的名称改为username,在根据实体类的属性拼接SQL的使用,就会使用@TableField()中指定的名称username进行拼接,完成查询

【示例】

  1. 修改实体类中的name字段为username;

  2. 在username字段上添加@TableField("name")注解将username字段映射成数据库中的字段

  3. 添加单元测试方法

    java 复制代码
        @Test
        void getStudentOne() {
            QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("id", 5L);
            Student student1 = studentMapper.selectOne(queryWrapper);
            System.out.println(student1);
        }
  4. 测试结果

5.3.2、关键字作为属性名

数据库字段和表实体类的属性一致,框架在拼接SQL语句的时候,会使用属性名称直接拼接sql语句

但是如果字段的名是数据库中的关键字就会报错

例如:

sql 复制代码
SELECT id,username AS name,age,email,desc FROM tb_student

这条语句直接进行查询的时候,会出现错误,原因是desc属于关键字,不能直接用于sql查询,要解决这个问题,就需要将desc字段加上``符号,将它变为不是关键字,才能完成查询。

这个问题的根本也是改变生成的SQL语句的字段名称,也就是需要通过@TableField()改变实体类的属性名称,将desc变为desc

【示例】

  1. 在表中添加desc字段

    sql 复制代码
    alter table tb_student add(`desc` VARCHAR(255));
    update tb_student set `desc`='北京';
  2. 实体类中添加desc属性

    java 复制代码
    private String desc;
  3. 使用@TableField()改变属性名

    java 复制代码
    @TableField("`desc`")
  4. 添加单元测试

    java 复制代码
        @Test
        void getStudentDesc() {
            QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("id", 5L);
            Student student1 = studentMapper.selectOne(queryWrapper);
            System.out.println(student1);
        }
  5. 测试结果

    此时可以观察到,框架拼接生成的SQL语句的字段名称变为了desc,这样是可以正常完成查询的

4、字段失效

当数据库中有字段不希望被查询时可以通过@TableField(select = false)来隐藏这个字段,那在拼接SQL语句的时候,就不会拼接这个字段

如:如果不想显示年龄信息,那么可以在age属性上添加这个注解,来隐藏这个字段

【示例】

  1. 字段添加隐藏

  2. 编写单元测试方法

    java 复制代码
        @Test
        void getStudentAge() {
            QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("id", 5L);
            Student student1 = studentMapper.selectOne(queryWrapper);
            System.out.println(student1);
        }
  3. 查询结果

5、视图属性

在实际开发中,有些字段不需要数据库存储,但是却需要展示,需要展示也就是意味着实体类中需要存在这个字段,这些实体类中存在但是数据库中不存在的字段叫做视图字段

根据之前的经验,框架会默认将实体类中的属性作为查询字段进行拼接,但是像这种视图字段是不能够作为查询条件的。因为数据库中没有这个字段,所以查询字段如果包含这个字段,SQL语句会出现问题

通过@TableField(exist = false)来去掉这个字段,不让他作为查询字段

【示例】

  1. 实体类中添加userAddress字段

  2. 字段上添加@TableField(exist = false)

  3. 编写单元测试

    java 复制代码
        @Test
        void getStudentUserAddress() {
            QueryWrapper<Student> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("id", 5L);
            Student student1 = studentMapper.selectOne(queryWrapper);
            System.out.println(student1);
        }
  4. 测试结果

六、条件构造器

1、条件构造器

6.1.1、构造器简介

之前进行的MybatisPlus的操作,没有涉及到条件查询,实际上在开发需求中条件查询是非常普遍的

想要使用MybatisPlus完成条件查询,基于面向对象的思想,万物皆对象,那么查询条件也需要使用对象来完成封装

先看一下在MybatisPlus中和条件有关的类有哪些,他们之间有什么关系,理清楚了这个,在传递条件对象的时候就很清晰了

6.1.2、构造器类

(1)、Wrapper

接口类,条件类的顶层,提供了一些获取和判断相关的方法

java 复制代码
public abstract class Wrapper<T> implements ISqlSegment {}
(2)、AbstractWrapper

抽象类,Wrapper的子类,提供了所有的条件相关方法

java 复制代码
public abstract class AbstractWrapper<T, R, Children extends AbstractWrapper<T, R, Children>> extends Wrapper<T> implements Compare<Children, R>, Nested<Children, Children>, Join<Children>, Func<Children, R> {}
(3)、AbstractLambdaWrapper

抽象类,AbstractWrapper的子类,确定字段参数为方法引用类型

java 复制代码
public abstract class AbstractLambdaWrapper<T, Children extends AbstractLambdaWrapper<T, Children>> extends AbstractWrapper<T, SFunction<T, ?>, Children> {}
(4)、QueryWrapper

实现类,AbstractWrapper的子类,如果需要传递String类型的字段信息,创建该对象

java 复制代码
public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>> implements Query<QueryWrapper<T>, T, String> {}
(5)、LambdaQueryWrapper

实现类,AbstractLambdaWrapper的子类,如果需要传递方法引用方式的字段信息,创建该对象

复制代码
public class LambdaQueryWrapper<T> extends AbstractLambdaWrapper<T, LambdaQueryWrapper<T>> implements Query<LambdaQueryWrapper<T>, T, SFunction<T, ?>> {}

6.1.3、构造器总结

2、构造器查询

创建测试模块MyBatisPlus005

6.2.1、等值查询

(1)、eq
使用QueryWrapper

使用QueryWrapper对象,构建查询条件

java 复制代码
    @Test
    void eq() {
        //1.创建条件查询对象
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //2.设置查询条件,指定查询的字段和匹配的值
        queryWrapper.eq("name", "Jack");
        //3.进行条件查询
        User user = userMapper.selectOne(queryWrapper);
        System.out.println(user);
    }
使用LambdaQueryWrapper

如果每次都是自己进行字段名称的编写,有可能会出现名称写错的情况,可以使用LambdaQueryWrapper对象,在构建字段时使用方法引用的方式来选择字段,这样做可以避免字段拼写错误出现问题

java 复制代码
    @Test
    void eq2() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定String字段名称和值
        lambdaQueryWrapper.eq(User::getName, "Jack");
        //3.使用条件完成查询
        User user = userMapper.selectOne(lambdaQueryWrapper);
        System.out.println(user);
    }
判空查询

构建的条件应该是从客户端通过请求发送过来的,由服务端接收的。在网站中一般都会有多个条件入口,用户可以选择一个或多个条件进行查询,那这个时候在请求时,我们不能确定所有的条件都是有值的,部分条件可能用户没有传值,那该条件就为null。

比如在电商网站中,可以选择多个查询条件。

那为null的条件是不需要进行查询条件拼接的,否则就会出现如下情况,将为null的条件进行拼接,筛选后无法查询出结果

java 复制代码
    @Test
    void isNull() {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getName, null);
        User user = userMapper.selectOne(lambdaQueryWrapper);
        System.out.println(user);
    }

当然要解决这个问题,可以先判断是否为空,根据判断结果选择是否拼接该字段,这个功能其实不需要我们写,由MybatisPlus的方法已经提供好了。

java 复制代码
    @Test
    void isNull2() {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        String name = null;
        lambdaQueryWrapper.eq(name != null, User::getName, name);
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(2)、allEq

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

  • 参数params:表示传递的Map集合
  • 参数null2IsNull:表示对于为null的条件是否判断isNull

先演示一下如何通过多个eq,构建多条件查询

java 复制代码
    @Test
    void allEq1() {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getName, "Tom");
        lambdaQueryWrapper.eq(User::getAge, 28);
        User user = userMapper.selectOne(lambdaQueryWrapper);
        System.out.println(user);
    }

如果此时有多个条件需要同时判断,可以将这多个条件放入到Map集合中,更加的方便

java 复制代码
    @Test
    void allEq2() {
        //1.创建QueryWrapper对象
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();

        //2.构建条件Map
        HashMap<String, Object> hashMap = new HashMap<>();
        hashMap.put("name", "Tom");
        hashMap.put("age", null);

        //3.使用条件完成查询
        queryWrapper.allEq(hashMap, false);
        User user = userMapper.selectOne(queryWrapper);
        System.out.println(user);
    }
(3)、ne

表示不等于判断

java 复制代码
    @Test
    void ne() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定String字段名称和值
        String name = "Tom";
        lambdaQueryWrapper.ne(User::getName, name);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

拼接的SQL如下

6.2.2、范围查询

(1)、gt

大于比较符

java 复制代码
    @Test
    void gt() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        Integer age = 18;
        lambdaQueryWrapper.gt(User::getAge, age);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(2)、ge

大于等于比较符

java 复制代码
    @Test
    void ge() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        Integer age = 18;
        lambdaQueryWrapper.ge(User::getAge, age);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(3)、lt

小于比较符

java 复制代码
    @Test
    void lt() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        Integer age = 30;
        lambdaQueryWrapper.lt(User::getAge, age);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(4)、le

小于等于比较符

java 复制代码
    @Test
    void le() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        Integer age = 30;
        lambdaQueryWrapper.le(User::getAge, age);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(5)、between
java 复制代码
    @Test
    void between() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.between(User::getAge, 18, 30);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(6)、notbetween
java 复制代码
    @Test
    void notBetween() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.notBetween(User::getAge, 18, 30);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

6.2.3、模糊查询

(1)、like

LIKE '%值%'

例: like("name", "王") ---> name like '%王%'

java 复制代码
    @Test
    void like() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.like(User::getName, "J");
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(2)、notLike

NOT LIKE '%值%'

例: notLike("name", "王") ---> name not like '%王%'

java 复制代码
    @Test
    void notLike() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/1befc87d17d74dbdb752b4ba06f622c6.png)

        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.notLike(User::getName, "J");
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(3)、likeLeft

LIKE '%值'

例: likeLeft("name", "王") ---> name like '%王'

java 复制代码
    @Test
    void likeLeft() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.likeLeft(User::getName, "e");
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(4)、likeRight

LIKE '值%'

例: likeRight("name", "王") ---> name like '王%'

java 复制代码
    @Test
    void likeRight() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.likeRight(User::getName, "J");
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

6.2.4、判空查询

(1)、isNull
java 复制代码
    @Test
    void isNullTest() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称
        lambdaQueryWrapper.isNull(User::getName);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(2)、isNotNull
java 复制代码
    @Test
    void isNotNull() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称
        lambdaQueryWrapper.isNotNull(User::getName);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

6.2.5、包含查询

(1)、in
java 复制代码
    //字段=值 or 字段=值  -> in
    @Test
    void in() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        ArrayList<Integer> arrayList = new ArrayList<>();
        Collections.addAll(arrayList, 18, 20, 22);
        lambdaQueryWrapper.in(User::getAge, arrayList);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }


    @Test
    void in2() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.in(User::getAge, 18, 20, 22);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(2)、notIn
java 复制代码
    @Test
    void notIn() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        ArrayList<Integer> arrayList = new ArrayList<>();
        Collections.addAll(arrayList, 18, 20, 22);
        lambdaQueryWrapper.notIn(User::getAge, arrayList);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

    
    //字段!=值 and 字段!=值   -> notIn
    @Test
    void notIn2() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.notIn(User::getAge, 18, 20, 22);
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(3)、inSql
java 复制代码
    @Test
    void inSql() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.inSql(User::getAge, "18,20,22");
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
java 复制代码
    @Test
    void inSql2(){
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.inSql(User::getAge,"select age from user where age > 20");
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(4)、notInSql
java 复制代码
    @Test
    void notInSql(){
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.notInSql(User::getAge,"18,20,22");
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
java 复制代码
    @Test
    void notInSql2(){
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.设置条件,指定字段名称和值
        lambdaQueryWrapper.notInSql(User::getAge,"select age from user where age > 20");
        //3.使用条件完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

6.2.6、分组查询

(1)、group by
java 复制代码
    @Test
    void groupBy() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //分组字段
        queryWrapper.groupBy("age");
        //查询字段
        queryWrapper.select("age,count(*) as field_count");
        List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
        System.out.println(maps);
    }

6.2.7、聚合查询

(1)、having
java 复制代码
    @Test
    void having() {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        //分组字段
        queryWrapper.groupBy("age");
        //查询字段
        queryWrapper.select("age,count(*) as field_count");
        //聚合条件筛选
        queryWrapper.having("field_count = 1");
        List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
        System.out.println(maps);
    }

6.2.8、排序查询

(1)、orderByAsc

排序:ORDER BY 字段, ... ASC

例: orderByAsc("id", "name") ---> order by id ASC,name ASC

java 复制代码
    @Test
    void orderByAsc() {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.orderByAsc(User::getAge, User::getId);
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(2)、orderByDesc

排序:ORDER BY 字段, ... DESC

例: orderByDesc("id", "name") ---> order by id DESC,name DESC

java 复制代码
    @Test
    void orderByDesc() {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.orderByDesc(User::getAge, User::getId);
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(3)、orderBy

排序:ORDER BY 字段, ...

例: orderBy(true, true, "id", "name") ---> order by id ASC,name ASC

java 复制代码
    @Test
    void orderBy() {
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        // 设置排序字段和排序的方式  
        // 参数1:如果排序字段的值为null的时候,是否还要作为排序字段参与排序  
        // 参数2:是否是升序排序  参数3:排序字段
        lambdaQueryWrapper.orderBy(false, true, User::getAge);
        lambdaQueryWrapper.orderBy(true, false, User::getId);
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

6.2.9、FUNC查询

根据不同的情况来选择拼接不同的查询条件

java 复制代码
    @Test
    void func() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.构建逻辑判断语句
        //可能会根据不同的情况来选择拼接不同的查询条件
        lambdaQueryWrapper.func(userLambdaQueryWrapper -> {
            if (false) {
                userLambdaQueryWrapper.eq(User::getId, 1);
            } else {
                userLambdaQueryWrapper.ne(User::getId, 1);
            }
        });
        //3.完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

6.2.10、逻辑查询查询

6.2.10.1、and
  • 正常拼接默认就是and

  • AND 嵌套

    例: and(i -> i.eq("name", "李白").ne("status", "活着")) ---> and (name = '李白' and status<> '活着')

(1)、and查询
java 复制代码
    @Test
    void and(){
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.构建条件查询语句
        lambdaQueryWrapper.gt(User::getAge,22).lt(User::getAge,30);
        //3.完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(2)、and嵌套
java 复制代码
    @Test
    void and2(){
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.构建条件查询语句
        lambdaQueryWrapper.eq(User::getName,"wang").and(i -> i.gt(User::getAge,26).or().lt(User::getAge,22));
        //3.完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
6.2.10.2、or
  • 拼接 OR

  • 主动调用 or 表示紧接着下一个方法不是用 and 连接

    (不调用 or 则默认为使用 and 连接)

(1)、or查询
java 复制代码
    @Test
    void or(){
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.构建条件查询语句
        lambdaQueryWrapper.lt(User::getAge,20).or().gt(User::getAge,23);
        //3.完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(2)、or嵌套
java 复制代码
    @Test
    void or2(){
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getName,"wang").or(i -> i.gt(User::getAge,22).lt(User::getAge,26));
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
6.2.10.3、nested
java 复制代码
@Test
void nested(){
    //1.创建QueryWrapper对象
    LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    //2.构建条件查询语句
    lambdaQueryWrapper.nested(i -> i.eq(User::getName, "Billie").ne(User::getAge, 22));
    //3.完成查询
    List<User> users = userMapper.selectList(lambdaQueryWrapper);
    System.out.println(users);
}

6.2.11、apply自定义条件查询

java 复制代码
    @Test
    void apply() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.构建条件查询语句
        lambdaQueryWrapper.apply("id = 1");
        //3.完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

6.2.12、last查询

在SQL的最后拼接语句

java 复制代码
    @Test
    void last() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.构建条件查询语句
        lambdaQueryWrapper.last("limit 0,2");
        //3.完成查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

6.2.13、exists查询

(1)、exists
java 复制代码
    @Test
    void exists() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.构建查询条件
        lambdaQueryWrapper.exists("select id from user where age = 188");
        //3.查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }
(2)、notExists
java 复制代码
    @Test
    void notExists() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.构建查询条件
        lambdaQueryWrapper.notExists("select id from user where age = 18");
        //3.查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

6.2.14、指定字段查询

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

java 复制代码
    @Test
    void select() {
        //1.创建QueryWrapper对象
        LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //2.构建查询条件
        lambdaQueryWrapper.select(User::getId, User::getName);
        //3.查询
        List<User> users = userMapper.selectList(lambdaQueryWrapper);
        System.out.println(users);
    }

七、ActiveRecord模式

1、ActiveRecord介绍

ActiveRecord(活动记录,简称AR),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的一行记录。

ActiveRecord 一直广受解释型动态语言( PHP 、 Ruby 等)的喜爱,通过围绕一个数据对象进行CRUD操作。而 Java 作为准静态(编译型)语言,对于 ActiveRecord 往往只能感叹其优雅,所以 MP 也在 AR 道路上进行了一定的探索,仅仅需要让实体类继承 Model 类且实现主键指定方法,即可开启 AR 之旅。

2、ActiveRecord实现

创建测试模块MyBatisPlus006

  • 第一步:

    让实体类继承Model类并指定泛型

    java 复制代码
    package com.longdidi.domain;
    
    import com.baomidou.mybatisplus.extension.activerecord.Model;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User extends Model<User> {
        //主键
        private Long id;
        //姓名
        private String name;
        //年龄
        private Integer age;
        //邮箱
        private String email;
    }

    Model类中提供了一些增删改查方法,这样的话就可以直接使用实体类对象调用这些增删改查方法了,简化了操作的语法,但是它的底层依然是需要UserMapper的,所以持久层接口并不能省略

  • 第二步

    提供Mapper接口

    java 复制代码
    package com.longdidi.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.longdidi.domain.User;
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
    
    }

3、测试

7.3.1、添加数据

java 复制代码
@SpringBootTest
public class UserTest {
    @Test
    void activeRecordAdd() {
        User user = new User();
        user.setName("wang");
        user.setAge(35);
        user.setEmail("[email protected]");
        user.insert();
    }
}

7.3.2、删除数据

java 复制代码
@SpringBootTest
public class UserTest {
    @Test
    void activeRecordDelete() {
        User user = new User();
        user.setId(8L);
        user.deleteById();
    }
}

7.3.3、修改数据

java 复制代码
@SpringBootTest
public class UserTest {
    @Test
    void activeRecordUpdate() {
        User user = new User();
        user.setId(6L);
        user.setAge(50);
        user.updateById();
    }
}

7.3.4、查询数据

java 复制代码
@SpringBootTest
public class UserTest {
    @Test
    void activeRecordSelect() {
        User user = new User();
        user.setId(6L);
        User result = user.selectById();
        System.out.println(result);
    }
}

八、SimpleQuery工具类

1、SimpleQuery介绍

SimpleQuery可以对selectList查询后的结果用Stream流进行了一些封装,使其可以返回一些指定结果,简洁了api的调用

2、list

java 复制代码
@SpringBootTest
public class UserTest {
    //基于字段封装集合
    @Test
    void testList() {
        List<Long> ids = SimpleQuery.list(new LambdaQueryWrapper<User>().eq(User::getName, "Mary"), User::getId);
        System.out.println(ids);
    }
}
java 复制代码
@SpringBootTest
public class UserTest {
    //对于封装后的字段进行lambda操作
    @Test
    void testList2() {
        List<String> names = SimpleQuery.list(new LambdaQueryWrapper<User>().eq(User::getName, "Mary"), User::getName, e -> Optional.of(e.getName()).map(String::toLowerCase).ifPresent(e::setName));
        System.out.println(names);
    }
}

3、Map

java 复制代码
@SpringBootTest
public class UserTest {
    // 演示将所有的对象以id,实体的方式封装为Map集合
    @Test
    void testMap() {
        //将所有元素封装为Map形式
        Map<Long, User> idEntityMap = SimpleQuery.keyMap(
                new LambdaQueryWrapper<>(), User::getId);
        System.out.println(idEntityMap);
    }
}
java 复制代码
@SpringBootTest
public class UserTest {
    // 演示将单个对象以id,实体的方式封装为Map集合
    @Test
    void testMap2() {
        //将单个元素封装为Map形式
        Map<Long, User> idEntityMap = SimpleQuery.keyMap(
                new LambdaQueryWrapper<User>().eq(User::getId, 1L), User::getId);
        System.out.println(idEntityMap);
    }
}
java 复制代码
@SpringBootTest
public class UserTest {
    // 演示只想要id和name组成的map
    @Test
    void testMap3() {
        //只想要只想要id和name组成的map
        Map<Long, String> idNameMap = SimpleQuery.map(new LambdaQueryWrapper<>(), User::getId, User::getName);
        System.out.println(idNameMap);
    }
}

4、Group

演示分组效果

java 复制代码
@Test
void testGroup(){
    Map<String, List<User>> nameUsersMap = SimpleQuery.group(new LambdaQueryWrapper<>(), User::getName);
    System.out.println(nameUsersMap);
}

九、主键策略

主键的作用就是唯一标识,可以通过这个唯一标识来定位到这条数据

对于表数据中的主键,我们可以自己设计生成规则,生成主键。但是在更多的场景中,没有特殊要求的话,每次自己手动生成的比较麻烦,这时可以借助框架提供好的主键生成策略,来生成主键,这样比较方便快捷

在MybatisPlus中提供了一个注解,是@TableId,该注解提供了各种的主键生成策略,我们可以通过使用该注解来对于新增的数据指定主键生成策略。那么在以后新增数据的时候,数据就会按照我们指定的主键生成策略来生成对应的主键

创建测试模块MyBatisPlus008

1、AUTO策略

该策略为跟随数据库表的主键递增策略,前提是数据库表的主键要设置为自增

此处要设置好下次递增的数字

实体类添加注解,指定主键生成策略

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User{
    //主键
    @TableId(type = IdType.AUTO)
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

单元测试

java 复制代码
    @Test
    void primaryKey() {
        User user = new User();
        user.setName("Mary");
        user.setAge(35);
        user.setEmail("[email protected]");
        userMapper.insert(user);
    }

2、INPUT策略

该策略表示,必须由我们手动的插入id,否则无法添加数据

java 复制代码
package com.longdidi.domain;

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

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User{
    //主键
    @TableId(type = IdType.INPUT)
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

由于不使用AUTO了,所以把自动递增去掉

这里如果省略不写id会发现无法插入数据

java 复制代码
    @Test![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/86940f0f8d544d5c861b389e4dfc34ae.png)

    void primaryInputKey1() {
        User user = new User();
        user.setName("Mary");
        user.setAge(35);
        user.setEmail("[email protected]");
        userMapper.insert(user);
    }

但是我们自己指定了id,发现可以添加成功

java 复制代码
    @Test
    void primaryInputKey2() {
        User user = new User();
        user.setId(6L);
        user.setName("Mary");
        user.setAge(35);
        user.setEmail("[email protected]");
        userMapper.insert(user);
    }

3、ASSIGN_ID策略

思考一下之前这种自动递增的方式有什么问题:如果将来一张表的数据量很大就需要进行分表

常见的分表策略有两种:

  • 水平拆分

    水平拆分就是将一个大的表按照数据量进行拆分

  • 垂直拆分

    垂直拆分就是将一个大的表按照字段进行拆分

对于拆分后的数据有三点需求,就拿水平拆分来说:

【1】之前的表的主键是有序的,拆分后还是有序的

【2】虽然做了表的拆分,但是每条数据还需要保证主键的唯一性

【3】主键最好不要直接暴露数据的数量,这样容易被外界知道关键信息

那就需要有一种算法,能够实现这三个需求,这个算法就是雪花算法

雪花算法是由一个64位的二进制组成的,最终就是一个Long类型的数值

主要分为四部分存储

【1】1位的符号位,固定值为0

【2】41位的时间戳

【3】10位的机器码,包含5位机器id和5位服务id

【4】12位的序列号

使用雪花算法可以实现有序、唯一、且不直接暴露排序的数字

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User{
    //主键
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

单元测试类

java 复制代码
    @Test
    void primaryKeyAssign(){
        User user = new User();
        user.setName("Jerry");
        user.setAge(38);
        user.setEmail("[email protected]");
        userMapper.insert(user);
    }

可以在插入后发现一个19位长度的id,该id就是雪花算法生成的id,这是二级制的十进制表示形式

4、NONE策略

NONE策略表示不指定主键生成策略,当没有指定主键生成策略或者主键策略为NONE的时候,他跟随的是全局策略

全局配置中 id-type是用于配置主键生成策略的

yml 复制代码
mybatis-plus:
  global-config:
    db-config:
      id-type: assign_id

id-type的默认值是雪花算法

5、ASSIGN_UUID策略

UUID全局唯一标识符,定义为一个字符串主键,采用32位数字组成,编码采用16进制,定义了在时间和空间都完全唯一的系统信息

UUID的编码规则:

【1】1~8位采用系统时间,在系统时间上精确到毫秒级保证时间上的唯一性;

【2】9~16位采用底层的IP地址,在服务器集群中的唯一性;

【3】17~24位采用当前对象的HashCode值,在一个内部对象上的唯一性;

【4】25~32位采用调用方法的一个随机数,在一个对象内的毫秒级的唯一性。

通过以上4种策略可以保证唯一性。在系统中需要用到随机数的地方都可以考虑采用UUID算法。

想要演示UUID的效果需要改变一下表的字段类型和实体类的属性类型

将数据库表的字段类型改为varchar(50)

将实体类的属性类型改为String,并指定主键生成策略为IdType.ASSIGN_UUID

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User{
    //主键
    @TableId(type = IdType.ASSIGN_UUID)
    private String id;
    //姓名
    private String name;
    //年龄
    private Integer age;
    //邮箱
    private String email;
}

单元测试

java 复制代码
    @Test
    void primaryKeyUUID(){
        User user = new User();
        user.setName("Jerry");
        user.setAge(38);
        user.setEmail("[email protected]");
        userMapper.insert(user);
    }

测试结果

十、常用插件/配置

创建测试模块MyBatisPlus009

1、分页插件

分页操作在实际开发中非常的常见,在各种平台和网站中都可以看到分页的效果

例如:京东商城的分页效果

10.1.1、Page分页类

Page 类继承了 IPage 类,实现了简单分页模型

如果你需要实现自己的分页模型,可以继承 Page 类或实现 IPage

属性名 类型 默认值 描述
records List emptyList 查询数据列表
total Long 0 查询列表总记录数
size Long 10 每页显示条数,默认 10
current Long 1 当前页
orders List emptyList 排序字段信息
optimizeCountSql boolean true 自动优化 COUNT SQL
optimizeJoinOfCountSql boolean true 自动优化 COUNT SQL 是否把 join 查询部分移除
searchCount boolean true 是否进行 count 查询
maxLimit Long 单页分页条数限制
countId String XML 自定义 count 查询的 statementId

通过这些方法可以轻松地在 MyBatis-Plus 中实现分页查询,提高应用的性能和用户体验

10.1.2、插件使用

在MybatisPlus中可以通过两种方式实现查询语句

【1】通过MybatisPlus提供的方法来实现条件查询

【2】通过自定义SQL语句的方式来实现查询

接下来就来演示这两种分页方式如何实现

10.1.2.1、分页插件

在大部分场景下,如果我们的SQL没有这么复杂,是可以直接通过MybatisPlus提供的方法来实现查询的,在这种情况下,我们可以通过配置分页插件来实现分页效果

分页的本质就是需要设置一个拦截器,通过拦截器拦截了SQL,通过在SQL语句的结尾添加limit关键字,来实现分页的效果

步骤

  • 第一步(加依赖)

    v3.5.9 起,PaginationInnerInterceptor 已分离出来

    如需使用则需单独引入 mybatis-plus-jsqlparser 依赖

    xml 复制代码
    <dependency>
       <groupId>com.baomidou</groupId>
       <artifactId>mybatis-plus-jsqlparser</artifactId>
       <version>3.5.11</version>
    </dependency>
  • 第二步(配置拦截器)

    通过配置类来指定一个具体数据库的分页插件,因为不同的数据库的方言不同,具体生成的分页语句也会不同,这里我们指定数据库为Mysql数据库

    java 复制代码
    package com.longdidi.config;
    
    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    
    import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MybatisPlusConfig {
        /**
         * 添加分页插件
         */
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加
            // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType
            return interceptor;
        }
    }

    PaginationInnerInterceptor 提供了以下属性来定制分页行为

    属性名 类型 默认值 描述
    overflow boolean false 溢出总页数后是否进行处理
    maxLimit Long 单页分页条数限制
    dbType DbType 数据库类型
    dialect IDialect 方言实现类

    建议单一数据库类型的均设置 dbType

  • 第三步

    实现分页查询效果

    java 复制代码
    package com.longdidi;
    
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.longdidi.domain.User;
    import com.longdidi.mapper.UserMapper;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    public class UserTest {
        @Autowired
        private UserMapper userMapper;
    
        @Test
        void selectPage() {
            //1.创建QueryWrapper对象
            LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            //2.创建分页查询对象,指定当前页和每页显示条数
            IPage<User> page = new Page<>(1, 3);
            //3.执行分页查询
            userMapper.selectPage(page, lambdaQueryWrapper);
            //4.查看分页查询的结果
            System.out.println("当前页码值:" + page.getCurrent());
            System.out.println("每页显示数:" + page.getSize());
            System.out.println("总页数:" + page.getPages());
            System.out.println("总条数:" + page.getTotal());
            System.out.println("当前页数据:" + page.getRecords());
        }
    
    }
  • 第四步

10.1.2.2、自定义分页插件

在某些场景下需要自定义SQL语句来进行查询

步骤

  • 第一步(加依赖)

    v3.5.9 起,PaginationInnerInterceptor 已分离出来

    如需使用则需单独引入 mybatis-plus-jsqlparser 依赖

    xml 复制代码
    <dependency>
       <groupId>com.baomidou</groupId>
       <artifactId>mybatis-plus-jsqlparser</artifactId>
       <version>3.5.11</version>
    </dependency>
  • 第二步

    在UserMapper.xml映射配置文件中提供查询语句

    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.longdidi.mapper.UserMapper">
        
        <select id="selectByName" resultType="com.longdidi.domain.User">
            select * from user where name = #{name}
        </select>
    
    </mapper>
  • 第三步

    在Mapper接口中提供对应的方法,方法中将IPage对象作为参数传入

    java 复制代码
    package com.longdidi.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.longdidi.domain.User;
    import org.apache.ibatis.annotations.Mapper;
    
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
        IPage<User> selectByName(IPage<User> page, String name);
    }
  • 第四步

    实现分页查询效果

    java 复制代码
        @Test
        void selectPage2() {
            //1.创建分页查询对象,指定当前页和每页显示条数
            IPage<User> page = new Page<>(1, 2);
            //2.执行分页查询
            userMapper.selectByName(page, "Mary");
            //3.查看分页查询的结果
            System.out.println("当前页码值:" + page.getCurrent());
            System.out.println("每页显示数:" + page.getSize());
            System.out.println("总页数:" + page.getPages());
            System.out.println("总条数:" + page.getTotal());
            System.out.println("当前页数据:" + page.getRecords());
        }

2、逻辑删除配置

前面完成了基本的增删改查操作,但是对于删除操作来说,在实际开发中一般不会真正的删除数据

举个例子:

在电商网站中会上架很多商品,这些商品下架以后,如果将这些商品从数据库中删除,那么在年底统计商品数据信息的时候,这个商品要统计的,所以这个商品信息是不能删除的

如果商城中的商品下架了,这时候将商品从数据库删掉

那到了年终总结的时候,要总结一下这一年的销售额,发现少了20000,这肯定不合理,所以是不能将数据真实删除的。

这里就采取逻辑删除的方案,逻辑删除的操作就是增加一个字段表示这个数据的状态,如果一条数据需要删除,通过改变这条数据的状态来实现,这样既可以表示这条数据是删除的状态,又保留了数据以便以后统计

10.2.1、局部配置

  • 第一步

    先在表中增加一列字段,表示是否删除的状态,这里使用的字段类型为int类型,通过1表示该条数据可用,0表示该条数据不可用

    sql 复制代码
    alter table user add(status int(11));
  • 第二步

    实体类添加一个字段为Integer,用于对应表中的字段

    java 复制代码
    private Integer status;
  • 第三步

    添加@TableLogic(value = "1",delval = "0")注解表示该字段是逻辑删除字段,并配置删除与不删除对应的逻辑值

    java 复制代码
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User{
        //主键
        private Long id;
        //姓名
        private String name;
        //年龄
        private Integer age;
        //邮箱
        private String email;
    
        @TableLogic(value = "1",delval = "0")
        private Integer status;
    }
  • 第四步

    测试逻辑删除效果

    java 复制代码
        @Test
        void logicDelete(){
            userMapper.deleteById(5L);
        }

    查看拼接的SQL语句,发现在执行删除操作的时候,语句变成了修改,是将这条数据的状态由1变为的0,表示这条数据为删除状态

10.2.2、全局配置

可以通过全局配置来实现逻辑删除的效果

java 复制代码
mybatis-plus:
  global-config:
    db-config:
      # 配置逻辑删除的字段
      logic-delete-field: status
      # 配置逻辑删除的值
      logic-delete-value: 0
      # 配置逻辑不删除的值
      logic-not-delete-value: 1

3、通用枚举配置

当我们想要表示一组信息,这组信息只能从一些固定的值中进行选择,不能随意写,在这种场景下,枚举就非常的合适。

例如我们想要表示性别,性别只有两个值,要么是男性,要么是女性,那我们就可以使用枚举来描述性别。

  • 第一步

    先在表中添加一个字段,表示性别,这里我们一般使用int来描述,因为int类型可以通过0和1这两个值来表示两个不同的性别

    sql 复制代码
    alter table user add(gender int(2));
  • 第二步

    编写枚举类

    java 复制代码
    package com.longdidi.enums;
    
    public enum GenderEnum {
    
        MAN(0, "男"),
        WOMAN(1, "女");
    
        private Integer gender;
        private String genderName;
    
        GenderEnum(Integer gender, String genderName) {
            this.gender = gender;
            this.genderName = genderName;
        }
    }
  • 第三步

    实体类添加相关字段

    java 复制代码
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User{
        //主键
        private Long id;
        //姓名
        private String name;
        //年龄
        private Integer age;
        //邮箱
        private String email;
    
        @TableLogic(value = "1",delval = "0")
        private Integer status;
    
        private GenderEnum gender;
    }
  • 第四步

    为枚举属性gender添加上@EnumValue注解

    如果不加该注解,那么是无法将一个枚举类型作为int数字插入到数据库中

    这里对于枚举类型都给了对应的int的值,所以这里只需要进行一个@EnumValue配置,就可以将枚举类型作为数字插入到数据库中

    java 复制代码
    package com.longdidi.enums;
    
    import com.baomidou.mybatisplus.annotation.EnumValue;
    
    public enum GenderEnum {
    
        MAN(0, "男"),
        WOMAN(1, "女");
    
        @EnumValue
        private Integer gender;
        private String genderName;
    
        GenderEnum(Integer gender, String genderName) {
            this.gender = gender;
            this.genderName = genderName;
        }
    }
  • 第五步

    单元测试

    java 复制代码
        @Test
        void enumTest() {
            User user = new User();
            user.setName("liu");
            user.setAge(29);
            user.setEmail("[email protected]");
            user.setGender(GenderEnum.MAN);
            user.setStatus(1);
            userMapper.insert(user);
        }

4、字段类型处理器

在某些场景下,我们在实体类中是使用Map集合作为属性接收前端传递过来的数据的,但是这些数据存储在数据库时,我们使用的是json格式的数据进行存储,json本质是一个字符串,就是varchar类型

这里就需要使用到字段类型处理器来完成实体类的Map类型和数据库的varchar类型的互相转换

  • 第一步

    先在实体类中添加一个字段,Map类型

    java 复制代码
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User{
        //主键
        private Long id;
        //姓名
        private String name;
        //年龄
        private Integer age;
        //邮箱
        private String email;
    
        @TableLogic(value = "1",delval = "0")
        private Integer status;
    
        private GenderEnum gender;
    
        private Map<String,String> contact;//联系方式
    }
  • 第二步

    在数据库中我们添加一个字段,为varchar类型

    sql 复制代码
    alter table user add(contact varchar(255));
  • 第三步

    为实体类添加上对应的注解,实现使用字段类型处理器进行不同类型数据转换

    添加两处位置

    • 类上

      添加@TableName(autoResultMap = true)

      查询时将json字符串封装为Map集合

    • 字段上

      添加@TableField(typeHandler = FastjsonTypeHandler.class)

      指定字段类型处理器

    java 复制代码
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @TableName(autoResultMap = true)//查询时将json字符串封装为Map集合
    public class User{
        //主键
        private Long id;
        //姓名
        private String name;
        //年龄
        private Integer age;
        //邮箱
        private String email;
    
        @TableLogic(value = "1",delval = "0")
        private Integer status;
    
        private GenderEnum gender;
        
        @TableField(typeHandler = FastjsonTypeHandler.class)//指定字段类型处理器
        private Map<String,String> contact;//联系方式
    }
  • 第四步

    引入依赖

    字段类型处理器依赖Fastjson这个Json处理器,所以我们需要引入对应的依赖

    xml 复制代码
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.83</version>
    </dependency>
  • 第五步

    测试添加操作

    java 复制代码
        @Test
        void typeHandler() {
            User user = new User();
            user.setName("zhang");
            user.setAge(28);
            user.setEmail("[email protected]");
            user.setGender(GenderEnum.MAN);
            user.setStatus(1);
            HashMap<String, String> contact = new HashMap<>();
            contact.put("phone", "010-1234567");
            contact.put("tel", "13388889999");
            user.setContact(contact);
    
            userMapper.insert(user);
        }
  • 第六步

    测试查询

    java 复制代码
        @Test
        void typeHandlerSelect(){
            List<User> users = userMapper.selectList(null);
            System.out.println(users);
        }

5、自动填充

在项目中有一些属性,如果我们不希望每次都填充的话,我们可以设置为自动填充

比如常见的创建时间和更新时间可以设置为自动填充

  • 第一步

    在数据库的表中添加两个字段

    java 复制代码
    alter table user add(create_time datetime);
    alter table user add(update_time datetime);

    注意只有设置了下划线和小驼峰映射,这种mysql的写法才能和实体类完成映射

    yml 复制代码
    mybatis-plus:
      configuration:
        map-underscore-to-camel-case: true
  • 第二步

    在实体类中,添加对应字段,并为需要自动填充的属性指定填充时机

    java 复制代码
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @TableName(autoResultMap = true)//查询时将json字符串封装为Map集合
    public class User{
        //主键
        private Long id;
        //姓名
        private String name;
        //年龄
        private Integer age;
        //邮箱
        private String email;
    
        @TableLogic(value = "1",delval = "0")
        private Integer status;
    
        private GenderEnum gender;
    
        @TableField(typeHandler = FastjsonTypeHandler.class)//指定字段类型处理器
        private Map<String,String> contact;//联系方式
    
        // 新增时自动填充
        @TableField(fill = FieldFill.INSERT)
        private Date createTime;
    
        // 新增和修改时自动填充
        @TableField(fill = FieldFill.INSERT_UPDATE)
        private Date updateTime;
    
    }
  • 第三步

    编写自动填充处理器,指定填充策略

    java 复制代码
    package com.longdidi.config;
    
    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    
    @Component
    public class MyMetaHandler implements MetaObjectHandler {
        @Override
        public void insertFill(MetaObject metaObject) {
            setFieldValByName("createTime", new Date(), metaObject);
            setFieldValByName("updateTime", new Date(), metaObject);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            setFieldValByName("updateTime", new Date(), metaObject);
        }
    }
  • 第四步

    • 设置一下mysql时区

      复制代码
      set GLOBAL time_zone='+8:00';
      select NOW();
    • 修改配置文件时区

      将配置文件的时区修改为serverTimezone=Asia/Shanghai

  • 第五步

    测试插入操作

    java 复制代码
        @Test
        void testFill(){
            User user = new User();
            user.setName("wang");
            user.setAge(35);
            user.setEmail("[email protected]");
            user.setGender(GenderEnum.MAN);
            user.setStatus(1);
            HashMap<String, String> contact = new HashMap<>();
            contact.put("phone","010-1234567");
            contact.put("tel","13388889999");
            userMapper.insert(user);
        }
  • 第六步

    测试更新操作

    java 复制代码
        @Test
        void testFill2() {
            User user = new User();
            user.setId(6L);
            user.setName("wang");
            user.setAge(35);
            user.setEmail("[email protected]");
            user.setGender(GenderEnum.MAN);
            user.setStatus(1);
            HashMap<String, String> contact = new HashMap<>();
            contact.put("phone", "010-1234567");
            contact.put("tel", "13388889999");
    
            userMapper.updateById(user);
        }

6、防止全表更新/删除插件

在实际开发中全表更新和删除是非常危险的操作,在MybatisPlus中提供了插件和防止这种危险操作的发生

先演示一下全表更新的场景

java 复制代码
    @Test
    public void testUpdateAll() {
        User user = new User();
        user.setGender(GenderEnum.MAN);
        userService.saveOrUpdate(user);
    }

这是很危险的

解决方式

  • 第一步

    注入MybatisPlusInterceptor类,并配置BlockAttackInnerInterceptor拦截器

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

    测试全表更新,会出现抛出异常,防止了全表更新

    java 复制代码
        @Test
        void allUpdate() {
            User user = new User();
            user.setName("wang");
            userService.update(user, null);
        }

7、MybatisX快速开发插件

MybatisX是一款IDEA提供的插件,目的是为了我们简化Mybatis以及MybatisPlus框架而生。

10.7.1、插件安装

【1】首先选择File -> Settings

【2】选择Plugins

【3】搜索MybatisX,点击安装

【4】在已安装的目录下可以看到有了这个IDEA插件

【5】重启IDEA,让该插件生效,至此MybatisX插件就安装完毕了

10.7.2、插件功能

插件安装好以后来看一下插件的功能

(1)、接口和映射文件跳转
(2)、逆向工程

逆向工程就是通过数据库表结构,逆向产生Java工程的结构

包括以下几点:

(1)实体类

(2)Mapper接口

(3)Mapper映射文件

(4)Service接口

(5)Service实现类

  • 第一步

    创建一个模块MyBatisPlusX001用于测试逆向工程功能

  • 第二步

    引入依赖

    xml 复制代码
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--mysql驱动-->
            <dependency>
                <groupId>com.mysql</groupId>
                <artifactId>mysql-connector-j</artifactId>
                <version>8.4.0</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
                <version>3.5.11</version>
            </dependency>
            <!--lombok插件依赖-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.38</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
  • 第三步

    配置数据库连接信息

    yml 复制代码
    spring:
      datasource:
        password: root
        username: root
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.0.109:3306/mybatisplus?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false&rewriteBatchedStatements=true
  • 第四步

    使用IDEA连接mysql

  • 第五步

    填写连接信息,测试连接通过

  • 第六步

    找到表右键,选择插件的逆向工程选项

  • 第七步

    编写逆向工程配置信息

  • 第八步

    添加配置选项

  • 第九步

    观察生成结构,发现下面这些内容都已产生

    (1)实体类

    (2)Mapper接口

    (3)Mapper映射文件

    (4)Service接口

    (5)Service映射文件

  • 第十步

    在Mapper接口上添加@Mapper注解

    java 复制代码
    /**
     * @author hailo
     * @description 针对表【user】的数据库操作Mapper
     * @createDate 2025-04-10 12:25:14
     * @Entity com.longdidi.domain.User
     */
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
    
    }
  • 第十一步

    测试代码环境

    java 复制代码
    @SpringBootTest
    public class UserTest {
        @Autowired
        UserMapper userMapper;
    
        @Test
        void test() {
            User user = userMapper.selectById(1);
        }
    }
(3)、常见需求代码生成

虽然Mapper接口中提供了一些常见方法,可以直接使用这些常见的方法来完成sql操作,但是对于实际场景中各种复杂的操作需求来说,依然是不够用的,所以MybatisX提供了更多的方法,以及可以根据这些方法直接生成对应的sql语句,这样使得开发变得更加的简单

  • 第一步

    在Mapper接口中根据名称联想常见的操作

    java 复制代码
    @Mapper
    public interface UserMapper extends BaseMapper<User> {
        //添加操作
        int insertSelective(User user);
    
        //删除操作
        int deleteByNameAndAge(@Param("name") String name, @Param("age") Integer age);
    
        //修改操作
        int updateNameByAge(@Param("name") String name, @Param("age") Integer age);
    
        //查询操作
        List<User> selectAllByAgeBetween(@Param("beginAge") Integer beginAge, @Param("endAge") Integer endAge);
    }
  • 第二步

    自动生成SQL

  • 第三步

    在映射配置文件中,会生成对应的sql,并不需要我们编写

    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.longdidi.mapper.UserMapper">
        <insert id="insertSelective">
            insert into user
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="id != null">id,</if>
                <if test="name != null">name,</if>
                <if test="age != null">age,</if>
                <if test="email != null">email,</if>
            </trim>
            values
            <trim prefix="(" suffix=")" suffixOverrides=",">
                <if test="id != null">#{id,jdbcType=BIGINT},</if>
                <if test="name != null">#{name,jdbcType=VARCHAR},</if>
                <if test="age != null">#{age,jdbcType=INTEGER},</if>
                <if test="email != null">#{email,jdbcType=VARCHAR},</if>
            </trim>
        </insert>
    
    </mapper>

8、乐观锁插件

首先要了解开发中的一个常见场景叫做并发请求

并发请求就是在同一时刻有多个请求同时请求服务器资源,如果是获取信息,没什么问题,但是如果是对于信息做修改操作呢,那就会出现问题。

这里举一个具体的例子,比如目前商品的库存只剩余1件了,这个时候有多个用户都想要购买这件商品,都发起了购买商品的请求,那么能让这多个用户都购买到么,肯定是不行的,因为多个用户都买到了这件商品,那么就会出现超卖问题,库存不够是没法发货的。所以在开发中就要解决这种超卖的问题

抛开超卖这一种场景,诸如此类并发访问的场景非常多,这类场景的核心问题就是,一个请求在执行的过程中,其他请求不能改变数据,如果是一次完整的请求,在该请求的过程中其他请求没有对于这个数据产生修改操作,那么这个请求是能够正常修改数据的。如果该请求在改变数据的过程中,已经有其他请求改变了数据,那该请求就不去改变这条数据了。

想要解决这类问题,最常见的就是加锁的思想,锁可以用验证在请求的执行过程中,是否有数据发生改变。

常见的数据库锁类型有两种,悲观锁和乐观锁

一次完成的修改操作是,先查询数据,然后修改数据。

  • 悲观锁

    悲观锁是在查询的时候就锁定数据,在这次请求未完成之前,不会释放锁。等到这次请求完毕以后,再释放锁,释放了锁以后,其他请求才可以对于这条数据完成读写

    这样做的操作能够保证读取到的信息就是当前的信息,保证了信息的正确性,但是并发效率很低,在实际开发中使用悲观锁的场景很少,因为在并发时我们是要保证效率的。

  • 乐观锁

    乐观锁是通过表字段完成设计的,他的核心思想是,在读取的时候不加锁,其他请求依然可以读取到这个数据,在修改的时候判断一个数据是否有被修改过,如果有被修改过,那本次请求的修改操作失效。

    具体的通过sql是这样实现的

    sql 复制代码
    Update 表 set 字段 = 新值,version = version + 1 where version = 1

    这样做的操作是不会对于数据读取产生影响,并发的效率较高。但是可能目前看到的数据并不是真实信息数据,是被修改之前的,但是在很多场景下是可以容忍的,并不是产生很大影响,例如很多时候我们看到的是有库存,或者都加入到购物车了,但是点进去以后库存没有了。

使用步骤

  • 第一步

    在数据库表中添加一个字段version,表示版本,默认值是1

    sql 复制代码
    alter table user add(version int(11) default '1');
  • 第二步

    找到实体类,添加对应的属性,并使用@Version标注为这是一个乐观锁字段信息

    java 复制代码
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @TableName(autoResultMap = true)//查询时将json字符串封装为Map集合
    public class User{
        //主键
        private Long id;
        //姓名
        private String name;
        //年龄
        private Integer age;
        //邮箱
        private String email;
    
        @TableLogic(value = "1",delval = "0")
        private Integer status;
    
        private GenderEnum gender;
    
        @TableField(typeHandler = FastjsonTypeHandler.class)//指定字段类型处理器
        private Map<String,String> contact;//联系方式
    
        // 新增时自动填充
        @TableField(fill = FieldFill.INSERT)
        private Date createTime;
    
        // 新增和修改时自动填充
        @TableField(fill = FieldFill.INSERT_UPDATE)
        private Date updateTime;
    
        @Version
        private Integer version;
    
    }
  • 第三步

    添加拦截器

    因为要对每条修改语句完成语句的增强,这里通过拦截器的配置,让每条修改的sql语句在执行的时候,都加上版本控制的功能

    java 复制代码
    @Configuration
    public class MybatisPlusConfig {
    
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor(){
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
            return interceptor;
        }
    }
  • 第四步

    测试效果,这里模拟先查询,再修改

    java 复制代码
        @Test
        void updateTest(){
            User user = userMapper.selectById(4L);
            user.setName("li");
            userMapper.updateById(user);
        }

    通过查看拼接好的SQL语句发现查询时将User的数据查询出来,是包含version版本信息的

    当我们完成修改时会将版本号 + 1

    此时查看数据发现,更改姓名后,version已经为2了

    接下来模拟一下当出现多个修改请求的时候,是否能够做到乐观锁的效果

    乐观锁的效果是,一个请求在修改的过程中,是允许另一个请求查询的,但是修改时会通过版本号是否改变来决定是否修改,如果版本号变了,证明已经有请求修改过数据了,那这次修改不生效,如果版本号没有发生变化,那就完成修改。

    java 复制代码
    @Test
    void updateTest2(){
        //模拟操作1的查询操作
        User user1 = userMapper.selectById(4L);
    
        //模拟操作2的查询操作
        User user2 = userMapper.selectById(4L);
    
        //模拟操作2的修改操作
        user2.setName("lisi");
        userMapper.updateById(user2);
    
        //模拟操作1的修改操作
        user1.setName("zhangsan");
        userMapper.updateById(user1);
    }

    来看下这段代码的执行过程,这段代码其实是两次操作,只不过操作1在执行的过程中,有操作2完成了对于数据的修改,这时操作1就无法再次进行修改了

    操作1的查询:此时版本为2

    操作2的查询:此时版本为2

    操作2的修改:此时检查版本,版本没有变化,所以完成修改,并将版本改为3

    操作1的修改:此时检查版本,版本已经有最初获取的版本信息发生了变化,所以杜绝修改

9、代码生成器

MyBatis-Plus 全新代码生成器在继承原有功能的基础上,引入了更加灵活和高效的 builder 模式,使得开发者能够快速生成符合需求的代码,同时保持代码的优雅和整洁。

这个新特性旨在进一步提升开发效率,减少重复劳动,让开发者能够更加专注于业务逻辑的实现。

10.9.1、特点说明

  1. Builder 模式:通过 builder 模式,开发者可以链式调用配置方法,直观地构建代码生成器的配置,使得代码更加清晰易读。
  2. 快速配置:新代码生成器提供了快速配置选项,如全局配置、包配置、策略配置等,可以一键设置常用选项,快速启动代码生成过程。
  3. 模板引擎:支持 Freemarker 等模板引擎,允许开发者自定义代码模板,以生成符合项目特定风格的代码。
  4. Lombok 集成:新代码生成器默认启用 Lombok,减少了样板代码的编写,提高了代码的可读性和维护性。
  5. 多数据库支持:支持多种数据库类型,如 MySQL、Oracle、SQL Server 等,只需配置相应的数据库连接信息即可。
  6. 灵活的数据源配置:提供了丰富的数据源配置选项,包括数据库查询方式、类型转换器、关键字处理器等,满足不同数据库的需求。

10.9.2、使用方式

参考官网,使用代码生成器需要引入两个依赖

  • 第一步

    创建测试模块MyBatisPlusX2

  • 第二步

    引入依赖

    xml 复制代码
    <!--代码生成器依赖-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-generator</artifactId>
        <version>3.5.11</version>
    </dependency>
    
    <!--freemarker模板依赖-->
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
        <version>2.3.34</version>
    </dependency>
  • 第三步

    编写代码生成器代码

    java 复制代码
    package com.longdidi;
    
    import com.baomidou.mybatisplus.generator.FastAutoGenerator;
    import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    public class UserTests {
        public static void main(String[] args) {
            // 使用 FastAutoGenerator 快速配置代码生成器
            FastAutoGenerator.create("jdbc:mysql://192.168.0.109:3306/mybatisplus?serverTimezone=GMT%2B8", "root", "root")
                    .globalConfig(builder -> {
                        builder.author("longdidi") // 设置作者
                                .outputDir("H:\\WorkSpace\\WorkSpace3\\MyBatisPlus\\MyBatisPlusX002\\src\\main\\java"); // 输出目录
                    })
                    .packageConfig(builder -> {
                        builder.parent("com.longdidi") // 设置父包名
                                .entity("domain") // 设置实体类包名
                                .mapper("mapper") // 设置 Mapper 接口包名
                                .service("service") // 设置 Service 接口包名
                                .serviceImpl("service.impl") // 设置 Service 实现类包名
                                .xml("mappers"); // 设置 Mapper XML 文件包名
                    })
                    .strategyConfig(builder -> {
                        builder.addInclude("user", "tb_student") // 设置需要生成的表名
                                .entityBuilder()
                                .enableLombok() // 启用 Lombok
                                .enableTableFieldAnnotation() // 启用字段注解
                                .controllerBuilder()
                                .enableRestStyle(); // 启用 REST 风格
                    })
                    .templateEngine(new FreemarkerTemplateEngine()) // 使用 Freemarker 模板引擎
                    .execute(); // 执行生成
        }
    }
  • 第四步

    执行,查看生成效果

10.9.3、配置说明

参考官方文档:https://baomidou.com/reference/new-code-generator-configuration/

(1)、数据库配置
基础配置
属性 说明 示例
url jdbc 路径 jdbc:mysql://127.0.0.1:3306/mybatis-plus
username 数据库账号 root
password 数据库密码 123456
java 复制代码
DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder("jdbc:mysql://127.0.0.1:3306/mybatis-plus", "root", "123456").build();
可选配置
方法 说明 示例
dbQuery(IDbQuery) 数据库查询 new MySqlQuery(),只在SQLQuery下生效
schema(String) 数据库 schema(部分数据库适用) mybatis-plus
typeConvert(ITypeConvert) 数据库类型转换器 new MySqlTypeConvert(),只在SQLQuery下生效
keyWordsHandler(IKeyWordsHandler) 数据库关键字处理器 new MySqlKeyWordsHandler()
typeConvertHandler(ITypeConvertHandler) 类型转换器(默认) 自定义实现ITypeConvertHandler,只在DefaultQuery下生效
databaseQueryClass(AbstractDatabaseQuery) 数据库查询方式 默认DefaultQuery.class(通用元数据), SQLQuery.class(SQL查询)
java 复制代码
// 使用SQL查询的方式生成代码,属于旧的代码生成方式,通用性不是好,老的代码可以继续使用,适配数据库需要完成dbQuery和typeConvert的扩展,后期不再维护这种方式
DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder("jdbc:mysql://127.0.0.1:3306/mybatis-plus", "root", "123456")
    .dbQuery(new MySqlQuery())
    .schema("mybatis-plus")
    .typeConvert(new MySqlTypeConvert())
    .keyWordsHandler(new MySqlKeyWordsHandler())
    .databaseQueryClass(SQLQuery.class)
    .build();

// 使用元数据查询的方式生成代码,默认已经根据jdbcType来适配java类型,支持使用typeConvertHandler来转换需要映射的类型映射
DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder("jdbc:mysql://127.0.0.1:3306/mybatis-plus", "root", "123456")
    .schema("mybatis-plus")
    .keyWordsHandler(new MySqlKeyWordsHandler())
    .build();
(2)、全局配置 (GlobalConfig)

全局配置提供了对代码生成器整体行为的设置,包括输出目录、作者信息、Kotlin 模式、Swagger 集成、时间类型策略等。

方法说明
方法 说明 示例
disableOpenDir() 禁止自动打开输出目录 默认值: true
outputDir(String) 指定代码生成的输出目录 /opt/baomidou 默认值: Windows: D:// Linux/Mac: /tmp
author(String) 设置作者名 baomidou 默认值: 配置文件中的作者名
enableKotlin() 开启 Kotlin 模式 默认值: false
enableSwagger() 开启 Swagger 模式 默认值: false
dateType(DateType) 设置时间类型策略 DateType.ONLY_DATE 默认值: DateType.TIME_PACK
commentDate(String) 设置注释日期格式 默认值: yyyy-MM-dd
示例配置
java 复制代码
GlobalConfig globalConfig = new GlobalConfig.Builder()

    .disableOpenDir(false) // 允许自动打开输出目录

    .outputDir("/path/to/output") // 设置输出目录

    .author("Your Name") // 设置作者名

    .enableKotlin(true) // 开启 Kotlin 模式

    .enableSwagger(true) // 开启 Swagger 模式

    .dateType(DateType.ONLY_DATE) // 设置时间类型策略

    .commentDate("yyyy-MM-dd") // 设置注释日期格式

    .build();
(2)、包配置 (PackageConfig)

包配置用于定义生成代码的包结构,包括父包名、模块名、实体类包名、服务层包名等。

方法说明
方法 说明 示例
parent(String) 设置父包名 默认值: com.baomidou
moduleName(String) 设置父包模块名 默认值: 无
entity(String) 设置 Entity 包名 默认值: entity
service(String) 设置 Service 包名 默认值: service
serviceImpl(String) 设置 Service Impl 包名 默认值: service.impl
mapper(String) 设置 Mapper 包名 默认值: mapper
xml(String) 设置 Mapper XML 包名 默认值: mapper.xml
controller(String) 设置 Controller 包名 默认值: controller
pathInfo(Map<OutputFile, String>) 设置路径配置信息 Collections.singletonMap(OutputFile.mapperXml, "D://")
示例配置
java 复制代码
PackageConfig packageConfig = new PackageConfig.Builder()

    .parent("com.example") // 设置父包名

    .moduleName("myapp") // 设置父包模块名

    .entity("model") // 设置 Entity 包名

    .service("service") // 设置 Service 包名

    .serviceImpl("service.impl") // 设置 Service Impl 包名

    .mapper("dao") // 设置 Mapper 包名

    .xml("mappers") // 设置 Mapper XML 包名

    .controller("controller") // 设置 Controller 包名

    .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "/path/to/xml")) // 设置路径配置信息

    .build();
(4)、模板配置 (TemplateConfig)

注意:自 MyBatis-Plus 3.5.6 版本开始,模板配置已迁移至 StrategyConfig 中。以下是迁移后的配置方式。

方法说明
方法 说明 示例
entityBuilder() 设置实体类模板
javaTemplate(String) 设置 Java 实体模板 /templates/entity.java
disable() 禁用实体类生成
serviceBuilder() 设置 Service 层模板
disableService() 禁用 Service 层生成
serviceTemplate(String) 设置 Service 模板 /templates/service.java
serviceImplTemplate(String) 设置 ServiceImpl 模板 /templates/serviceImpl.java
示例配置
java 复制代码
// 3.5.6 之前的配置示例
TemplateConfig templateConfig = new TemplateConfig.Builder()
    .disable(TemplateType.ENTITY)
    .entity("/templates/entity.java")
    .service("/templates/service.java")
    .serviceImpl("/templates/serviceImpl.java")
    .mapper("/templates/mapper.java")
    .mapperXml("/templates/mapper.xml")
    .controller("/templates/controller.java")
    .build();

// 3.5.6 之后的配置示例
StrategyConfig strategyConfig = new StrategyConfig.Builder()
    .entityBuilder()
    .javaTemplate("/templates/entity.java") // 设置实体类模板
    .disable() // 禁用实体类生成
    .serviceBuilder()
    .disableService() // 禁用 Service 层生成
    .serviceTemplate("/templates/service.java") // 设置 Service 模板
    .serviceImplTemplate("/templates/serviceImpl.java") // 设置 ServiceImpl 模板
    .build();
(5)、注入配置 (InjectionConfig)

注入配置允许开发者自定义代码生成器的行为,包括在输出文件之前执行的逻辑、自定义配置 Map 对象、自定义配置模板文件等。

方法说明
方法 说明 示例
beforeOutputFile(BiConsumer<TableInfo, Map<String, Object>>) 输出文件之前执行的逻辑 在生成文件之前执行自定义逻辑,如打印表信息或修改配置数据
customMap(Map<String, Object>) 自定义配置 Map 对象 用于在模板中访问自定义的配置信息,如项目名称、作者等
customFile(Map<String, String>) 自定义配置模板文件 用于指定自定义的模板文件路径,可以格式化文件名,参考测试用例 H2CodeGeneratorTest.testCustomFileByList
示例配置
java 复制代码
InjectionConfig injectionConfig = new InjectionConfig.Builder()
    .beforeOutputFile((tableInfo, objectMap) -> {
        System.out.println("准备生成文件: " + tableInfo.getEntityName());
        // 可以在这里添加自定义逻辑,如修改 objectMap 中的配置
    })
    .customMap(Collections.singletonMap("projectName", "MyBatis-Plus Generator"))
    .customFile(Collections.singletonMap("custom.txt", "/templates/custom.vm"))
    .build();

通过上述配置,开发者可以根据自己的需求,灵活地定制代码生成器的行为。例如,在生成文件之前执行特定的逻辑,或者使用自定义的模板文件来生成代码。这些配置选项提供了极大的灵活性,使得 MyBatis-Plus 代码生成器能够适应各种复杂的项目需求。

(6)、策略配置 (StrategyConfig)

策略配置是 MyBatis-Plus 代码生成器的核心部分,它允许开发者根据项目需求定制代码生成的规则,包括命名模式、表和字段的过滤、以及各个代码模块的生成策略。

方法说明
方法 说明 示例
enableCapitalMode 开启大写命名 默认值: false
enableSkipView 开启跳过视图 默认值: false
disableSqlFilter 禁用 SQL 过滤 默认值: true,如果 SQL 过滤不支持,可以关闭此选项
enableSchema 启用 schema 默认值: false,多 schema 场景时启用
likeTable(LikeTable) 模糊表匹配(SQL 过滤) notLikeTable 互斥,只能配置一项
notLikeTable(LikeTable) 模糊表排除(SQL 过滤) likeTable 互斥,只能配置一项
addInclude(String...) 增加表匹配(内存过滤) addExclude 互斥,只能配置一项,支持正则匹配,如 ^t_.* 匹配所有以 t_ 开头的表名
addExclude(String...) 增加表排除匹配(内存过滤) addInclude 互斥,只能配置一项,支持正则匹配,如 .*st$ 匹配所有以 st 结尾的表名
addTablePrefix(String...) 增加过滤表前缀
addTableSuffix(String...) 增加过滤表后缀
addFieldPrefix(String...) 增加过滤字段前缀
addFieldSuffix(String...) 增加过滤字段后缀
outputFile 内置模板输出文件处理 参考测试用例 H2CodeGeneratorTest.testOutputFile
entityBuilder 实体策略配置
controllerBuilder Controller 策略配置
mapperBuilder Mapper 策略配置
serviceBuilder Service 策略配置
示例配置
java 复制代码
StrategyConfig strategyConfig = new StrategyConfig.Builder()
    .enableCapitalMode() // 开启大写命名
    .enableSkipView() // 开启跳过视图
    .disableSqlFilter() // 禁用 SQL 过滤
    .likeTable(new LikeTable("USER")) // 模糊匹配表名
    .addInclude("t_simple") // 增加表匹配
    .addTablePrefix("t_", "c_") // 增加过滤表前缀
    .addFieldSuffix("_flag") // 增加过滤字段后缀
    .build();
(7)、Entity 策略配置

实体策略配置用于定制实体类的生成规则,包括父类、序列化版本 UID、文件覆盖、字段常量、链式模型、Lombok 模型等。

方法说明
方法 说明 示例
nameConvert(INameConvert) 名称转换实现
superClass(Class<?>) 设置父类 BaseEntity.class
superClass(String) 设置父类 com.baomidou.global.BaseEntity
disableSerialVersionUID 禁用生成 serialVersionUID 默认值: true
enableFileOverride 覆盖已生成文件 默认值: false
enableColumnConstant 开启生成字段常量 默认值: false
enableChainModel 开启链式模型 默认值: false
enableLombok 开启 Lombok 模型 默认值: false 默认只有Getter,Setter,自3.5.10后增加ToString
enableRemoveIsPrefix 开启 Boolean 类型字段移除 is 前缀 默认值: false
enableTableFieldAnnotation 开启生成实体时生成字段注解 默认值: false
enableActiveRecord 开启 ActiveRecord 模型 默认值: false
versionColumnName(String) 乐观锁字段名(数据库字段) versionColumnNameversionPropertyName 二选一即可
versionPropertyName(String) 乐观锁属性名(实体) versionColumnNameversionPropertyName 二选一即可
logicDeleteColumnName(String) 逻辑删除字段名(数据库字段) logicDeleteColumnNamelogicDeletePropertyName 二选一即可
logicDeletePropertyName(String) 逻辑删除属性名(实体) logicDeleteColumnNamelogicDeletePropertyName 二选一即可
naming 数据库表映射到实体的命名策略 默认下划线转驼峰命名: NamingStrategy.underline_to_camel
columnNaming 数据库表字段映射到实体的命名策略 默认为 null,未指定按照 naming 执行
addSuperEntityColumns(String...) 添加父类公共字段
addIgnoreColumns(String...) 添加忽略字段
addTableFills(IFill...) 添加表字段填充
addTableFills(List) 添加表字段填充
idType(IdType) 全局主键类型
convertFileName(ConverterFileName) 转换文件名称
formatFileName(String) 格式化文件名称
toString(boolean) 是否生成ToString方法 默认为true, 自3.5.10开始
fieldUseJavaDoc 启用字段文档注释 默认为true, 自3.5.10开始
classAnnotations(ClassAnnotationAttributes) 添加实体类注解 自3.5.10开始
tableAnnotationHandler 表注解处理器 自3.5.10开始
tableFieldAnnotationHandler 字段注解处理器 自3.5.10开始
enableLombok(ClassAnnotationAttributes...) 开启 Lombok 模型并设置Lombok注解 自3.5.10开始. 使用@Data示例: enableLombok(new ClassAnnotationAttributes("@Data","lombok.Data"))
示例配置
java 复制代码
StrategyConfig strategyConfig = new StrategyConfig.Builder()
    .entityBuilder()
    .superClass(BaseEntity.class)
    .disableSerialVersionUID()
    .enableChainModel()
    .enableLombok()
    .enableRemoveIsPrefix()
    .enableTableFieldAnnotation()
    .enableActiveRecord()
    .versionColumnName("version")
    .logicDeleteColumnName("deleted")
    .naming(NamingStrategy.no_change)
    .columnNaming(NamingStrategy.underline_to_camel)
    .addSuperEntityColumns("id", "created_by", "created_time", "updated_by", "updated_time")
    .addIgnoreColumns("age")
    .addTableFills(new Column("create_time", FieldFill.INSERT))
    .addTableFills(new Property("updateTime", FieldFill.INSERT_UPDATE))
    .idType(IdType.AUTO)
    .formatFileName("%sEntity")
    .build();
(8)、Controller 策略配置

Controller 策略配置用于定制 Controller 类的生成规则,包括父类、文件覆盖、驼峰转连字符、RestController 注解等。

方法说明
方法 说明 示例
superClass(Class<?>) 设置父类 BaseController.class
superClass(String) 设置父类 com.baomidou.global.BaseController
enableFileOverride 覆盖已生成文件 默认值: false
enableHyphenStyle 开启驼峰转连字符 默认值: false
enableRestStyle 开启生成@RestController 控制器 默认值: false
convertFileName(ConverterFileName) 转换文件名称
formatFileName(String) 格式化文件名称
示例配置
java 复制代码
StrategyConfig strategyConfig = new StrategyConfig.Builder()
    .controllerBuilder()
    .superClass(BaseController.class)
    .enableHyphenStyle()
    .enableRestStyle()
    .formatFileName("%sAction")
    .build();
(9)、Service 策略配置

Service 策略配置用于定制 Service 接口和实现类的生成规则,包括父类、文件覆盖、文件名称转换等。

方法说明
方法 说明 示例
superServiceClass(Class<?>) 接口父类 BaseService.class
superServiceClass(String) 设置 Service 接口父类 com.baomidou.global.BaseService
superServiceImplClass(Class<?>) 设置 Service 实现类父类 BaseServiceImpl.class
superServiceImplClass(String) 设置 Service 实现类父类 com.baomidou.global.BaseServiceImpl
enableFileOverride 覆盖已生成文件 默认值: false
convertServiceFileName(ConverterFileName) 转换 Service 接口文件名称
convertServiceImplFileName(ConverterFileName) 转换 Service 实现类文件名称
formatServiceFileName(String) 格式化 Service 接口文件名称
formatServiceImplFileName(String) 格式化 Service 实现类文件名称
示例配置
java 复制代码
StrategyConfig strategyConfig = new StrategyConfig.Builder()
    .serviceBuilder()
    .superServiceClass(BaseService.class)
    .superServiceImplClass(BaseServiceImpl.class)
    .formatServiceFileName("%sService")
    .formatServiceImplFileName("%sServiceImp")
    .build();
(10)、Mapper 策略配置

Mapper 策略配置用于定制 Mapper 接口和对应的 XML 映射文件的生成规则,包括父类、文件覆盖、Mapper 注解、结果映射、列列表、缓存实现类等。

方法说明
方法 说明 示例
superClass(Class<?>) 设置父类 BaseMapper.class
superClass(String) 设置父类 com.baomidou.global.BaseMapper
enableFileOverride 覆盖已生成文件 默认值: false
enableMapperAnnotation 开启 @Mapper 注解 默认值: false
enableBaseResultMap 启用 BaseResultMap 生成 默认值: false
enableBaseColumnList 启用 BaseColumnList 默认值: false
cache(Class<? extends Cache>) 设置缓存实现类 MyMapperCache.class
convertMapperFileName(ConverterFileName) 转换 Mapper 类文件名称
convertXmlFileName(ConverterFileName) 转换 XML 文件名称
formatMapperFileName(String) 格式化 Mapper 文件名称
formatXmlFileName(String) 格式化 XML 实现类文件名称
generateMapperMethodHandler 自定义生成Mapper方法实现 自3.5.10开始
示例配置
java 复制代码
StrategyConfig strategyConfig = new StrategyConfig.Builder()
    .mapperBuilder()
    .superClass(BaseMapper.class)
    .enableMapperAnnotation()
    .enableBaseResultMap()
    .enableBaseColumnList()
    .cache(MyMapperCache.class)
    .formatMapperFileName("%sDao")
    .formatXmlFileName("%sXml")
    .build();
(11)、自定义模板配置

MyBatis-Plus 代码生成器支持自定义模板,如 DTO (Data Transfer Object) 和 VO (Value Object) 等。

java 复制代码
FastAutoGenerator.create(url, username, password)
    .globalConfig(builder -> {
        builder.author("abc") // 设置作者
            .enableSwagger() // 开启 swagger 模式
            .disableOpenDir() // 禁止打开输出目录
            .outputDir(finalProjectPath + "/src/main/java"); // 指定输出目录
    })
    .packageConfig(builder -> {
        builder.parent("com.baomidou.mybatisplus.samples") // 设置父包名
            .moduleName("test") // 设置父包模块名
            .entity("model.entity") // 设置实体类包名
            .pathInfo(Collections.singletonMap(OutputFile.xml, finalProjectPath + "/src/main/resources/mapper")); // 设置 Mapper XML 文件生成路径
    })
     .injectionConfig(injectConfig -> {
                Map<String,Object> customMap = new HashMap<>();
                customMap.put("abc","1234");
                injectConfig.customMap(customMap); //注入自定义属性
                injectConfig.customFile(new CustomFile.Builder()
                    .fileName("entityDTO.java") //文件名称
                    .templatePath("templates/entityDTO.java.ftl") //指定生成模板路径
                    .packageName("dto") //包名,自3.5.10开始,可通过在package里面获取自定义包全路径,低版本下无法获取,示例:package.entityDTO
                    .build());
      })
    .templateEngine(new FreemarkerTemplateEngine())
    .execute(); // 执行生成

在上面的示例中定义了一个名为 entityDTO.java.ftl 的自定义 Freemarker 模板,并将其路径添加到 customFile 映射中

在生成代码时代码生成器将使用这个模板来生成 DTO 类

10、SQL分析插件

在日常开发工作当中避免不了查看当前程序所执行的SQL语句以及了解它的执行时间,方便分析是否出现了慢SQL问题

我们可以使用MybatisPlus提供的SQL分析打印的功能,来获取SQL语句执行的时间。

  • 第一步

    由于该功能依赖于p6spy组件,所以需要在pom.xml中先引入该组件

    复制代码
    <dependency>
        <groupId>p6spy</groupId>
        <artifactId>p6spy</artifactId>
        <version>3.9.1</version>
    </dependency>
  • 第二步

    在application.yml中进行配置

    将驱动和url修改

    yml 复制代码
    spring:
      datasource:
        password: root
        username: root
        driver-class-name: com.p6spy.engine.spy.P6SpyDriver
        url: jdbc:p6spy:mysql://192.168.0.109:3306/mybatisplus?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false&rewriteBatchedStatements=true
  • 第三步

    在resources下创建 spy.properties配置文件

    properties 复制代码
    #3.2.1以上使用modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
    
    # 自定义日志打印
    logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
    
    #日志输出到控制台
    appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
    
    # 使用日志系统记录 sql
    #appender=com.p6spy.engine.spy.appender.Slf4JLogger
    
    # 设置 p6spy driver 代理
    deregisterdrivers=true
    
    # 取消JDBC URL前缀
    useprefix=true
    
    # 配置记录 Log 例外,可去掉的结果集error,info,batch,debug,statement,commit,rollback,result,resultset.
    excludecategories=info,debug,result,commit,resultset
    
    # 日期格式
    dateformat=yyyy-MM-dd HH:mm:ss
    
    # 实际驱动可多个
    #driverlist=org.h2.Driver
    
    # 是否开启慢SQL记录
    outagedetection=true
    
    # 慢SQL记录标准 2 秒
    outagedetectioninterval=2
  • 第四步

    测试执行查询所有的操作,可以看到sql语句的执行时间

    java 复制代码
        @Test
        void selectP6spy() {
            //1.创建分页查询对象,指定当前页和每页显示条数
            IPage<User> page = new Page<>(1, 2);
            //2.执行分页查询
            userMapper.selectByName(page, "Mary");
        }

11、多数据源插件

当一个项目的数据库的数据十分庞大时,在完成SQL操作的时候,需要检索的数据就会更多,我们会遇到性能问题,会出现SQL执行效率低的问题。

针对这个问题,我们的解决方案是,将一个数据库中的数据,拆分到多个数据库中,从而减少单个数据库的数据量,从分摊访问请求的压力和减少单个数据库数据量这两个方面,都提升了效率。

我们来演示一下,在MybatisPlus中,如何演示数据源切换的效果

  • 第一步

    先创建一个新的模块MyBatisPlus010

  • 第二步

    引入依赖

    java 复制代码
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
        <version>4.3.1</version>
    </dependency>
  • 第三步

    创建新的数据库,提供多数据源环境

    • 数据库1

      sql 复制代码
      create database mybatisplus1;
      use mybatisplus1;
      
      DROP TABLE IF EXISTS `user`;
      
      CREATE TABLE `user`
      (
          id BIGINT NOT NULL COMMENT '主键ID',
          name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
          age INT NULL DEFAULT NULL COMMENT '年龄',
          email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
          PRIMARY KEY (id)
      );
      
      DELETE FROM `user`;
      
      INSERT INTO `user` (id, name, age, email) VALUES
      (1, 'Jone', 18, '[email protected]'),
      (2, 'Jack', 20, '[email protected]'),
      (3, 'Tom', 28, '[email protected]'),
      (4, 'Sandy', 21, '[email protected]'),
      (5, 'Billie', 24, '[email protected]');
    • 数据库2

      sql 复制代码
      create database mybatisplus2;
      use mybatisplus2;
      
      
      DROP TABLE IF EXISTS `user`;
      
      CREATE TABLE `user`
      (
          id BIGINT NOT NULL COMMENT '主键ID',
          name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
          age INT NULL DEFAULT NULL COMMENT '年龄',
          email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
          PRIMARY KEY (id)
      );
      
      DELETE FROM `user`;
      
      INSERT INTO `user` (id, name, age, email) VALUES
      (1, 'Jone2', 18, '[email protected]'),
      (2, 'Jack2', 20, '[email protected]'),
      (3, 'Tom2', 28, '[email protected]'),
      (4, 'Sandy2', 21, '[email protected]'),
      (5, 'Billie2', 24, '[email protected]');
  • 第四步

    编写配置文件,指定多数据源信息

    yml 复制代码
    spring:
      datasource:
        dynamic:
          primary: master
          strict: false
          datasource:
            master:
              username: root
              password: root
              url: jdbc:mysql://192.168.0.109:3306/mybatisplus1?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
              driver-class-name: com.mysql.cj.jdbc.Driver
            slave_1:
              username: root
              password: root
              url: jdbc:mysql://192.168.0.109:3306/mybatisplus2?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
              driver-class-name: com.mysql.cj.jdbc.Driver
  • 第五步

    创建多个Service,分别使用@DS注解描述不同的数据源信息

    java 复制代码
    package com.longdidi.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.longdidi.domain.User;
    
    public interface UserService extends IService<User> {
    }
    
    -----------------------------------
        
    package com.longdidi.service;
    
    import com.baomidou.mybatisplus.extension.service.IService;
    import com.longdidi.domain.User;
    
    public interface UserService2 extends IService<User> {
    }
    
    -----------------------------------
        
        
    package com.longdidi.service.impl;
    
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.longdidi.domain.User;
    import com.longdidi.mapper.UserMapper;
    import com.longdidi.service.UserService;
    import org.springframework.stereotype.Service;
    
    @Service
    @DS("master")
    public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
    }
    
    
    -----------------------------------
        
    package com.longdidi.service.impl;
    
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.longdidi.domain.User;
    import com.longdidi.mapper.UserMapper;
    import com.longdidi.service.UserService;
    import com.longdidi.service.UserService2;
    import org.springframework.stereotype.Service;
    
    @Service
    @DS("slave_1")
    public class UserServiceImpl2 extends ServiceImpl<UserMapper, User> implements UserService2 {
    }
  • 第六步

    测试service多数据源环境执行结果

    java 复制代码
    package com.longdidi;
    
    import com.longdidi.domain.User;
    import com.longdidi.service.impl.UserServiceImpl;
    import com.longdidi.service.impl.UserServiceImpl2;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    @SpringBootTest
    public class UserTest {
        @Autowired
        private UserServiceImpl userServiceImpl;
    
        @Autowired
        private UserServiceImpl2 userServiceImpl2;
    
        @Test
        public void select(){
            User user = userServiceImpl.getById(1L);
            System.out.println(user);
        }
    
        @Test
        public void select2(){
            User user = userServiceImpl2.getById(1L);
            System.out.println(user);
        }
    
    }

    测试select()方法

    测试select2()方法

相关推荐
Java致死14 天前
MybatisPlus
java·mybatis·mybatisplus
鸽鸽程序猿24 天前
【JavaEE】MyBatis - Plus
java-ee·mybatisplus
SummerGao.1 个月前
【实操】Mybatis-plus2.x升级到3.x
java·spring boot·mybatisplus·系统升级
鲨鱼辣椒_TUT2 个月前
MyBatisPlus学习
mybatisplus
Strawberry_ahh2 个月前
MybatisPlus常用增删改查
java·mybatisplus
灰色孤星A3 个月前
MyBatisPlus详解(三)lambdaQuery、lambdaUpdate、批量新增、代码生成、Db静态工具、逻辑删除
java·mybatis·mybatisplus·代码生成器·逻辑删除·lambdaquery
@ 李青龙4 个月前
MybatisPlus--mybatis升级版
mybatis·mybatisplus
Charlie__ZS4 个月前
MybatisPlus-配置加密
mybatis·mybatisplus·aes加密
it界的哈士奇4 个月前
麒麟系统+达梦数据库+MybatisPlus+Redis+SpringBoot
数据库·spring boot·redis·达梦·mybatisplus