一、MyBatisPlus简介
MyBatis-Plus官网MyBatis-Plus 🚀 为简化开发而生
MyBatis是一个基于Java的持久层框架,它支持定制化SQL、存储过程以及高级映射。以下是对MyBatis的详细介绍:
-
基本概述
- 简介:MyBatis 是一个优秀的持久层框架,它提供了一种将数据库操作与Java对象之间的映射关系定义在XML文件或注解中的方式,以简化数据库访问和操作。
- 历史背景:MyBatis最初称为iBATIS,于2002年首次发布,后在2010年迁移到Apache软件基金会并改名为MyBatis。自那以后,经过不断的改进和发展,已经成为了Java持久层框架中的佼佼者。
-
主要特点
- 简单易学:MyBatis本身很小且简单,没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。
- 灵活性高:MyBatis不会对应用程序或者数据库的现有设计强加任何影响。SQL写在XML里,便于统一管理和优化。通过SQL语句可以满足操作数据库的所有需求。
- 解除耦合:提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。
- 对象关系映射:支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组建维护。
- 动态SQL:MyBatis提供了动态SQL的功能,可以根据不同的条件生成不同的SQL语句,从而避免了手动编写大量重复的SQL代码。
-
架构组成
- API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
- 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
- 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
二、MyBatisPlus入门体验
MyBatis无疑已经为Java开发者提供了简化数据库的操作,单表的CRUD(增删改查)功能代码重复度很高,也没有什么难度。而这部分代码量往往比较大,开发起来比较费时。
因此,目前企业中都会使用一些组件来简化或省略单表的CRUD开发工作。目前在国内使用较多的一个组件就是MybatisPlus,下面由一套案例来体验一下MyBatisPlus。
2.1环境准备
2.1.1构建一张表用于操作数据库
-- 导出 表 mp.user 结构
CREATE TABLE `user` (
`id` BIGINT(19) NOT NULL AUTO_INCREMENT COMMENT '用户id',
`username` VARCHAR(50) NOT NULL COMMENT '用户名' COLLATE 'utf8_general_ci',
`password` VARCHAR(128) NOT NULL COMMENT '密码' COLLATE 'utf8_general_ci',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `username` (`username`) USING BTREE
)
COMMENT='用户表'
COLLATE='utf8_general_ci'
ENGINE=InnoDB
ROW_FORMAT=COMPACT
AUTO_INCREMENT=5
;
-- 正在导出表 mp.user 的数据:~4 rows (大约)
INSERT INTO `user` (`id`, `username`, `password`, `create_time`, `update_time`) VALUES
(1, 'Jack', '123', '2023-05-19 20:50:21', '2023-06-19 20:50:21'),
(2, 'Rose', '123', '2023-05-19 21:00:23', '2023-06-19 21:00:23'),
(3, 'Hope', '123', '2023-06-19 22:37:44', '2023-06-19 22:37:44'),
(4, 'Thomas', '123', '2023-06-19 23:44:45', '2023-06-19 23:44:45');
2.1.2环境依赖导入
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.dfbz</groupId>
<artifactId>01_MyBatisPlus</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/>
</parent>
<dependencies>
<!--springboot依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--测试场景-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--mybatis plus场景-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!--测试单元-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
mybatis-plus-boot-starter 该依赖包含了mybatis依赖,mybatisplus的出现并不是意味着代替mybatis,而是两者相辅相成。
2.2.3application.yml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mp?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root # 数据库用户名
password: MySQL123 # 数据库密码
logging:
level:
com.itheima: debug
pattern:
dateformat: HH:mm:ss
2.2.4构建实体类对象User
package com.nuc.domain.po;
import cn.hutool.system.UserInfo;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @Description:
* @Author:Jerry
* @Date Created in 2024-11-26 14:39
*/
@Data
public class User {
private Long id;
private String username;
private String password;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
2.2.5Mapper接口
在mapper包下构建UserMapper.class接口,并继承BaseMapper,指定泛型<User>
package com.itheima.mp.mapper;
public interface UserMapper extends BaseMapper<User> {
}
2.2.5启动类MpDemoApplication
package com.nuc;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.nuc.mapper")
@SpringBootApplication
public class MpDemoApplication{
public static void main(String[] args) {
SpringApplication.run(MpDemoReviewsApplication.class, args);
}
}
2.2测试
在测试包下构建UserMapperTest测试类
package com.itheima.mp.mapper;
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void testSelectById() {
User user = userMapper.selectById(5L);
System.out.println("user = " + user);
}
}
运行该测试类后,控制台便会打印出id为5L的所有用户信息,此时我们可以看出,并没有书写一条sql语句,但功能还是实现了,这就是MyBatisPlus的强大之处,它极大地减少sql语句的编写。
三、MyBatisPlus原理
3.1原理
MyBatisPlus原理其实很简单,它内部封装了一系列简单的CRUD方法供开发者调用,对于一些简单的数据库表的操作,开发者不必编写sql语句,直接调用方法即可,例如上述的UserMapper中继承了BaseMapper,BaseMapper中提供了很多方法,如下图所示(部分):
例如insert(T entity)作用为向数据库表中插入一条数据
deleteById(Seriablizable id)作用为根据id删除数据等等...
3.2常用注解
在刚刚的入门案例中,我们仅仅引入了依赖,继承了BaseMapper就能使用MybatisPlus,非常简单。但是问题来了: MybatisPlus如何知道我们要查询的是哪张表?表中有哪些字段呢?
其实我们在继承BaseMapper时,还指定了泛型
MybatisPlus就是根据PO实体的信息来推断出表的信息,从而生成SQL的。默认情况下:
-
MybatisPlus会把PO实体的类名驼峰转下划线作为表名
-
MybatisPlus会把PO实体的所有变量名驼峰转下划线作为表的字段名,并根据变量类型推断字段类型
-
MybatisPlus会把名为id的字段作为主键
但很多情况下,默认的实现与实际场景不符,因此MybatisPlus提供了一些注解便于我们声明表信息。
3.2.1@TableName
表明注解,用于指定实体类对用的数据库表名,示例:
@Data
@TableName("user")
public class User {
private Long id;
private String username;
private String password;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
3.2.2@TableId
用于表示实体类中对应数据库表的字段名称,示例:
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
@TableId有两个属性
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value | String | 否 | “” | 表名 |
type | Enum | 否 | IdType.NONE | 指定主键类型 |
上述案例使用的type = IdType.AUTO表示主键自增。
3.3.3@TableField
该注解使用机制:
-
成员变量名与数据库字段名不一致
-
成员变量是以
is
命名,按照JavaBean
的规范,MybatisPlus
识别字段时会把is
去除,这就导致与数据库不符。 -
成员变量名与数据库一致,但是与数据库的关键字冲突。使用
@TableField
注解给字段名添加转义字符:``
示例:
@TableName("user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
@TableField("isMarried")
private Boolean isMarried;
@TableField("concat")
private String concat;
}
补充:该注解中,属性fill用于自动填充字段,使用示例如下:
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String password;
@TableField(fill = FieldFill.INSERT) // 执行插入操作时,自动填充该字段
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 执行更新或插入操作时,自动填充该字段
private LocalDateTime updateTime;
}
3.3自定义sql
MyBatisPlus的出现,并不意味着要代替MyBatis,而是两者相辅相成,在一些复杂业务逻辑下,MyBatisPlus提供的API不足以满足开发需求,此时便需要开发者字段定义sql语句
(1)配置mapper读取地址
在application.yml中配置以下内容
mybatis:
mapper-locations: classpath*:mapper/*.xml # 扫描mapper.xml文件
(2)在mapper中扩写方法
package com.itheima.mp.mapper;
public interface UserMapper extends BaseMapper<User> {
public List<User> selectAllUser();
}
(3)编写UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mp.mapper.UserMapper">
<! -- id为扩展方法名,resultType为返回的单个数据类型 -->
<select id="queryUserById" resultType="com.nuc.mp.domain.po.User">
SELECT * FROM user
</select>
</mapper>
四、核心功能
4.1条件构造器Wrapper
在数据库表的增删改查操作中,查询操作无疑是最为复杂的,他设计的条件会随着业务的复杂性而增多,使得开发者必须编写复杂的where条件,MyBatisPlus为提供了可以构造条件的方法,避免了where的编写。
无论是修改、删除、查询,都可以使用QueryWrapper来构建查询条件,BaseMapper中有很多方法都以Wrapper类作为参数,如下图所示:
参数中的Wrapper
就是条件构造的抽象类,其下有很多默认实现,继承关系如图:
AbstractWrapper继承自Wrapper抽象类,其中提供了很多方法用于条件构造:
方法名 | 作用 | 示例 |
---|---|---|
eq | 等于= | eq("name", "Jerry") --> where name = 'Jerry' |
ne | 不等于<> | ne("name", "Jerry") --> where name <> 'Jerry' |
gt | 大于> | gt("age",18) --> age > 18 |
ge | 大于等于>= | ge("age",18) --> age >= 18 |
lt | 小于< | lt("age",18) --> age < 18 |
le | 小于等于<= | le("age",18) --> age <= 18 |
between | between 值1 and 值2 | between("age", 18, 20) --> age between 18 and 20 |
notBetween | not between 值1 and 值2 | not between("age", 18, 20) --> age between 18 and 20 |
like | like '%值%' | like("name","郭") --> name like '%郭%' |
noLike | not like '%值%' | notLike("name","郭") --> name not like '%郭' |
likeLeft | like '%值' | likeLeft("name","郭") --> name like '%郭' |
likeRight | like '值%' | likeRight("name","郭") --> name like '郭%' |
isNull | 字段is null | isNull("name") --> name is null |
isNotNull | 字段is not null | isNotNull("name") --> name is not null |
in | 字段 in (值1,值2...) | in("age", 1, 2) --> name in (1, 2, 3) |
notIn | 字段not in (值1,值2...) | notIn("age", 1, 2) --> name not in (1, 2, 3) |
groupBy | 用于分组 | groupBy("id") --> group by id |
orderByAsc | order by 字段,.. asc | orderByAsc("age") --> order by age (asc) |
orderByDesc | order by 字段,.. desc | orderByDesc("age") --> order by age desc |
orderBy | order by 字段,.. | orderBy("age") --> order by age |
那么如何利用Wrapper构造复杂条件查询呢
使用QueryWrapper,其在AbsractWrapper的基础上扩写了无论是修改、删除、查询,都可以使用QueryWrapper来构建查询条件,示例代码:
/**
* @Description:
* @Author:Jerry
* @Date Created in 2024-11-24 9:31
*/
@Test
void testQueryWrapper1() {
// 构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.select("id", "username", "age")
wrapper.like("username", "J")
wrapper.ge("age", 12);
/* 对应sql语句为 select id, username, age from user where username like '%J%' and age = 12;*/
// 执行查询操作
List<User> users = userMapper.selectList(wrapper);
}
利用QueryWrapper实现子查询操作。示例代码
/**
* @Description:子查询
* @Author:Jerry
* @Date Created in 2024-11-24 9:31
*/
@Test
void testQueryWrapper2() {
// 构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.inSql("name", "select name from user where age > 21")
/* 对应sql语句为 select name in (select name from user where age > 21) from user*/
// 执行查询操作
List<User> users = userMapper.selectList(wrapper);
}
利用QueryWrapper实现排序操作。示例代码
/**
* @Description:排序查询
* @Author:Jerry
* @Date Created in 2024-11-24 9:31
*/
@Test
void testQueryWrapper3() {
// 构建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<User>();
wrapper.orderBySesc("age");
/* 对应sql语句为 select * from user orderby age desc*/
// wrapper.orderBySesc("age");
/* 对应sql语句为 select * from user orderby age asc*/
// 执行查询操作
List<User> users = userMapper.selectList(wrapper);
}
4.2UpdateWrapper
UpdateWrapper为AbstractWrapper的子类,其中也具备查询方法,但是在之前的查询的方法中扩写了更新操作,示例1:
/**
* @Description:setSql方法
* @Author:Jerry
* @Date Created in 2024-11-26 9:31
*/
@Test
void testUpdateWrapper4() {
// 构建查询条件
UpdateWrapper<User> wrapper = new UpdateWrapper<User>();
wrapper.eq("id", 1);
wrapper.setSql("age = age - 10");
/* 对应sql语句为 update user set age = age - 10 where id = 1*/
// wrapper.orderBySesc("age");
/* 对应sql语句为 select * from user orderby age asc*/
// 执行更新操作
userMapper.update(null, wrapper);
// 第一个参数为修改的字段,也可以不填,根据指定的sql语句更新
}
示例2:
/**
* @Description:Wrappers.update(user)传递查询的条件,使用wrapper来修改
* @Author:Jerry
* @Date Created in 2024-11-26 9:31
*/
@Test
void testUpdateWrapper5() {
User user = new User();
user.setId(1L);
// 构建查询条件
UpdateWrapper<User> wrapper = new UpdateWrapper<User>();
wrapper.set("age", 10);
wrapper.set("name", "Echo");
/* 对应sql语句为 update user set age = 10 , name = 'Echo' where id = 1*/
// 执行更新操作
userMapper.update(null, wrapper);
// 第一个参数为修改的字段,也可以不填,根据指定的sql语句更新
}
4.3LambdaQueryWrapper
上述的UpdateWrapper与QueryWrapper有一个弊端是在构建条件时需要写死字段名,这样一来代码的可读性和维护性会变差,还会导致字符串魔法值。
而MyBatisPlus提供的基于LambdaWrapper解决了这一问题,他利用基于变量的getter方法结合反射机制,计算出对应的字段名,LambdaWrapper包含两个,LambdaQueryWrapper和LambdaUpdateWrapper
LambdaQueryWrapper使用示例:
/**
* @Description:LambdaQueryWraper
* @Author:Jerry
* @Date Created in 2024-11-26 9:31
*/
@Test
void testLambdaQueryWraper() {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
// 构建查询条件
queryWrapper.name(User::getName != null, User::getName , "Je");
// 执行查询
// 第一个参数含义是当字段不为空时进行查询
// 对应sql select * from user where name like '%Je%';
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
}
4.4LambdaUpdateWrapper
@Test
public void testLambdaUpdateWrapper() throws Exception {
LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(User::getId,"1");
wrapper.set(User::getName,"xiaolan");
wrapper.set(User::getAge,18);
userMapper.update(null,wrapper);
}
五、Service接口
5.1概念
MybatisPlus不仅提供了BaseMapper,还提供了通用的Service接口及默认实现,封装了一些常用的service模板方法。 通用接口为IService
,默认实现为ServiceImpl
,其中封装的方法可以分为以下几类:
-
save
:新增 -
remove
:删除 -
update
:更新 -
get
:查询单个结果 -
list
:查询集合结果 -
count
:计数 -
page
:分页查询
5.2基本用法
由于Service
中经常需要定义与业务有关的自定义方法,因此我们不能直接使用IService
,而是自定义Service
接口,然后继承IService
以拓展方法。同时,让自定义的Service实现类
继承ServiceImpl
,这样就不用自己实现IService
中的接口了。
首先,定义IUserService
,继承IService
:
package com.itheima.mp.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.mp.domain.po.User;
public interface IUserService extends IService<User> {
// 拓展自定义方法
}
然后,编写UserServiceImpl
类,继承ServiceImpl
,实现UserService
:
package com.itheima.mp.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.mp.domain.po.User;
import com.itheima.mp.domain.po.service.IUserService;
import com.itheima.mp.mapper.UserMapper;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements IUserService {
}
六、代码生成
6.1.代码生成
在使用MybatisPlus以后,基础的Mapper
、Service
、PO
代码相对固定,重复编写也比较麻烦。因此MybatisPlus官方提供了代码生成器根据数据库表结构生成PO
、Mapper
、Service
等相关代码。只不过代码生成器同样要编码使用,也很麻烦。
这里推荐大家使用一款MybatisPlus
的插件,它可以基于图形化界面完成MybatisPlus
的代码生成,非常简单。
6.2安装插件
在Idea
的plugins市场中搜索并安装MyBatisPlus
插件:
6.3插件使用
在菜单顶部找到config database
点击后,测试数据库连接
之后点击code generation生成代码