官网:https://baomidou.com/

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

# 1 快速入门

# 1.1 新建 springboot 项目

# 1.2 导入 jar

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

完整的 pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.qfedu</groupId>
    <artifactId>springboot03</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot03</name>
    <description>springboot03</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

# 1.3 yml 配置

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3308/plus?serverTimezone=Asia/Shanghai
    username: root
    password: root
# mybatis plus 的配置和 mybatis 类似,之前在 mybatis 的配置,使用 mybtis-plus 替换
mybatis-plus:
  configuration:
    # 控制台打印日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

# 1.4 代码

# 1)实体类

package com.qfedu.springboot03.entity;
import lombok.Data;
@Data
public class Employee {
    private Integer id;
    private String name;
    private Integer age;
    private String sex;
    private String phone;
}

# 2)Dao 层接口

自定义接口需要实现 BaseMapper 接口

package com.qfedu.springboot03.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.qfedu.springboot03.entity.Employee;
public interface EmployeeDao extends BaseMapper<Employee> {
}

注意:可以进入 BaseMapper 接口,查看方法

# 3)Service 层接口和实现类

接口继承 IService 接口

package com.qfedu.springboot03.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.qfedu.springboot03.entity.Employee;
public interface EmployeeService extends IService<Employee> {
}

实现类继承 ServiceImpl,实现相关接口

package com.qfedu.springboot03.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qfedu.springboot03.dao.EmployeeDao;
import com.qfedu.springboot03.entity.Employee;
import com.qfedu.springboot03.service.EmployeeService;
import org.springframework.stereotype.Service;
@Service
public class EmployeeServiceImpl extends ServiceImpl<EmployeeDao, Employee> implements EmployeeService {
}

注意:可以进入 IService 接口,查看方法

# 4)启动类

扫描 dao 层接口

package com.qfedu.springboot03;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.qfedu.springboot03.dao")
public class Springboot03Application {
    public static void main(String[] args) {
        SpringApplication.run(Springboot03Application.class, args);
    }
}

# 5)测试类

package com.qfedu.springboot03;
import com.qfedu.springboot03.dao.EmployeeDao;
import com.qfedu.springboot03.entity.Employee;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import java.util.List;
@SpringBootTest
class Springboot03ApplicationTests {
    @Resource
    private EmployeeDao employeeDao;
    @Test
    void contextLoads() {
    }
    @Test
    public void testSelectById() {
        System.out.println(("----- selectById method test ------"));
        Employee employee = employeeDao.selectById(1);
        System.out.println(employee);
    }
    @Test
    public void testSelectList() {
        System.out.println(("----- selectAll method test ------"));
        List<Employee> employees = employeeDao.selectList(null);
        System.out.println(employees.size());
    }
}

# 1.5 实体类中使用的注解

注意:当表名、字段名和实体类的类名、属性名不一致时,必须使用这些注解

@TableName修饰实体类,指定的表实体类对象的表名
@TableId修饰属性,指定主键对应的属性 value 主键字段名,可以不写 type 主键类型,IdType.AUTO 自增 IdType.ASSIGN_ID 雪花算法生成的值 IdType.ASSIGN_UUID uuid
@TableField修饰属性,针对非主键的属性
package com.qfedu.springboot03.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
// 指定表名后,就可以确定 sql 语句需要操作的表
// 当实体类与表名不一致时必须指定
@TableName("t_dept")
public class Department {
    // 指定该属性为主键,当属性名和主键字段名不一致时,必须指定
    // 主键为自增类型(与数据库保持一致)
    @TableId(value = "did", type = IdType.AUTO)
    // 雪花算法生成的 id
    // @TableId(type = IdType.ASSIGN_ID)
    // @TableId(type = IdType.ASSIGN_UUID)
    private Integer did;
    private String dname;
}
@Test
public void testSelectDepartment() {
    List<Department> departments = departmentDao.selectList(null);
    System.out.println(departments.size());
	// 如果表中的主键的字段名不是 id, 需要设置 @TableId (value = "did", type = IdType.AUTO)
    Department department = departmentDao.selectById(10);
    System.out.println(department);
}
@Test
public void testInserDept() {
    Department department = new Department();
    department.setDname("hahahah");
    int insert = departmentDao.insert(department);
    // 插入后,会自动返回自增的 id
    System.out.println(department.getDid());
    System.out.println(insert);
}

# 2 条件构造器 Wrapper

# 2.1 类关系

