MybatisPlus
mybatis-plus:3.5.3.1 注意:springboot版本需要在3.2.0以下才兼容
mybatis-plus官网:https://baomidou.com
快速了解(入门案例)
准备数据库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18DROP 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)
);
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');创建项目,导入依赖(pom.xml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.5</version>
</parent>
<groupId>cn.xnj</groupId>
<artifactId>springboot-mybatisplus-04</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- spring-boot核心包 -->
<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.5.3.1</version>
</dependency>
<!--数据库相关配置启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--druid数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.22</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
</dependencies>
<!--SpringBoot应用打包插件-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>配置文件(application.yml)和启动类(MainApp)
application.yml:src/main/resources1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
url: jdbc:mysql://localhost:3306/studb
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
#myBatisPlus常用配置
mybatis-plus:
type-aliases-package: com.xnj.domain #别名扫描,这样在mapperXml里就不需要写类的全路径名了
mapper-locations: classpath*:mapper/**/*.xml #Mapper.xml文件地址,默认值
configuration:
map-underscore-to-camel-case: true #是否开启下划线和驼峰的映射
cache-enabled: false #是否开启二级缓存
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #控制台打印日志
global-config:
db-config:
id-type: assign_id #id为雪花算法生成
update-strategy: not_null #更新策略:只更新非空字段
table-prefix: t_ #表前缀MainApp:cn.xnj
1
2
3
4
5
6
7
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class,args);
}
}实体类(User)和持久层(UserMapper)
User: cn.xnj.pojo1
2
3
4
5
6
7
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}UserMapper:xn.xnj.mapper
1
2public interface UserMapper extends BaseMapper<User> {
}测试(MyBatisPlusTest)
MyBatisPlusTest:src/main/test/java/cn.xnj.test1
2
3
4
5
6
7
8
9
10
11
public class MyBatisPlusTest {
private UserMapper userMapper;
public void test01(){
List<User> userList = userMapper.selectList(null);
System.out.println(userList);
}
}
基于Mapper接口CRUD
下面使用的案例即上面的入门案例
Insert方法
语法:1
2
3
4// 插入一条记录
// T 就是要插入的实体对象
// 默认主键生成策略为雪花算法
int insert(T entity);
参数说明:
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 |
示例:1
2
3
4
5
6
7
8
9
public void test_insert(){
User user = new User();
user.setName("李四");
user.setAge(20);
user.setEmail("EMAIL");
int rows = userMapper.insert(user);
System.out.println("rows:"+rows);
}
Delete方法
语法:1
2
3
4
5
6
7
8
9
10
11
12
13// 1. 根据 ID 删除
int deleteById(Serializable id);
// 2. 删除(根据ID 批量删除)
int deleteBatchIds(; Collection<? extends Serializable> idList)
// 3. 根据 columnMap 条件,删除记录
int deleteByMap(; Map<String, Object> columnMap)
// 4. 根据 entity 条件,删除记录
int delete(; Wrapper<T> wrapper)
参数说明:
类型 | 参数名 | 描述 |
---|---|---|
Serializable | id | 主键 ID |
Collection<? extends Serializable> | idList | 主键 ID 列表(不能为 null 以及 empty) |
Map |
columnMap | 表字段 map 对象 |
Wrapper |
wrapper | 实体对象封装操作类(可以为 null) |
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void test_delete(){
// 1. 根据id删除
//DELETE FROM user WHERE id=?
int rows1 = userMapper.deleteById(1276067841L);
System.out.println("rows1="+rows1);
// 2. 删除(根据ID 批量删除)
//DELETE FROM user WHERE id IN ( ? , ? , ? )
Collection<Long> ids = List.of(1L,2L,3L);
int rows2 = userMapper.deleteBatchIds(ids);
System.out.println("rows2="+rows2);
// 3. 根据 columnMap 条件,删除记录
//DELETE FROM user WHERE name = ? AND age = ?
Map<String,Object> columnMap = new HashMap<>();
columnMap.put("age",24);
columnMap.put("name","Billie");
int rows4 = userMapper.deleteByMap(columnMap);
System.out.println("rows4="+rows4);
// 4. 根据 entity 条件,删除记录 wrapper 条件
//wrapper 条件封装对象,可以无限的封装条件
//userMapper.delete(wrapper);
}
Update方法
语法:1
2
3
4
5
6// 1. 根据 ID 修改 主键属性必须有值
int updateById(; T entity)
// 2. 根据 whereWrapper 条件,更新记录
int update( T updateEntity,
; Wrapper<T> whereWrapper)
参数说明:
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 (set 条件值,可为 null) |
Wrapper |
updateWrapper | 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) |
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void test_update(){
//当属性为null时,不修改,所以实体属性都用封装类型
// 1. 根据 ID 修改 ,主键属性必须有值 修改id为1的用户的年龄为30
// UPDATE user SET age=? WHERE id=?
User user = new User();
user.setId(1L);
user.setAge(30);
int rows1 = userMapper.updateById(user);
System.out.println("rows1="+rows1);
// 2. 根据 whereWrapper 条件,更新记录
//将所有人的年龄改为22
// UPDATE user SET age=?
User user2 = new User();
user2.setAge(22);
int rows2 = userMapper.update(user2,null);//null表示没有条件
System.out.println("rows2="+rows2);
}
Select方法
语法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// 1. 根据 ID 查询
T selectById(Serializable id);
// 2. 查询(根据ID 批量查询)
List<T> selectBatchIds(; Collection<? extends Serializable> idList)
// 3. 根据 entity 条件,查询一条记录
T selectOne(; Wrapper<T> queryWrapper)
// 4. 根据 entity 条件,查询全部记录
List<T> selectList(; Wrapper<T> queryWrapper)
// 5. 查询(根据 columnMap 条件)
List<T> selectByMap(; Map<String, Object> columnMap)
// 6. 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(; Wrapper<T> queryWrapper)
// 7. 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(; Wrapper<T> queryWrapper)
// 8. 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, ; Wrapper<T> queryWrapper)
// 9. 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, ; Wrapper<T> queryWrapper)
// 10. 根据 Wrapper 条件,查询总记录数
Integer selectCount(; Wrapper<T> queryWrapper)
参数说明:
类型 | 参数名 | 描述 |
---|---|---|
Serializable | id | 主键 ID |
Wrapper |
queryWrapper | 实体对象封装操作类(可以为 null) |
Collection<? extends Serializable> | idList | 主键 ID 列表(不能为 null 以及 empty) |
Map |
columnMap | 表字段 map 对象 |
IPage |
page | 分页查询条件(可以为 RowBounds.DEFAULT) |
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void test_select(){
//1. 根据id查询
//SELECT id,name,age,email FROM user WHERE id=?
User user = userMapper.selectById(1L);
System.out.println("user="+user);
//2. 根据ID 批量查询
//SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
Collection<Long> ids = List.of(1L,2L,3L);
List<User> userList1 = userMapper.selectBatchIds(ids);
System.out.println("userList1 ="+userList1);
// 5. 查询(根据 columnMap 条件)
//SELECT id,name,age,email FROM user WHERE name =? AND age =?
Map<String,Object> columnMap = new HashMap<>();
columnMap.put("age",24);
columnMap.put("name","Billie");
List<User> userList2 = userMapper.selectByMap(columnMap);
System.out.println("userList2="+userList2);
}
基于Service接口CRUD
下面使用的案例即上面的入门案例
通用 Service CRUD 封装IService (opens new window)接口,进一步封装 CRUD 采用 get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分 Mapper
层避免混淆,
对比Mapper接口CRUD区别:
- service添加了批量方法
- service层的方法自动添加事务
使用IService接口方式
mapper还是照前面写
继承BaseMapper<T>
【cn.xnj.mapper】1
public interface UserMapper extends BaseMapper<User> {}
service接口
继承Iservice<T>
【cn.xnj.service】1
public interface UserService extends IService<User> {}
service的实现类
继承ServiceImpl<M extends BaseMapper<T>, T>
【cn.xnj.service.impl】1
2
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}
save
语法:1
2
3
4
5
6// 1. 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 2. 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 3. 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
参数:
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 |
Collection |
entityList | 实体对象集合 |
int | batchSize | 插入批次数量(即一次insert语句插入的条数) |
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public void test_save(){
//1.插入一条记录(选择字段,策略插入)
User user1 = new User(null,"李四",20,"EMAIL");
boolean flg1 = userService.save(user1);
//2. 插入(批量) 默认批次 这里不能再用上面的user对象加入集合中进行批量插入,出现主键重复
//假设默认的批次的大小为2
/**
* INSERT INTO user (name,age, email) VALUES
* ('王五1', 20,'EMAIL1'),
* ('王五2', 21,'EMAIL2'),
*/
List<User> userList = new ArrayList<>();
User user2 = new User(null,"王五1",20,"EMAIL");
User user3 = new User(null,"王五2",21,"EMAIL2");
userList.add(user2);
userList.add(user3);
boolean flg2 = userService.saveBatch(userList);
//3. 插入(批量) 分批次 设置批次大小为1
/**
* INSERT INTO user (name,age, email) VALUES('张三1', 20,'EMAIL'),
* INSERT INTO user (name,age, email) VALUES('张三2', 21,'EMAIL2'),
* INSERT INTO user (name,age, email) VALUES('张三3', 20,'EMAIL3'),
*/
List<User> userList2 = new ArrayList<>();
User user4 = new User(null,"张三1",20,"EMAIL");
User user5 = new User(null,"张三2",21,"EMAIL2");
User user6 = new User(null,"张三3",20,"EMAIL");
userList2.add(user4);
userList2.add(user5);
userList2.add(user6);
boolean flg3 = userService.saveBatch(userList2,1);
}
saveOrUpdate
语法:1
2
3
4
5
6
7
8// 1. TableId 注解属性值存在则更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 2. 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 3. 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 4. 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
参数:
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 |
Wrapper |
updateWrapper | 实体对象封装操作类 UpdateWrapper |
Collection |
entityList | 实体对象集合 |
int | batchSize | 插入批次数量 |
示例:1
2
3
4
5
6
7
8
9
10
11
public void test_saveOrUpdate(){
// 1. TableId 注解属性值存在则更新记录,否插入一条记录
//如果id有值就更新,没有值就插入一条记录
User user = new User(null,"John Doe",30,"john.doe@example.com");
boolean flg1 = userService.saveOrUpdate(user);// 插入一条记录
user.setId(1843191296522436610L);
user.setAge(40);
boolean flg2 = userService.saveOrUpdate(user);// 更新一条记录
}
remove
语法:1
2
3
4
5
6
7
8// 1. 根据 queryWrapper 设置的条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 2. 根据 ID 删除
boolean removeById(Serializable id);
// 3. 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 4. 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
参数:
类型 | 参数名 | 描述 |
---|---|---|
Wrapper |
queryWrapper | 实体包装类 QueryWrapper |
Serializable | id | 主键 ID |
Map |
columnMap | 表字段 map 对象 |
Collection<? extends Serializable> | idList | 主键 ID 列表 |
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void test_remove(){
// 2. 根据ID 删除一条记录
boolean flag = userService.removeById(1843191296522436610L);
// 3. 根据 columnMap 条件,删除记录
Map<String, Object> columnMap = new HashMap<>();
columnMap.put("name", "张三1");
columnMap.put("age", 20);
boolean flag2 = userService.removeByMap(columnMap);
// 4. 删除(根据ID 批量删除)
//DELETE FROM user WHERE id IN (?,?,? )
Collection<Long> ids = List.of(1L,2L,3L);
boolean flag3 = userService.removeByIds(ids);
}
update
语法:1
2
3
4
5
6
7
8
9
10// 1. 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 2. 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 3. 根据 ID 选择修改
boolean updateById(T entity);
// 4. 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 5. 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
参数:
类型 | 参数名 | 描述 |
---|---|---|
Wrapper |
updateWrapper | 实体对象封装操作类 UpdateWrapper |
T | entity | 实体对象 |
Collection |
entityList | 实体对象集合 |
int | batchSize | 更新批次数量 |
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void test_update() {
//为空的属性不更改
// 3. 根据 ID 选择修改
User user = new User(1843124048071974914L, "李四", 28, null);
boolean flag = userService.updateById(user);
// 4. 根据ID 批量更新 默认批次
List<User> userList = new ArrayList<>();
User user1 = new User(5L, null, 58, null);
User user2 = new User(4L, null, 68, null);
userList.add(user1);
userList.add(user2);
boolean flag2 = userService.updateBatchById(userList);
// 5. 根据ID 批量更新 分批次
}
get
语法:1
2
3
4
5
6
7
8
9
10// 1. 根据 ID 查询
T getById(Serializable id);
// 2. 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 3. 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 4. 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 5. 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
参数:
类型 | 参数名 | 描述 |
---|---|---|
Serializable | id | 主键 ID |
Wrapper |
queryWrapper | 实体对象封装操作类 QueryWrapper |
boolean | throwEx | 有多个 result 是否抛出异常 |
T | entity | 实体对象 |
Function<? super Object, V> | mapper 转换函数 |
示例:1
2
3
4
5
6
7
public void test_get(){
User user = userService.getById(3L);//返回的是单个对象
System.out.println(user);
}
list
语法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 1. 查询所有
List<T> list();
// 2. 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 3. 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 4. 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 5. 查询所有列表
List<Map<String, Object>> listMaps();
// 6. 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 7. 查询全部记录
List<Object> listObjs();
// 8. 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 9. 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 10. 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
参数:
|类型|参数名|描述|
|:—-:|:—-:|:—-:|
|Wrapper
|Collection<? extends Serializable>|idList|主键 ID 列表|
|Map
|Function<? super Object, V>|mapper|转换函数|
示例:1
2
3
4
5
6
public void test_list(){
List<User> userList = userService.list(null);//查询全部,返回的是集合
System.out.println(userList);
}
MyBatisPlus更新策略(Mybatis-Plus update strategy)
使用Mybatis-Plus提供的更新方法时,若实体中的字段为null
,默认情况下,最终生成的update语句中,不会包含该字段。若想改变默认行为,可做以下配置。
全局配置
在application.yml
中配置如下参数1
2
3
4mybatis-plus:
global-config:
db-config:
update-strategy: <strategy>注:上述
<strategy>
可选值有:ignore
、not_null
、not_empty
、never
,默认值为not_null
ignore
:忽略空值判断,不管字段是否为空,都会进行更新not_null
:进行非空判断,字段非空才会进行判断
not_empty
:进行非空判断,并进行非空串(””)判断,主要针对字符串类型never
:从不进行更新,不管该字段为何值,都不更新
局部配置
在实体类中的具体字段通过@TableField
注解进行配置,如下:1
2
3
private String password;
page分页查询
前置条件说明【依然是上面入门案例】
1
2
3
4
5
6
7
8
9package cn.xnj.mapper;
public interface UserMapper extends BaseMapper<User> {}
package cn.xnj.service;
public interface UserService extends IService<User> {}
package cn.xnj.service.impl;
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}在配置类中将mybatis-plus插件加入ioc容器中,并加入
分页插件
,并指定数据库类型如DbType.MYSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainApp {
public static void main(String[] args) {
SpringApplication.run(MainApp.class,args);
}
//mybatis-plus插件加入到ioc容器中
public MybatisPlusInterceptor plusInterceptor(){
//mybatis-plus的插件集合【加入到这个插件集合中即可:分页插件,乐观锁插件..】
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//加入分页插件
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return mybatisPlusInterceptor;
}
}MybatisPlus的
BaseMapper
和ServiceImpl
均提供了常用的分页查询的方法,例如:BaseMapper
的分页查询:1
IPage<T> selectPage(IPage<T> page,Wrapper<T> queryWrapper);
ServiceImpl
的分页查询:1
2
3
4// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);自定义Mapper
对于自定义SQL,也可以十分方便的完成分页查询,如下Mapper
接口:1
IPage<UserVo> selectPageVo(IPage<?> page, Integer state);
Mapper.xml
:1
2
3<select id="selectPageVo" resultType="xxx.xxx.xxx.UserVo">
SELECT id,name FROM user WHERE state=#{state}
</select>注意:
Mapper.xml
中的SQL只需实现查询list
的逻辑即可,无需关注分页的逻辑。
- 测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MybatisPlusPageTest {
private UserMapper userMapper;
public void test01(){
//IPage -> Page(页码,页容量)
IPage<User> page = new Page<>(1,2);
IPage<User> result = userMapper.selectPage(page,null);//第二个参数为条件
//结果最后也会被封装到page中
long current = result.getCurrent();//当前页页码
long size = result.getSize();//页容量
List<User> records = result.getRecords();//当前页数据
long total = result.getTotal();//总记录数
long pages = result.getPages();//总页数
boolean hasNext = result.hasNext();//是否有下一页
boolean hasPrevious = result.hasPrevious();//是否有上一页
}
}
自定义page分页查询
- 同上方,需要在配置类中添加mybatis-plus分页的插件
在mapper中定义你自己的page方法
注意方法返回值:IPage<查询返回类型> 如查询部分字段还重命名了就可以用Map1
2
3
4
5package cn.xnj.mapper;
public interface UserMapper extends BaseMapper<User> {
//返回的是IPage
IPage<User> selectPageByAge(IPage<User> page, ; Integer age)
}mybatisplus对mapperxml文件的默认目录是:resources/mapper
所以如果不在配置文件里新定规则的话,直接创建如下:resources/mapper/XxxMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
<!-- namespace=接口类的全限定名,这样实现对应 -->
<mapper namespace="cn.xnj.mapper.UserMapper">
<!--resultType的value是page的泛型-->
<select id="selectPageByAge" resultType="cn.xnj.pojo.User">
select * from user where age>#{age}
</select>
</mapper>测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private UserMapper userMapper;
public void testMyPage(){
//IPage -> Page(页码,页容量)
IPage<User> page = new Page<>(1,2);
IPage<User> result=userMapper.selectPageByAge(page,18);
//结果page最后也会被封装结果
long current = result.getCurrent();//当前页页码
long size = result.getSize();//页容量
List<User> records = result.getRecords();//当前页数据
long total = result.getTotal();//总记录数
long pages = result.getPages();//总页数
boolean hasNext = result.hasNext();//是否有下一页
boolean hasPrevious = result.hasPrevious();//是否有上一页
System.out.println(result);
}
条件构造器
MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。
修改的时候:UpdateWrapper、LambdaUpdateWrapper
删除,查询,修改:QueryWrapper、LambdaQueryWrapper
函数:
函数名 | 说明 | 函数名 | 说明 |
---|---|---|---|
eq | 等于= | ne | 不等于<> |
gt | 大于> | ge | 大于等于>= |
lt | 小于< | le | 小于等于<= |
between | between a and b | notBetween | not between a and b |
like | like ‘%值%’ | notLike | not like ‘%值%’ |
likeLeft | like ‘%值’ | likeRight | like ‘值%’ |
isNull | 字段 is null | isNotNull | 字段 is not null |
in | 字段 in (v0,v1,..) | notIn | 字段 not in (v0,v1,..) |
inSql | 字段 in (sql语句) | notInSql | 字段 not in (sql语句) |
groupBy | 分组 | orderBy | 排序 |
orderByAsc | 升序排序 | orderByDesc | 降序排序 |
having | having(sql语句) | ||
or | 拼接or | and | and嵌套 |
基于QueryWrapper组装条件
示例:
创建一个QueryWrapper对象来组装条件
普通使用或链式调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void test_01(){
//查询用户名包含'张',年龄在20,30之间,并且邮箱不为空
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
/*queryWrapper.like("name","张");
queryWrapper.between("age",20,30);
queryWrapper.isNotNull("email");*/
//链式调用
queryWrapper.like("name","张")
.between("age",20,30)
.isNotNull("email");
List<User> userList = userMapper.selectList(queryWrapper);
System.out.println(userList);
}
排序1
2
3
4
5
6
7// 按年龄降序查询用户,如果年龄相同,则按id升序排列
QueryWrapper<User> queryWrapper1 = new QueryWrapper<>();
queryWrapper1.orderByDesc("age")
.orderByAsc("id");
List<User> userList1 = userMapper.selectList(queryWrapper1);
System.out.println(userList1);
查询指定列 queryWrapper2.select("列1","列2"...)
1
2
3
4
5
6
7// 查询用户名和年龄字段,并且用户年龄大于25的用户
QueryWrapper<User> queryWrapper2 = new QueryWrapper<>();
queryWrapper2.gt("age",25);
queryWrapper2.select("name","age");
List<User> userList2 = userMapper.selectList(queryWrapper2);
System.out.println(userList2);
删除1
2
3
4
5
6
7
8
9
public void test_02(){
// 1. 删除邮箱为null的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int rows = userMapper.delete(queryWrapper);
System.out.println(rows);
}
修改操作
使用queryWrapper + 实体类形式可以实现修改,但是无法将列值修改为null值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void test_03(){
//将年龄大于20并且用户名中包含有'张'或邮箱为null的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age",20)
.like("name","张")//条件直接俄调用方法默认使用and拼接
.or().isNull("email");
User user = new User();
user.setAge(25);
user.setEmail("EMAIL666");
int rows = userMapper.update(user,queryWrapper);
System.out.println(rows);
}
条件判断(if test=”判断”)
每个方法都会有一个boolean condition,运行第一位放一个比较表达式 true则整个条件生效 false不生效1
2
3
4
5
6
7
8
9
10
public void test_04(){
//前端传两个参数:name不为空 条件=,age>18 条件=
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
String name = "张xx";
Integer age = 23;
queryWrapper.eq(StringUtils.isNotBlank(name),"name",name);
queryWrapper.eq(age!=null && age>18,"age",age);
List<User> userList = userMapper.selectList(queryWrapper);
}
基于UpdateWrapper组装条件
updateWrapper能直接修改数据,也能将数据修改为任意值如null【QueryWrapper不能修改为null】1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MyBatisPlusUpdateWrapperTest {
private UserMapper userMapper;
public void test_01(){
//updatewrappper修改【条件,修改】
// 可以直接修改数据 .set(“列名”,值)
//允许修改为任意值 .set("列名",null)
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.gt("age",20)
.like("name","张")
.or().isNull("email")
.set("age",99);
int rows = userMapper.update(null, updateWrapper);
}
}
基于LambdaQueryWrapper和LambdaUpdateWrapper组装条件
在写条件字段时可以直接:(对象::get方法,值)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class MyBatisPlusLambdaQueryWrapperTest {
private UserMapper userMapper;
//LambdaQueryWrapper
public void test_select(){
// 查询用户名包含'张',年龄在20,30之间,并且邮箱不为空
//queryWrapper,链式调用
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name","张")
.between("age",20,30)
.isNotNull("email");
List<User> userList1 = userMapper.selectList(queryWrapper);
//LambdaQueryWrapper,lambda
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.like(User::getName,"张")
.between(User::getAge,20,30)
.isNotNull(User::getEmail);
List<User> userList2 = userMapper.selectList(lambdaQueryWrapper);
}
//LambdaUpdateWrapper
public void test_update(){
//将年龄大于20并且用户名中包含有'张'的用户年龄改为18岁
//UpdateQueryWrapper,修改
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.gt("age",20)
.like("name","张")//条件直接俄调用方法默认使用and拼接
.set("age",18);
int rows1 = userMapper.update(null,updateWrapper);
//LambdaQueryWrapper
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.gt(User::getAge,20)
.like(User::getName,"张")
.set(User::getAge,18);
int rows2 = userMapper.update(null,lambdaUpdateWrapper);
}
}
MyBatisPlus核心注解使用
- @TableName(value=”表名”)
如果仅使用该注解,而不指定值,就使用类名为表名,忽略大小写,可以省略该注解!
也可以在全局配置文件中指定类名与表名的映射关系1
2
3
4mybatis-plus: # mybatis-plus的配置
global-config:
db-config:
table-prefix: t_ # 表名前缀字符串 User -> t_uservalue
:当实体类名与数据库表名不一致的时候,可以用value来指定数据库表名
@TableId(value=”主键列名”,type=主键策略)
用在属性上指定主键id字段value
:当主键名与属性名完全不同时在里面填主键字段名type
:主键策略如:auto、ASSIGN_ID(雪花算法生成不重复的long型id,默认)
ASSIGN_ID雪花算法: 1.数据库主键为 bigint / varchar(64)类型 2.实体属性为long 3.随机生成不重复
auto自增:1.数据库主键类型为数字 设置了auto_increment 2.插入数据自增长
也可以通过全局配置文件配置type1
2
3
4
5
6
7#myBatisPlus常用配置
mybatis-plus:
global-config:
db-config:
id-type: assign_id #id为雪花算法生成
update-strategy: not_null #更新策略:只更新非空字段
table-prefix: t_ #表前缀
@TableField(value="非主键字段名",exist = 是否为表字段)
- @TableField用于实体类非主键字段的其他属性上
value
:当表字段名和属性名不一致就可以使用value来指定exist
:当为false时表示该字段不属于数据库表字段,插入或查询时不参与
逻辑删除的实现
物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据
逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库中仍旧能看到此条数据记录
表添加逻辑删除字段(可以是一个布尔类型、整数类型或枚举类型。)
1
alter table user add deleted int default 0 ; # int 类型 1 逻辑删除 0 未逻辑删除
实体类添加逻辑删除属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class User {
// @TableId
private Integer id;
private String name;
private Integer age;
private String email;
//逻辑删除,默认值为0,1为删除
//当删除数据的时候,会自动将deleted字段的值改为1
//当查询数据的时候,会自动将deleted字段的值为1的数据过滤掉,只查询deleted=0的数据
//逻辑删除字段 int mybatis-plus下,默认 逻辑删除值为1 未逻辑删除 0
private Integer deleted;
}如果觉得每个类都添加
@TableLogic
注解麻烦,可以在全局配置中指定1
2
3
4
5
6mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
通用枚举
情景:
- 对一个实体类有状态字段,存入数据库的状态字段为数字如:1,2。
- 返回前端时需要将数字转为对应的字符串如:”正常”、“冻结”
定义如下实体类在实体类中的状态字段改为枚举类,原本判断如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum UserStatus {
Nomarl(1, "正常"),
Freeze(2, "冻结");
// 枚举值序列化到数据库
private final int code;
//枚举值传给前端
private final String value;
UserStatus(int code, String value) {
this.code = code;
this.value = value;
}
}user.getStatus() == 1
改为user.getStatus() == UserStatus.Nomarl
注解讲解
- 添加配置和注解
@EnumValue
然后在枚举类中,需要传给数据库的枚举类字段,那么在枚举类中添加1
2
3mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler #枚举类型处理器@EnumValue
注解。最后保存到数据库时,会保存枚举类的注解字段值 - JsonValue注解
@JsonValue
在枚举类中,需要传给前端的枚举类字段,那么在枚举类中添加@JsonValue
注解。最后查询结果会返回枚举类的注解字段值
- 添加配置和注解
乐观锁的实现
乐观锁和悲观锁:
用上厕所蹲坑来比喻,悲观锁就是有厕所门的厕所,一个人进去了锁了门,另一个人就进不去了。乐观锁就是没有厕所门的厕所,一个人进去了,另一个人进去发现已经有人了就失败,再进去发现还是有人还是失败,直到厕所里没人了再进去成功。
注意:悲观锁和乐观锁是两种解决并发数据问题的思路,不是具体技术!!!
乐观锁具体方案:
版本号/时间戳:为数据添加一个版本号或时间戳字段,每次更新数据时,比较当前版本号或时间戳与期望值是否一致,若一致则更新成功,否则表示数据已被修改,需要进行冲突处理。
- 每条数据添加一个版本号字段version
- 取出记录时,获取当前 version
- 更新时,检查获取版本号是不是数据库当前最新版本号
- 如果是[证明没有人修改数据], 执行更新, set 数据更新 , version = version+ 1
- 如果 version 不对[证明有人已经修改了],我们现在的其他记录就是失效数据!就更新失败
实现步骤:
实体类添加乐观锁字段并添加@Version注解
1
2
private Integer version;数据库也添加version字段
1
alter table user add version int default 1 ; # int 类型 乐观锁字段
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,
- 仅支持 updateById(id) 与 update(entity, wrapper) 方法
添加版本号更新插件
1
2
3
4
5
6
7
8
9
10
11
12//mybatis-plus插件加入到ioc容器中
public MybatisPlusInterceptor plusInterceptor(){
//mybatis-plus的插件集合【加入到这个插件集合中即可:分页插件,乐观锁插件..】
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//加入分页插件
//mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//加入乐观锁插件 mybatis-plus会在更新的时候,每次帮我们对比版本号字段和增加版本号+1
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}之后正常使用更新即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//演示乐观锁生效场景
public void testQuick7(){
//步骤1: 先查询,在更新 获取version数据
//同时查询两条,但是version唯一,最后更新的失败
User user = userMapper.selectById(5); //version=1
User user1 = userMapper.selectById(5);//version=1
user.setAge(20); //age=20
user1.setAge(30); //age=30
userMapper.updateById(user);//修改成功 version=2
//乐观锁生效,失败!
userMapper.updateById(user1);//2!=1 修改失败
//age=20
}
防全表更新和删除实现
添加防止全表更新和删除拦截器
1
2
3
4
5
6
7
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//防止全表更新和删除插件【防止恶意操作】
mybatisPlusInterceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}测试全部更新或全部删除
1
2
3
4
5
6
7
8
9
public void testQuick8(){
User user = new User();
user.setName("custom_name");
user.setEmail("xxx@mail.com");
//Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
//全局更新,报错
userService.saveOrUpdate(user,null);
}
MyBatis-Plus代码生成器(MyBatisX插件)
确保你的idea安装了MyBatisX的插件
点击idea右侧数据库,连接数据库
右键表,点击
MyBatisX-Generator
module path
:选择项目如 D:/code/springboot-projectbase package
:项目根包如 cn.xnjrelative package
:存实体类的包如 pojoignore file prefix/sufix
:忽略字段的前/后缀ignore table prefix/sufix
:忽略表的前前/后缀如 t_
点击next
annotation注解
: MyBatis-Plus3options
: Lombok Modeltemplate
: mybatis-plus3
点击finish即可生成
注意实体类属性中version,deleted等字段还需要自己添加相应需求注解
MyBatisX代码生成器:在mapper里写方法名时会发现mybatisx的提示,按其提示写,然后alter+enter将会帮我们自动生成代码