官网: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 b | between("age", 18, 30) ---> age between 18 and 30 |
notBetween(R column, Object val1, Object val2); | not between a and b | notBetween("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 NULL | isNull("name") ---> name is null |
isNotNull(R column); | IS NOT NULL | isNotNull("name") ---> name is not null |
groupBy(R... columns) | GROUP BY | groupBy("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 字段,... ASC | orderByAsc("id", "name") ---> order by id ASC,name ASC |
orderByDesc(R... columns); | ORDER BY 字段,... DESC | orderByDesc("id", "name") ---> order by id DESC,name DESC |
or(); | a or b | eq ("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); | 拼接 sql | apply("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 乐观锁插件
# 数据库表
增加表示版本号的字段
# 实体类
使用 @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
一个客户端启动事务,执行 for update,此时还没有进行其他操作,事务没有结束
另一个客户端针对相同记录进行更新,无法执行,超时报错
# 乐观锁
对数据的处理持乐观态度,认为数据一般情况下不会发生冲突,只有数据更新时,才会对数据是否冲突进行检测。如果发现冲突,则返回错误信息给用户,让用户决定如何操作。乐观锁的实现不依靠数据库提供的锁机制,一般是通过版本号,或者时间戳实现乐观锁机制。