Wrapper  条件构造抽象类
	-- AbstractWrapper 查询条件封装,用于生成 sql 中的 where 语句。
		-- QueryWrapper Entity对象封装操作类,可用于查询字段。
		-- UpdateWrapper Update条件封装操作类,用于更新。
		-- AbstractLambdaWrapper 使用 Lambda 表达式封装 wrapper
			-- LambdaQueryWrapper 使用 Lambda 语法封装条件,用于查询。
			-- LambdaUpdateWrapper 使用 Lambda 语法封装条件,用于更新。

注意:

条件构造器用于构建 sql 语句的 where 条件,增删改查都可以使用;

EntityWrapper,在 3.0 版本以上就不再使用,改为了 QueryWrapper。

# 2.2 主要方法

eq(R column, Object val)=eq ("name", "老王") ---> name = ' 老王'
ne(R column, Object val);<>ne ("name", "老王") ---> name <> ' 老王'
gt(R column, Object val);>gt ("name", "老王") ---> name > ' 老王'
ge(R column, Object val);>=ge ("name", "老王") ---> name >= ' 老王'
lt(R column, Object val);<lt ("name", "老王") ---> name < ' 老王'
le(R column, Object val);<=le ("name", "老王") ---> name <= ' 老王'
between(R column, Object val1, Object val2)between a and bbetween("age", 18, 30) ---> age between 18 and 30
notBetween(R column, Object val1, Object val2);not between a and bnotBetween("age", 18, 30) ---> age not between 18 and 30
in(R column, Object... values);IN (v0, v1, ...)in("age",{1,2,3}) ---> age in (1,2,3)
notIn(R column, Object... values);NOT IN (v0, v1, ...)notIn("age",{1,2,3}) ---> age not in (1,2,3)
inSql(R column, Object... values);IN (sql 语句)inSql("id", "select id from table where id < 3") ---> id in (select id from table where id < 3)
notInSql(R column, Object... values);NOT IN (sql 语句)
like(R column, Object val);LIKE '% 值 %'like ("name", "王") ---> name like '% 王 %'
notLike(R column, Object val)NOT LIKE '% 值 %'notLike ("name", "王") ---> name not like '% 王 %'
likeLeft(R column, Object val)LIKE '% 值'likeLeft ("name", "王") ---> name like '% 王'
likeRight(R column, Object val)LIKE ' 值 %'likeRight ("name", "王") ---> name like ' 王 %'
isNull(R column)IS NULLisNull("name") ---> name is null
isNotNull(R column);IS NOT NULLisNotNull("name") ---> name is not null
groupBy(R... columns)GROUP BYgroupBy("id", "name") ---> group by id,name
having(String sqlHaving, Object... params)HAVING (sql 语句)having("sum(age) > {0}", 11) ---> having sum(age) > 11
orderByAsc(R... columns)ORDER BY 字段,... ASCorderByAsc("id", "name") ---> order by id ASC,name ASC
orderByDesc(R... columns);ORDER BY 字段,... DESCorderByDesc("id", "name") ---> order by id DESC,name DESC
or();a or beq ("id",1).or ().eq ("name","老王") ---> id = 1 or name = ' 老王'
or(Consumer<Param> consumer)or 嵌套or (i -> i.eq ("name", "李白").ne ("status", "活着")) ---> or (name = ' 李白 ' and status <> ' 活着 ')
and(Consumer<Param> consumer)and 嵌套and (i -> i.eq ("name", "李白").ne ("status", "活着")) ---> and (name = ' 李白 ' and status <> ' 活着 ')
nested(Consumer<Param> consumer);普通嵌套nested (i -> i.eq ("name", "李白").ne ("status", "活着")) ---> (name = ' 李白 ' and status <> ' 活着 ')
apply(String applySql, Object... params);拼接 sqlapply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08") ---> date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
last(String lastSql)无视优化规则直接拼接到 sql 的最后
exists(String existsSql)拼接 exists 语句exists("select id from table where age = 1") ---> exists (select id from table where age = 1)
QueryWrapper
select(String... sqlSelect)用于指定查询需要返回的字段select("id", "name", "age") ---> select id, name, age
select(Predicate<TableFieldInfo> predicate);通过 Lambda 表达式,过滤需要的字段
lambda();返回一个 LambdaQueryWrapper 对象
UpdateWrapper
set(String column, Object val);set 字段值set("name", null) ---> set name = null
setSql(String sql);setSql ("name = ' 老李头 '") ---> set name = ' 老李头'
lambda();返回一个 LambdaUpdateWrapper 对象

# 2.3 测试代码

