SpringBoot整合Mybatis-Plus
文章目录
前言
MyBatis-Plus
(简称 MP)是一个 MyBatis
的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
正如官方文档中所提到的,Mybatis-Plus拥有很多优秀的特性:
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
Mybatis-Plus 作为更优秀的持久层框架,继续沿用了 ORM
的思想,也支持很多的数据库连接
- mysql 、 mariadb 、 oracle 、 db2 、 h2 、 hsql 、 sqlite 、 postgresql 、 sqlserver 、 presto
- 达梦数据库 、 虚谷数据库 、 人大金仓数据库
官方也给出了免费的第三方视频可供学习:
Mybatis-Plus
的知识并不少,不是三言两语能够讲清楚的。这里选取了 Mybatis-Plus
的部分基础要点与 SpringBoot
进行整合学习,以练促学,达到以点带面的效果。
如果想要更近一步的学习 Mybatis-Plus
的高级应用,还是拿官网文档学习为最佳。
新建项目
使用 IDEA
工具新建 SpringBoot
项目,初始化组件选择 lombok
、Spring Web
、MySQL Driver
。
在 pom.xml
文件中引入 Mybatis-Plus
依赖、阿里巴巴的 druid
连接池
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.19</version>
</dependency>
配置连接
新建 dao 目录,在主程序中配置 Mapper 扫描
@SpringBootApplication
@MapperScan("com.example.dao")
public class SpringbootMybatisplusApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootMybatisplusApplication.class, args);
}
}
使用 Navicat
在 MySQL 中新建一个名为 mybatisplus
的数据库
新建 application.yml
文件,进行基本配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatisplus? useUnicode=true & useSSL=false &
characterEncoding=utf-8 & serverTimezone=Asia/Shanghai
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
# 配置日志
logging:
level:
root: info
com:
example:
dao: debug
这里因为 Mybatis-plus 内置了很多 sql 的增删改查方法,足够我们应付大多数的应用场景,(相当于提供了零 xml 配置的特性),所以我们不需要再通过 mapper-locations
进行 xml 文件的指定,如果有特殊需要再去做。同时,我们也不用通过 type-aliases-package:
对实体类器别名,mybatis-plus 会自动为实体类起别名。
我们去 mybatisplus
数据库中新建一个 user
表
DROP TABLE IF EXISTS user;
CREATE TABLE user(
id VARCHAR(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(25) DEFAULT NULL,
age INT(11) DEFAULT NULL,
bir TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY(id)
)ENGINE=INNODB DEFAULT charset=utf8;
随便插入几条数据
快速入门
新建 entity 目录,目录下新建 User 类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User {
private String id;
private String name;
private Integer age;
private Date bir;
}
@Accessors
是 lombok 的一个注解,翻译为存取器,通过该注解可以控制getter和setter方法的形式。chain 为 true,则setter方法返回当前对象。
//没加Accessors(chain = true)
public void setName(String name) {
this.name = name;
}
//加了Accessors(chain = true)
public User setName(String name) {
this.name = name;
return this;
}
在 dao 目录下新建 UserDao 接口。
我们要想在 Dao 层接口中使用 mybatis-plus
的增强,只需要继承 BaseMapper<实体类>
,就可以调用 mybatis-plus 封装好的增删改查方法。
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.entity.User;
import org.springframework.stereotype.Repository;
//使用mybatisplus增强结果
public interface UserDao extends BaseMapper<User> {
}
我们通过测试类进行测试
如果 userDao 标红,提示说不能自动装配也没有关系,测试类也是可以运行起来的。
@SpringBootTest
class SpringbootMybatisplusApplicationTests {
@Autowired
private UserDao userDao;
//查询所有
@Test
public void testFindAll(){
List<User> users = userDao.selectList(null);//参数为空默认查询所有数据
// lambda表达式
users.forEach(user -> System.out.println("user = "+ user));
}
}
可以看到,我们已经轻松查询到了 user 表中的数据
常用注解
我们在使用 mybatis-plus 的时候,最常用的是三个注解
- @TableName
- @TableId
- @TableField
@TableName
- 描述:表名注解,用来将实体对象与数据库表名完成映射
- 修饰范围:用在实体类上
- 常见属性:
value
:String类型,指定映射的表名(如果为空则默认将类名用来映射表名)resultMap
:String类型,用来指定 xml 配置中 resultMap 的 id 值(仅限于自己使用复杂resultMap时使用)
@TableId
- 描述:主键注释,用来将实体类中的属性与数据表中的主键完成映射
- 修饰范围:用在实体类的属性上
- 常见属性:
value
:String类型,指定实体类中与表中对应的主键列名type
:枚举类型,指定主键生成类型
IdType
值 | 描述 |
---|---|
AUTO | 数据库ID自增 |
NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | insert前自行set主键值 |
ASSIGN_ID | 分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0), 使用接口 IdentifierGenerator 的方法nextId (默认实现类为 DefaultIdentifierGenerator 雪花算法) |
ASSIGN_UUID | 分配UUID,主键类型为String(since 3.3.0), 使用接口 IdentifierGenerator 的方法nextUUID (默认default方法) |
@TableField
- 描述:字段注解(非主键),用来将实体类中的属性与数据表中的字段完成映射
- 修饰范围:用在实体类的属性上
- 常见属性:
- value:String类型,指定实体类中与表中对应的字段名(如果为空则默认将属性名用来映射字段名)
- exist:boolean类型,判断是否为数据库表字段,false表示不是数据表的字段
对于我们刚才建立的实体类来说,我们也可以这样编写
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName("user")//数据表名
public class User {
@TableId(value = "id", type = IdType.AUTO)//自增主键
private String id;
@TableField(value = "name")
private String name;
private int age;
private Date bir;
@TableField(exist = false)//不映射数据表中的任何字段
private String email;
}
重新运行测试类:
常用方法
查询方法
- 查询所有
//查询所有
@Test
public void testFindAll(){
List<User> users = userDao.selectList(null);//参数为空默认查询所有数据
users.forEach(user -> System.out.println("user = "+ user));
}
- 根据主键查询
//根据主键查询一个
@Test
public void testFindById(){
User user = userDao.selectById(1);
System.out.println("user = "+user);
}
- 条件查询
//条件查询
@Test
public void testFind(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("age", 23);//设置等值查询
//queryWrapper.lt("age", 23);//设置小于查询
//queryWrapper.le("age", 23);//设置小于等于查询
//ne对应不等于,gt对应大于,ge对应大于等于······
List<User> users = userDao.selectList(queryWrapper);
users.forEach(user -> System.out.println("user = "+user));
}
- 模糊查询
//模糊查询
@Test
public void testFindLike(){
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// like %?%
queryWrapper.like("name", "o");
//likeLeft %? 以xxx结尾的
//likeRight ?% 以xxx开头的
List<User> users = userDao.selectList(queryWrapper);
users.forEach(user -> System.out.println("user = "+ user));
}
模糊查询的结果为:
增加方法
//增加一条记录
@Test
public void testInsert(){
User user = new User();
user.setName("Jerry").setAge(23).setBir(new Date());
userDao.insert(user);
}
可以看到,数据表中已经成功添加了一条记录
更新方法
//基于主键id进行数据修改
@Test
public void testUpdateById(){
//修改3号的名字为Kafka
User user = userDao.selectById(3);
user.setName("Kafka");
userDao.updateById(user);
}
//批量修改
@Test
public void testUpdate(){
//将age为23的所有人名字修改为Chris
User user = new User();
user.setName("Chris");
QueryWrapper<User> updateWrapper = new QueryWrapper<>();
updateWrapper.eq("age", 23);
userDao.update(user, updateWrapper);
}
可以看到,第一个更新方法将 3 号的名字更新为 Kafka,第二个更新方法将年龄为23的两个人名字改成了 Chris
删除方法
//基于Id删除一条记录
@Test
public void testDeleteById() {
userDao.deleteById(3);
}
//基于条件进行删除
@Test
public void testDelete(){
//删除年龄>=23的所有人
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age", "23");
userDao.delete(queryWrapper);
}
可以看到,第一个方法删除了3号记录,第二个方法删除了年龄>=23的所有人
分页查询
注意,当前 Mybatis-Plus 的分页插件只支持单表查询,不支持多表连接查询。
我们刚才测试删除方法时已经将数据删除得所剩无几了,先往里插入几条数据
新建 config 目录,在目录下新建 UserPage
类
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@Configuration
@MapperScan("com.example.dao")
public class UserPage {
//分页的拦截器依赖
@Bean
public PaginationInterceptor paginationInterceptor(){
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
}
继续在测试类中进行测试:
- 不带条件的分页查询
//不带条件分页查询
@Test
public void testPage(){
//参数1:当前页,默认是1
// 参数2:每页显示记录数,默认为10
IPage<User> page = new Page<>(1, 2);
IPage<User> userIPage = userDao.selectPage(page, null);
//查看总数,并遍历
long total = userIPage.getTotal();
System.out.println("总记录数:" + total);
userIPage.getRecords().forEach(user -> System.out.println("user = " + user));
}
- 带条件的分页查询
//带条件分页查询
@Test
public void testPage2(){
IPage<User> page = new Page<>(1, 2);
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//根据返回年龄为20的记录做分页
queryWrapper.eq("age", 23);
IPage<User> userIPage = userDao.selectPage(page, queryWrapper);
//查看总数,并遍历
long total = userIPage.getTotal();
System.out.println("总记录数:" + total);
userIPage.getRecords().forEach(user -> System.out.println("user = " + user));
}
多数据源配置
为了确保数据库产品的稳定性,很多数据库拥有双机热备份功能。
- 第一台数据库服务器,是对外提供增删改业务的生产服务器
- 第二台数据库服务器,主要进行读的操作
这里我们就不用服务器进行演示了,直接用本机上两个不同的数据库来模拟不同的 MySQL 服务。
dynamic-datasource-spring-boot-starter
是一个基于 springboot 的快速集成多数据源的启动器,我们需要在 pom.xml
文件中引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
在 application.properties
文件中配置数据源(清空 application.yml)
spring.datasource.primary=master # 指定默认数据源
spring.datasource.dynamic.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.master.url=jdbc:mysql://localhost:3306/mybatisplus? \
useUnicode=true & characterEncoding=utf-8 & serverTimezone=Asia/Shanghai
spring.datasource.dynamic.datasource.master.username=root
spring.datasource.dynamic.datasource.master.password=123456
# 一主一丛,主数据源名称为 master,从数据源名称为 slave_1
# 一主多从同理
spring.datasource.dynamic.datasource.slave_1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.dynamic.datasource.slave_1.url=jdbc:mysql://localhost:3306/mybatisplus2? \
useUnicode=true & characterEncoding=utf-8 & serverTimezone=Asia/Shanghai
spring.datasource.dynamic.datasource.slave_1.username=root
spring.datasource.dynamic.datasource.slave_1.password=123456
在 Navicat
中新建一个数据库 mybatisplus2
作为从库,将主库中的 user 表复制过来,保证主库和从库的数据一致。
当我们要进行数据的业务切换时,需要在业务层进行,在 Dao 层是不行的。
新建 service
目录,目录下新建 UserService
接口
public interface UserService {
//查询方法
List<User> findAll();
//添加方法
void insert(User user);
}
在同级目录下,新建 UserServiceImpl
类,借助 Dao 层实现这个接口
import com.example.dao.UserDao;
import com.example.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Override
public List<User> findAll() {
return userDao.selectList(null);
}
@Override
public void insert(User user) {
userDao.insert(user);
}
}
在 test 包下,新建一个测试类 TestUserService
import com.example.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class TestUserService {
@Autowired
private UserService userService;
@Test
public void testFindAll(){
userService.findAll().forEach(user -> System.out.println("user = "+ user));
}
}
运行 testFindAll()
方法,会默认从主库中取出数据
dynamic
这个启动器为我们引入了 @DS
这个注解
@DS:
- 作用:用于切换数据源的注解
- 修饰范围:方法上或类上。
- 如果用在类上注解,则表明类中所有方法都使用这个数据源
- 如果用在方法上注解,则优先于类上的注解(局部优先原则)
- value属性:切换数据源的名称
现在主库和从库的数据是一致的,即使切换了也看不出来效果,因此我们先去修改一下从库的数据。我们把1号的名字由英文改为中文
然后我们去 UserServiceImpl
类中切换数据源。因为 @DS 注解遵循局部优先原则,那么findAll()
一定会到从数据源取数据
@Service
@Transactional
@DS("master")
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao;
@Override
@DS("slave_1")
public List<User> findAll() {
return userDao.selectList(null);
}
@Override
public void insert(User user) {
userDao.insert(user);
}
}
再次运行测试类可以发现,findAll()
方法会到从库中去取数据
我们接着在测试类编写插入的方法
@Test
public void testInsert(){
User user = new User();
user.setName("Nomun").setAge(22).setBir(new Date());
userService.insert(user);
}
运行该方法可以看到,主库中已经增加了这条记录
因为我们并没有配置主从同步,所以从库中没有插入这条记录