- Java工程师的进阶之旅
- 前置知识 SpringBoot Mybatis3 MySQL Maven
一、MyBatis-Plus简介
1.1、简介
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为
简化开发、提高效率而生。
我们的愿景是成为 MyBatis 最好的搭档,就像魂斗罗中的 1P、2P,基友搭配,效率翻倍。
1.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 操作智能分析阻断,也可自定义拦截规则,预防
误操作
1.3、支持数据库
任何能使用MyBatis进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下
- MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,
ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb - 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据
库,瀚高数据库
1.4、框架结构
二、入门案例
2.1、开发环境
- JDK8+
- maven:3.5.x 以上
- MySQL:5.7
- Spring Boot:2.6.3
- Mybatis-Plus:3.5.1
2.2、创建数据库和表
1.创建数据库和表
CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus`;
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
2.插入数据
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'Jack', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
2.3、创建工程
1、初始化工程
2、添加依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
3、配置
在 application.yml
配置文件中添加 mysql 数据库的相关配置:
# MySQL数据库连接
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis-plus:
# 配置类型别名
type-aliases-package: com.laptoy.mybatisplus.pojo
configuration:
# 配置MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
在 Spring Boot 启动类中添加 @MapperScan
注解,扫描 Mapper 文件夹
@SpringBootApplication
@MapperScan("com.laptoy.mybatisplus.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
4、编码
编写实体类 User.java
(此处使用了 Lombok 简化代码)
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
编写 Mapper 包下的 UserMapper
接口
@Repository
public interface UserMapper extends BaseMapper<User> {
}
2.4、开始使用
添加测试类,进行功能测试:
@SpringBootTest
public class SampleTest {
// 类是动态创建的@MapperScan,程序可以正常执行
// 为了避免报错,可以在mapper接口上添加 @Repository 注解
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
System.out.println(("----- selectAll method test ------"));
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
}
2.5、小结
通过以上几个简单的步骤,我们就实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写!
从以上步骤中,我们可以看到集成MyBatis-Plus
非常的简单,只需要引入 starter 工程,并配置 mapper 扫描路径即可。
但 MyBatis-Plus 的强大远不止这些功能,想要详细了解 MyBatis-Plus 的强大功能?那就继续往下看吧!
三、基本的CRUD
3.1、BaseMapper接口
- 通用 CRUD 封装
BaseMapper 接口
,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内 部对象注入容器- 泛型
T
为任意实体对象- 参数 Serializable 为任意类型主键 Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键
- 对象
Wrapper
为 条件构造器
MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,我们可以直接使用 BaseMapper接口源码地址
3.1.1、测试插入
@Test
public void testInsert() {
User user = new User(null, "jiali", 21, "baomi.com");
//INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
int result = userMapper.insert(user);
System.out.println("受影响行数:" + result);
System.out.println("id自动获取:" + user.getId());
}
最终执行的结果,所获取的id为1501824207777501186
这是因为MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id
3.1.2、测试删除
3.1.2.1、根据ID删除
@Test
public void testDeleteById() {
//DELETE FROM user WHERE id=?
int result = userMapper.deleteById(1501824207777501186L);
System.out.println("受影响行数:" + result);
}
3.1.2.2、根据ID批量删除
@Test
public void testDeleteBatchIds(){
//DELETE FROM user WHERE id IN ( ? , ? , ? )
List<Long> idList = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(idList);
System.out.println("受影响行数:"+result);
}
3.1.2.3、根据map条件删除
@Test
public void testDeleteByMap(){
//DELETE FROM user WHERE name = ? AND age = ?
Map<String, Object> map = new HashMap<>();
map.put("age", 21);
map.put("name", "jiali");
int result = userMapper.deleteByMap(map);
System.out.println("受影响行数:"+result);
}
3.1.3、测试修改
传入user对象根据user对象的id进行修改
@Test
public void testUpdateById(){
User user = new User(4L, "admin", 22, null);
//UPDATE user SET name=?, age=? WHERE id=?
int result = userMapper.updateById(user);
System.out.println("受影响行数:"+result);
}
3.1.4、测试查询
3.1.4.1、根据id查询
@Test
public void testSelectById(){
//SELECT id,name,age,email FROM user WHERE id=?
User user = userMapper.selectById(4L);
System.out.println(user);
}
3.1.4.2、根据多个id查询
@Test
public void testSelectBatchIds(){
//SELECT id,name,age,email FROM user WHERE id IN ( ? , ? )
List<Long> idList = Arrays.asList(4L, 5L);
List<User> list = userMapper.selectBatchIds(idList);
list.forEach(System.out::println);
}
3.1.4.3、根据map条件查询
@Test
public void testSelectByMap(){
//SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
Map<String, Object> map = new HashMap<>();
map.put("age", 22);
map.put("name", "admin");
List<User> list = userMapper.selectByMap(map);
list.forEach(System.out::println);
}
3.1.4.4、查询全部
@Test
public void testSelectList(){
//SELECT id,name,age,email FROM user
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
3.1.5、自定义功能
根据业务需求mabatisplus提供的方法不够用,这个时候就需要自定义映射
Springboot默认配置了mapper映射文件位置,只要放在类路径/mapper/**
下就可以自动扫描到,也可以自定义路径
@MapKey
:以某个字段的值作为key ,将每条数据转换的map集合作为value,放在一个map集合中
/**
* 根据id查询用户信息为map集合
*/
@MapKey("id")
Map<String, Object> selectMapById(Long id);
<?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.laptoy.mybatisplus.mapper.UserMapper">
<select id="selectMapById" resultType="java.util.Map">
select *
from user
where id = #{id};
</select>
</mapper>
@Test
public void testSelectMap() {
Map<String, Object> map = userMapper.selectMapById(1L);
System.out.println(map);
}
结果
3.2、IService接口
- 通用 Service CRUD 封装
IService接口
,进一步封装 CRUD 采用get 查询单行 remove 删除 list 查询集合 page 分页
前缀命名方式区分 Mapper 层避免混淆,- 泛型
T
为任意实体对象- 建议如果存在自定义通用 Service 方法的可能,请创建自己的 xxService 继承 Mybatis-Plus 提供的基类,再将自己的 xxServiceImpl 实现 xxService 且继承基类的实现类
- 对象
Wrapper
为 条件构造器
保证高可扩展性
1、Userservice接口
先继承IService接口
public interface UserService extends IService<User> {
}
2、UserSerivceImpl
实现当前UserService
并继承IService接口
的实现类ServiceImpl
,这样就不需要手动重写所有的IService方法
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
3.2.1、测试查询记录数
@Test
public void testGetCount() {
long count = userService.count();
System.out.println("总记录数:" + count);
}
3.2.2、测试批量添加
SQL长度有限制,海量数据插入单条SQL无法实行
因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
@Test
public void testSaveBatch() {
// SQL长度有限制,海量数据插入单条SQL无法实行
// 因此MP将批量插入放在了通用Service中实现,而不是通用Mapper
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 5; i++) {
User user = new User();
user.setName("user:" + i);
user.setAge(20 + i);
users.add(user);
}
//SQL:INSERT INTO t_user ( username, age ) VALUES ( ?, ? )
userService.saveBatch(users);
}
四、常用注解
4.1、@TableName
- 描述:表名注解,标识实体类对应的表
- 使用位置:实体类
- 解决数据库表名与实体类不一致的问题
单个实体类配置
@TableName("t _user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
全局配置application.yml
配置全局表名前缀
mybatis-plus:
global-config:
db-config:
table-prefix: t_
4.2、@TableId
4.2.1、简介及使用
- 描述:主键注解
- 使用位置:实体类主键字段
- MybatisPlus默认会将名为id字段的作为主键字段,如果数据表主键字段不叫id,需要使用
@TableId
指定 - 所以建议实体类主键字段都加
@TableId
如果实体类与数据表主键的字段名不一致,可以使用@TableId
来指定
测试:将数据表id字段改为uid
如果不使用@TableId
指定uid
属性会报错,因为MB默认配置会自动搜索主键字段名为id
,MB会不知道用谁当主键
public class User {
@TableId
private Long uid;
private String name;
private Integer age;
private String email;
}
4.2.2、@TableId的value属性
如果实体类与数据表主键的字段名不一致,可以使用value属性来指定
例如数据表字段为uid
实体类属性为 id
public class User {
@TableId("uid")
private Long id;
private String name;
private Integer age;
private String email;
}
4.2.3、@TableId的type属性
- 设置主键生成策略
- 默认使用雪花算法计算出20为id值
- 可以使用IdType属性来自定义id规则
- 如果插入时指定了id值那么这些type配置失效,id为设置的值
测试:数据表主键字段设置自动递增
public class User {
@TableId(value = "uid", type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}
4.2.4、设置全局主键生成策略
mybatis-plus:
global-config:
db-config:
id-type: xxx
4.3、雪花算法
雪花算法是由Twitter公布的分布式主键生成算法,它能够保证不同表的主键的不重复性,以及相同表的主键的有序性。
- 长度共64bit(一个long型)。
- 首先是一个符号位,1bit标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负
数是1,所以id一般是正数,最高位是0。 - 41bit时间截(毫秒级),存储的是时间截的差值(当前时间截 - 开始时间截),结果约等于69.73年。
- 10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID,可以部署在1024个节点)。
- 12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID)。
优点:整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率较高。
4.4、@TableField
解决实体类属性与数据库字段不一致问题
例如数据表字段为uname
实体类属性为 name
测试:修改数据表name属性为uname,此时如果实体类不指定uname属性就会报错
public class User {
@TableId(value = "uid", type = IdType.AUTO)
private Long id;
@TableField("uname")
private String name;
private Integer age;
private String email;
}
4.5、@TableLogic
- 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
- 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库
中仍旧能看到此条数据记录 - 使用场景:可以进行数据恢复
使用该注解后在进行任何增删改查时都会加上关于逻辑字段条件的语句
例如配置该注解在isDeleted字段,进行增删改查时就会自动带上关于is_deleted的字段
测试:
数据表添加字段id_deleted
默认值为0,类型为int
实体类添加属性,MB默认开启驼峰命名法
@TableLogic
private Integer isDeleted;
执行删除:删除的时候自动加上删除条件 is_deleted=0
@Test
public void delete() {
// UPDATE user SET is_deleted=1 WHERE uid=? AND is_deleted=0
userMapper.deleteById(1L);
}
执行查询:查询的时候自动加上查询条件 is_deleted = 0
@Test
public void testSelectList() {
// SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
id为1的数据查询不到,但数据库里仍有,因为逻辑只是把is_deleted
字段改为1了
五、条件构造器
5.1、Wrapper介绍
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装
- UpdateWrapper : Update 条件封装,能够直接set值而不需要new一个对象再调用set属性()方法
- AbstractLambdaWrapper : 使用Lambda 语法保证column不会写错
类::属性名
- LambdaQueryWrapper :Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapp
- AbstractWrapper : 查询条件封装,生成 sql 的 where 条件
参数 | 说明 |
---|---|
eq | 等于 |
gt | 大于 |
lt | 小于 |
between | 两者之间 |
like | 模糊查询 |
isNull | 为空 |
in | 在…之间 |
or | 或拼接 |
and | and嵌套 |
更多请查看MyBatisPlus官网关于条件构造器
5.2、QueryWrapper
5.2.1、组装查询条件
@Test
public void test01() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
//SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND (name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
wrapper.like("name", "a").between("age", 20, 30).isNotNull("email");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
5.2.2、组装排序条件
查询用户信息,按照年龄的降序排序,若年龄相同按照id升序排序
@Test
public void test02() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 ORDER BY age DESC,id ASC
wrapper.orderByDesc("age").orderByAsc("id");
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
5.2.3、组装删除条件
前面添加了逻辑删除,所以这里还是修改is_deleted
字段
@Test
public void test03() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// UPDATE user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)
wrapper.isNull("email");
int delete = userMapper.delete(wrapper);
}
5.2.4、条件优先级
5.2.4.1、组装修改条件一
将 年龄大于20
用户名包含a
或 邮箱为null
的用户信息修改
@Test
public void test04() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 20)
.like("name", "a")
.or()
.isNull("email");
User user = new User();
user.setName("jiali");
user.setEmail("aaa.com");
// UPDATE user SET name=?, email=? WHERE is_deleted=0 AND (age > ? AND name LIKE ? OR email IS NULL)
userMapper.update(user, wrapper);
}
5.2.4.2、组装修改条件二(Lamda表达式)
将 用户名包含a
并且 (年龄大于20或邮箱为null
)的用户信息修改
@Test
public void test05() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("name", "a")
.and(i -> i.gt("age", 20).or().isNull("email"));
User user = new User();
user.setName("jiali");
user.setEmail("aaa.com");
// UPDATE user SET name=?, email=? WHERE is_deleted=0 AND (name LIKE ? AND (age > ? OR email IS NULL))
userMapper.update(user, wrapper);
}
5.2.5、组装select语句
查询表的部分字段
@Test
public void test06() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
// //SELECT id,name FROM user
wrapper.select("id", "name");
// selectMaps()返回Map集合列表,通常配合select()使用,避免User对象中没有被查询到的列值 为null
List<Map<String, Object>> maps = userMapper.selectMaps(wrapper);
maps.forEach(System.out::println);
}
5.2.6、组装子查询
查询id小于等于3的用户信息
@Test
public void test07() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.inSql("id", "select id from user where id <=3");
// SELECT id,username AS name,age,email,is_deleted FROM t_user WHERE (id IN (select id from t_user where id <= 3))
List<User> users = userMapper.selectList(wrapper);
users.forEach(System.out::println);
}
5.3、UpdateWrapper
可以设置条件及修改的字段,不需要new实体类进行set赋值
@Test
public void test08() {
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.like("name", "a").and(i ->
i.gt("age", 20).isNull("email")
);
// 设置修改的字段
updateWrapper.set("name","jiali").set("email","jiali.com");
userMapper.update(null,updateWrapper);
}
5.4、模拟开发组装条件
@Test
public void test09() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
Integer ageBegin = 10;
Integer ageEnd = 30;
String name = "a";
if (StringUtils.isNotBlank(name)) {
wrapper.like("name", name);
}
if (ageBegin != null) {
wrapper.gt("age", ageBegin);
}
if (ageEnd != null) {
wrapper.lt("age", ageEnd);
}
// SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND (name LIKE ? AND age > ? AND age < ?)
userMapper.selectList(wrapper);
}
5.5、使用condition组装条件
很多方法都内置了condition属性
直接使用condition进行条件装配优化上述5.4的情况
@Test
public void test10() {
QueryWrapper<User> wrapper = new QueryWrapper<>();
Integer ageBegin = 10;
Integer ageEnd = 30;
String name = "a";
wrapper.like(StringUtils.isNotBlank(name), "name", name)
.ge(ageBegin != null, "age", ageBegin)
.le(ageEnd != null, "age", ageEnd);
userMapper.selectList(wrapper);
}
5.6、LambdaQueryWrapper
能够防止字段名写错,使用类::get属性
代替人为输入字段名
@Test
public void test11() {
String name = "a";
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(StringUtils.isNotBlank(name), User::getName, name);
// SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 AND (name LIKE ?)
List<User> users = userMapper.selectList(lambdaQueryWrapper);
users.forEach(System.out::println);
}
5.7、LambdaUpdateWrapper
能够防止字段名写错,使用类::get属性
代替人为输入字段名
@Test
public void test12() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.like(User::getName, "a");
// 设置修改的字段
wrapper.set(User::getName, "jiali");
userMapper.update(null, wrapper);
}
·
六、插件
6.1、分页插件
1、配置类指定拦截数据源为MYSQL
@Configuration
@MapperScan("com.laptoy.mybatisplus.mapper")
public class MBConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
@Test
public void test01() {
Page<User> page = new Page<>(1, 3);
// SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 LIMIT ?
mapper.selectPage(page, null);
}
2、selectPage
方法解析
Page
继承IPage
,所以可以直接传入Page对象
3、分页相关数据获取
@Test
public void test01() {
Page<User> page = new Page<>(2, 3);
// SELECT id,name,age,email,is_deleted FROM user WHERE is_deleted=0 LIMIT ?
mapper.selectPage(page, null);
System.out.println(page.getRecords());
System.out.println(page.getPages());
System.out.println(page.getTotal());
System.out.println(page.hasNext());
}
6.2、自定义分页功能
Page
MBP提供的分页对象,必须位于参数第一个位置
返回值也必须为Page
/**
* 通过年龄查询用户信息并分页
* @param page MBP提供的分页对象,必须位于参数第一个位置
*/
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("age") Integer age);
千万别加;
这个符号在后面,因为后面MBP还要进行拼接sql语句limit参数
<select id="selectPageVo" resultType="User">
select id, name, age from USER where age > #{age}
</select>
@Test
public void test02() {
Page<User> page = new Page<>(1, 3);
mapper.selectPageVo(page, 20);
System.out.println(page.getRecords());
System.out.println(page.getPages());
System.out.println(page.getTotal());
System.out.println(page.hasNext());
}
6.3、乐观锁
OptimisticLockerInnerInterceptor
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时, set version = oldVersion+1 where version = oldVersion
- 如果 version 不对,就更新失败
取出记录时,获取当前version
SELECT id,name,price,version FROM product WHERE id=1
更新时,version + 1,如果where语句中的version版本不对,则更新失败
UPDATE product SET price=price+50, version=version + 1 WHERE id=1 AND version=1
模拟乐观锁
1、数据表及数据
CREATE TABLE t_product (
id BIGINT(20) NOT NULL COMMENT '主键ID',
NAME VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
price INT(11) DEFAULT 0 COMMENT '价格',
VERSION INT(11) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (id)
);
INSERT INTO t_product (id, NAME, price) VALUES (1, 'macbook', 100);
2、实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
@TableName("t_product")
public class Product {
@TableId
private Long id;
private String name;
private Integer price;
@Version
private Integer version;
}
3、配置类
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
代码在这
@Test
public void test03() {
Product p1 = productMapper.selectById(1L); // 价格:100 版本:0
Product p2 = productMapper.selectById(1L); // 价格:100 版本:0
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1); // 成功 价格:150 版本:1
System.out.println("p1修改后的结果" + result1);// 1
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2); // 失败,因为p2的版本为0
System.out.println("p2修改后的结果" + result2);// 失败
if (result2 == 0) {
p2 = productMapper.selectById(1L); // 价格:150 版本:1
p2.setPrice(p2.getPrice() - 30);
result2 = productMapper.updateById(p2); // 价格:120 版本:2
System.out.println("p2再次修改后的结果" + result2);
}
Product customer = productMapper.selectById(1L);
System.out.println("用户看到的价格" + customer.getPrice()); // 120
}
说明
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下
newVersion = oldVersion + 1
- newVersion 会回写到 entity 中
- 仅支持 updateById(id) 与 update(entity, wrapper) 方法
- 在 update(entity, wrapper) 方法下, wrapper 不能复用!!!
·
七、通用枚举
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现
1、数据库添加int类型 sex字段
2、创建枚举类
@EnumValue
标记数据库存的值是code
@Getter
public enum SexEnum {
MALE(1, "男"),
FAMALE(2, "女");
// 将该字段作为值存入数据库,否则会默认将value(MALE,FAMALE)放入数据库
@EnumValue
private Integer sex;
private String sexName;
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
3、User类添加字段private SexEnum sex;
4、配置文件扫描枚举类所在包
mybatis-plus:
type-enums-package: com.laptoy.mybatisplus.enums
5、测试
@Test
public void test04() {
User user = new User();
user.setName("laptoy");
user.setSex(SexEnum.MALE);
mapper.insert(user);
}
·
八、代码生成器
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
修改自定义配置后直接执行
public class FastAutoGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false", "root", "root")
.globalConfig(builder -> {
builder.author("laptoy") // 设置作者
//.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://MyCode/GeneratorCode"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.laptoy") // 设置父包名
.moduleName("mybatisplus") // 设置父包模块名 // 也就是生成在com.laptoy.mybatisplus下
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://MyCode/GeneratorCode")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
·
九、多数据源
1、引入dynamic-datasource-spring-boot-starter
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
2、配置数据源
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master:
url: jdbc:mysql://localhost:3306/laptoy_user
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
slave_1:
url: jdbc:mysql://localhost:3306/laptoy_product
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
# 多主多从 纯粹多库(记得设置primary) 混合配置
spring: spring: spring:
datasource: datasource: datasource:
dynamic: dynamic: dynamic:
datasource: datasource: datasource:
master_1: mysql: master:
master_2: oracle: slave_1:
slave_1: sqlserver: slave_2:
slave_2: postgresql: oracle_1:
slave_3: h2: oracle_2:
3、使用 @DS 切换数据源
@DS 可以注解在方法上或类上,同时存在就近原则 方法
上注解 优先于 类
上注解。
注解 | 结果 |
---|---|
没有@DS | 默认数据源 |
@DS(“dsName”) | dsName可以为组名也可以为具体某个库的名称 |
@Service
@DS("master")
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private ProductMapper productMapper;
public List selectByMaster() {
return userMapper.selectList();
}
@Override
@DS("slave_1")
public List selectBySlave() {
return productMapper.selectList();
}
}
·
十、MyBatisX插件
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生 官网地址
10.1、定位接口与映射文件
10.2、代码快速生成
测试连接
设置显示的数据库
1、
2、
3、
结果展示
10.3、快速生成CRUD
Alt+Insert
·