@Test
public void testQueryWrapper() {
    QueryWrapper<Employee> wrapper = new QueryWrapper<>();
    wrapper.select("id", "name");
    wrapper.isNotNull("name");
    // 转换的 sql:SELECT id,name FROM employee WHERE (name IS NOT NULL)
    List<Employee> employees = employeeDao.selectList(wrapper);
    System.out.println(employees.size());
}
@Test
public void testQueryWrapper2() {
    QueryWrapper<Employee> wrapper = new QueryWrapper<>();
    wrapper.isNotNull("name");
    // SELECT id,name,age,sex,phone FROM employee WHERE (name IS NOT NULL)
    List<Employee> employees = employeeDao.selectList(wrapper);
    System.out.println(employees.size());
}
// 删除中使用 Wrapper
@Test
public void testQueryWrapper3() {
    QueryWrapper<Employee> wrapper = new QueryWrapper<>();
    wrapper.eq("age", 30);
    wrapper.like("name", "haha");
    // DELETE FROM employee WHERE (age = ? AND name LIKE ?)
    employeeDao.delete(wrapper);
}
@Test
public void testUpdateWrapper() {
    UpdateWrapper<Employee> wrapper = new UpdateWrapper<>();
    wrapper.eq("age", 20);
    Employee employee = new Employee();
    employee.setSex("男");
    // UPDATE employee SET sex=? WHERE (age = ?)
    employeeDao.update(employee, wrapper);
}
@Test
public void testUpdateWrapper2() {
    UpdateWrapper<Employee> wrapper = new UpdateWrapper<>();
    wrapper.set("sex", "女");
    wrapper.eq("age", 20);
    // UPDATE employee SET sex=? WHERE (age = ?)
    employeeDao.update(null, wrapper);
}
@Test
public void testUpdateWrapper3() {
    LambdaUpdateWrapper<Employee> wrapper = new LambdaUpdateWrapper<>();
    // 可以使用 lambda 表达式指定字段
    wrapper.set(Employee::getSex, "男");
    wrapper.eq(Employee::getAge, 20);
    // UPDATE employee SET sex=? WHERE (age = ?)
    employeeDao.update(null, wrapper);
}

# 3 插件

# 3.1 分页插件

# 配置类

package com.qfedu.springboot03.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 {
    // 从 MyBatis-Plus 3.4.0 开始,不再使用旧版本的 PaginationInterceptor ,而是使用 MybatisPlusInterceptor
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 向 MyBatis-Plus 的过滤器链中添加分页拦截器,需要设置数据库类型(主要用于分页方言)
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

# 测试

@Test
public void testSelectPage() {
    // 设置分页对象,第一个参数:页码,第二个参数:每页的记录数
    Page<Employee> page = new Page<>(2, 10);
    // page.setCurrent(1);
    // page.setSize(10);
    Page<Employee> page1 = employeeDao.selectPage(page, null);
    System.out.println(page1.getTotal());
    System.out.println(page1.getRecords().get(0).getName());
}

# 3.2 乐观锁插件

# 数据库表

增加表示版本号的字段

img

# 实体类

使用 @Version 修饰表示版本号的属性

package com.qfedu.springboot03.entity;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
@Data
public class Employee {
    private Integer id;
    private String name;
    private Integer age;
    private String sex;
    private String phone;
    @Version
    private Integer version;
}

# 配置

@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // 向 MyBatis-Plus 的过滤器链中添加分页拦截器,需要设置数据库类型(主要用于分页方言)
    interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
    // 乐观锁插件
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

# 测试

@Test
    public void testVersion() {
        Employee emp = employeeDao.selectById(1);
        int version = emp.getVersion();
        System.out.println(version);
        emp.setName("haha");
        employeeDao.updateById(emp);
    }
-- 注意看verson的值
==>  Preparing: UPDATE employee SET name=?, age=?, sex=?, phone=?, version=? WHERE id=? AND version=?
==> Parameters: haha(String), 12(Integer),(String), 13213(String), 2(Integer), 1(Integer), 1(Integer)
<==    Updates: 1

# 附录

# mysql 悲观锁与乐观锁

# 悲观锁

对于数据的处理持悲观态度,总认为获取和修改数据时,别人会修改数据。在整个数据处理过程中,需要将数据加锁。悲观锁的实现,通常依靠数据库提供的排他锁机制实现,比如 select .... for update

img

一个客户端启动事务,执行 for update,此时还没有进行其他操作,事务没有结束

另一个客户端针对相同记录进行更新,无法执行,超时报错

# 乐观锁

对数据的处理持乐观态度,认为数据一般情况下不会发生冲突,只有数据更新时,才会对数据是否冲突进行检测。如果发现冲突,则返回错误信息给用户,让用户决定如何操作。乐观锁的实现不依靠数据库提供的锁机制,一般是通过版本号,或者时间戳实现乐观锁机制。

img