MyBatisPlus
# MyBatisPlus
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
官方地址: http://mp.baomidou.com 代码发布地址: Github: https://github.com/baomidou/mybatis-plus Gitee: https://gitee.com/baomidou/mybatis-plus 文档发布地址: https://baomidou.com/pages/24112f
# 1.入门案例
# 1.1创建数据库及表
CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `mybatis_plus`;
CREATE TABLE `user` (
`id` BIGINT(20) NOT NULL COMMENT '主键ID',
`name` VARCHAR(30) DEFAULT NULL COMMENT '姓名',
`age` INT(11) DEFAULT NULL COMMENT '年龄',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
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
4
5
6
7
8
9
10
11
12
13
14
15
16
# 1.2创建Spring Boot工程
# a>初始化工程
使用 Spring Initializr 快速初始化一个 Spring Boot 工程
# b>引入依赖
<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.1</version>
</dependency>
<!--lombok用于简化实体类开发-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
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.3编写入门案例
# a>配置application.yml
spring:
# 配置数据源信息
datasource:
# 配置数据源类型
type: com.zaxxer.hikari.HikariDataSource
# 配置连接数据库信息
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
password: root
2
3
4
5
6
7
8
9
10
注意:
1、驱动类driver-class-name spring boot 2.0(内置jdbc5驱动),驱动类使用:
driver-class-name: com.mysql.jdbc.Driver spring boot 2.1及以上(内置jdbc8驱动),驱动类使用: driver-class-name: com.mysql.cj.jdbc.Driver 否则运行测试用例的时候会有 WARN 信息 2、连接地址url MySQL5.7版本的url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false MySQL8.0版本的url: jdbc:mysql://localhost:3306/mybatis_plus? serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false 否则运行测试用例报告如下错误: java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more
# b>添加实体
@Data //lombok注解
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
2
3
4
5
6
7
8
9
# c>添加mapper
com.ep.mybatis_plus.mapper.UserMapper
BaseMapper的泛型决定了操作哪张表
BaseMapper是MyBatis-Plus提供的模板mapper,其中包含了基本的CRUD方法,泛型为操作的实体类型
@Repository
public interface UserMapper extends BaseMapper<User> {
}
2
3
4
# d>启动类
在Spring Boot启动类中添加@MapperScan注解,扫描mapper包
@SpringBootApplication
// 扫描mapper所在的包
@MapperScan("com.ep.mybatis_plus.mapper")
public class MybatisPlusApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisPlusApplication.class, args);
}
}
2
3
4
5
6
7
8
9
# e>测试
@SpringBootTest
public class MybatisPlustest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelectList(){
//selectList()根据MP内置的条件构造器查询一个list集合,null表示没有条件,即查询所有
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
IDEA在 userMapper 处报错,因为找不到注入的对象,因为类是动态创建的,但是程序可以正确 的执行。 为了避免报错,可以在mapper接口上添加 @Repository 注解
# f>添加日志
在application.yml中配置日志输出
# 配置mybatis日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2
3
4
# 2.基本的CRUD
# 2.1BaseMapper
MyBatis-Plus中的基本CRUD在内置的BaseMapper中都已得到了实现,我们可以直接使用,接口如下:
/**
* Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
* <p>这个 Mapper 支持 id 泛型</p>
*
* @author hubin
* @since 2016-01-23
*/
public interface BaseMapper<T> extends Mapper<T> {
/**
* 插入一条记录
*
* @param entity 实体对象
*/
int insert(T entity);
/**
* 根据 ID 删除
*
* @param id 主键ID
*/
int deleteById(Serializable id);
/**
* 根据实体(ID)删除
*
* @param entity 实体对象
* @since 3.4.4
*/
int deleteById(T entity);
/**
* 根据 columnMap 条件,删除记录
*
* @param columnMap 表字段 map 对象
*/
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 entity 条件,删除记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 删除(根据ID或实体 批量删除)
*
* @param idList 主键ID列表或实体列表(不能为 null 以及 empty)
*/
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<?> idList);
/**
* 根据 ID 修改
*
* @param entity 实体对象
*/
int updateById(@Param(Constants.ENTITY) T entity);
/**
* 根据 whereEntity 条件,更新记录
*
* @param entity 实体对象 (set 条件值,可以为 null)
* @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
T selectById(Serializable id);
/**
* 查询(根据ID 批量查询)
*
* @param idList 主键ID列表(不能为 null 以及 empty)
*/
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
/**
* 查询(根据 columnMap 条件)
*
* @param columnMap 表字段 map 对象
*/
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
/**
* 根据 entity 条件,查询一条记录
* <p>查询一条记录,例如 qw.last("limit 1") 限制取一条记录, 注意:多条数据会报异常</p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
default T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper) {
List<T> ts = this.selectList(queryWrapper);
if (CollectionUtils.isNotEmpty(ts)) {
if (ts.size() != 1) {
throw ExceptionUtils.mpe("One record is expected, but the query result is multiple records");
}
return ts.get(0);
}
return null;
}
/**
* 根据 Wrapper 条件,判断是否存在记录
*
* @param queryWrapper 实体对象封装操作类
* @return
*/
default boolean exists(Wrapper<T> queryWrapper) {
Long count = this.selectCount(queryWrapper);
return null != count && count > 0;
}
/**
* 根据 Wrapper 条件,查询总记录数
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
Long selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录
* <p>注意: 只返回第一个字段的值</p>
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 entity 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件(可以为 RowBounds.DEFAULT)
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
<P extends IPage<T>> P selectPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
/**
* 根据 Wrapper 条件,查询全部记录(并翻页)
*
* @param page 分页查询条件
* @param queryWrapper 实体对象封装操作类
*/
<P extends IPage<Map<String, Object>>> P selectMapsPage(P page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# 2.2插入
/*
* 测试插入功能
* */
@Test
public void testInsert() {
User user = new User(null, "张三", 23, "zhangsan@atguigu.com");
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
userMapper.insert(user);
System.out.println("id自动获取:"+user.getId());
}
2
3
4
5
6
7
8
9
10
最终执行的结果,所获取的id为1549303562874556417 这是因为MyBatis-Plus在实现插入数据时,会默认基于雪花算法的策略生成id
# 2.3删除
# 2.3.1通过id删除记录
/*
* 通过id删除用户信息
* */
@Test
public void testDeleteById(){
// DELETE FROM user WHERE id=?
int result = userMapper.deleteById(1549303562874556417L);
System.out.println(result);
}
2
3
4
5
6
7
8
9
# 2.3.2通过id批量删除记录
/*
* 通过id批量删除记录
* */
@Test
public void testDeleteByIDS() {
// DELETE FROM user WHERE id IN ( ? , ? , ? )
List<Long> ids = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(ids);
System.out.println(result);
}
2
3
4
5
6
7
8
9
10
11
# 2.3.3通过map条件删除记录
/*
* 通过map条件删除记录
* */
@Test
public void testDeleteByMap() {
Map<String,Object> map = new HashMap<>();
map.put("name","张三");
map.put("age",23);
// DELETE FROM user WHERE name = ? AND age = ?
int result = userMapper.deleteByMap(map);
System.out.println(result);
}
2
3
4
5
6
7
8
9
10
11
12
13
# 2.4修改
/*
* 修改用户信息
* */
@Test
public void testUpdate(){
User user = new User();
user.setId(4L);
user.setAge(20);
user.setName("lisi");
// UPDATE user SET name=?, age=? WHERE id=?
int result = userMapper.updateById(user);
System.out.println(result);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.5查询
# 2.5.1根据id查询用户信息
/*
* 根据id查询用户信息
* */
@Test
public void testSelectById(){
// SELECT id,name,age,email FROM user WHERE id=?
User user = userMapper.selectById(1L);
System.out.println(user);
}
2
3
4
5
6
7
8
9
10
# 2.5.2根据多个id查询多个用户信息
/*
* 根据多个id查询多个用户信息
* */
@Test
public void testSelectByIds(){
List<Long> list = Arrays.asList(1L, 2L, 3L);
// SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
List<User> users = userMapper.selectBatchIds(list);
System.out.println(users);
}
2
3
4
5
6
7
8
9
10
# 2.5.3通过map条件查询用户信息
/*
* 通过map条件查询用户信息
* */
@Test
public void testSelectByMap(){
Map<String, Object> map = new HashMap<>();
map.put("name","Jack");
map.put("age",20);
// SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
List<User> users = userMapper.selectByMap(map);
System.out.println(users);
}
2
3
4
5
6
7
8
9
10
11
12
# 2.5.4查询所有数据
@Test
public void testSelectList(){
//查询所有用户信息
//SELECT id,name,age,email FROM user
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
2
3
4
5
6
7
通过观察BaseMapper中的方法,大多方法中都有Wrapper类型的形参,此为条件构造器,可针对于SQL语句设置不同的条件,若没有条件,则可以为该形参赋值null,即查询(删除/修改)所有数据
# 2.6自定义功能
因为配置有扫描的包,所以不需要再配置映射文件
application.yml
mybatis-plus:
configuration:
# 配置mybatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置类型别名
type-aliases-package: com.ep.mybatis_plus.pojo
2
3
4
5
6
resources/mapper/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.ep.mybatis_plus.mapper.UserMapper">
<!--User selectUserById(@Param("id")Integer id);-->
<select id="selectUserById" resultType="user">
select * from user where id = #{id}
</select>
</mapper>
2
3
4
5
6
7
8
9
10
11
# 3.通用Service
说明: 通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用 get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,泛型 T 为任意实体对象 建议如果存在自定义通用 Service 方法的可能,请创建自己的 IBaseService 继承Mybatis-Plus 提供的基类 官网地址:https://baomidou.com/pages/49cc81/#service-crud-%E6%8E%A5%E5%8F%
# 3.1 IService
MyBatis-Plus中有一个接口 IService和其实现类 ServiceImpl,封装了常见的业务层逻辑 详情查看源码IService和ServiceImpl
/**
* 顶级 Service
*
* @author hubin
* @since 2018-06-23
*/
public interface IService<T> {
/**
* 默认批次提交数量
*/
int DEFAULT_BATCH_SIZE = 1000;
/**
* 插入一条记录(选择字段,策略插入)
*
* @param entity 实体对象
*/
default boolean save(T entity) {
return SqlHelper.retBool(getBaseMapper().insert(entity));
}
/**
* 插入(批量)
*
* @param entityList 实体对象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean saveBatch(Collection<T> entityList) {
return saveBatch(entityList, DEFAULT_BATCH_SIZE);
}
/**
* 插入(批量)
*
* @param entityList 实体对象集合
* @param batchSize 插入批次数量
*/
boolean saveBatch(Collection<T> entityList, int batchSize);
/**
* 批量修改插入
*
* @param entityList 实体对象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean saveOrUpdateBatch(Collection<T> entityList) {
return saveOrUpdateBatch(entityList, DEFAULT_BATCH_SIZE);
}
/**
* 批量修改插入
*
* @param entityList 实体对象集合
* @param batchSize 每次的数量
*/
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
/**
* 根据 ID 删除
*
* @param id 主键ID
*/
default boolean removeById(Serializable id) {
return SqlHelper.retBool(getBaseMapper().deleteById(id));
}
/**
* 根据 ID 删除
*
* @param id 主键(类型必须与实体类型字段保持一致)
* @param useFill 是否启用填充(为true的情况,会将入参转换实体进行delete删除)
* @return 删除结果
* @since 3.5.0
*/
default boolean removeById(Serializable id, boolean useFill) {
throw new UnsupportedOperationException("不支持的方法!");
}
/**
* 根据实体(ID)删除
*
* @param entity 实体
* @since 3.4.4
*/
default boolean removeById(T entity) {
return SqlHelper.retBool(getBaseMapper().deleteById(entity));
}
/**
* 根据 columnMap 条件,删除记录
*
* @param columnMap 表字段 map 对象
*/
default boolean removeByMap(Map<String, Object> columnMap) {
Assert.notEmpty(columnMap, "error: columnMap must not be empty");
return SqlHelper.retBool(getBaseMapper().deleteByMap(columnMap));
}
/**
* 根据 entity 条件,删除记录
*
* @param queryWrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default boolean remove(Wrapper<T> queryWrapper) {
return SqlHelper.retBool(getBaseMapper().delete(queryWrapper));
}
/**
* 删除(根据ID 批量删除)
*
* @param list 主键ID或实体列表
*/
default boolean removeByIds(Collection<?> list) {
if (CollectionUtils.isEmpty(list)) {
return false;
}
return SqlHelper.retBool(getBaseMapper().deleteBatchIds(list));
}
/**
* 批量删除
*
* @param list 主键ID或实体列表
* @param useFill 是否填充(为true的情况,会将入参转换实体进行delete删除)
* @return 删除结果
* @since 3.5.0
*/
@Transactional(rollbackFor = Exception.class)
default boolean removeByIds(Collection<?> list, boolean useFill) {
if (CollectionUtils.isEmpty(list)) {
return false;
}
if (useFill) {
return removeBatchByIds(list, true);
}
return SqlHelper.retBool(getBaseMapper().deleteBatchIds(list));
}
/**
* 批量删除(jdbc批量提交)
*
* @param list 主键ID或实体列表(主键ID类型必须与实体类型字段保持一致)
* @return 删除结果
* @since 3.5.0
*/
@Transactional(rollbackFor = Exception.class)
default boolean removeBatchByIds(Collection<?> list) {
return removeBatchByIds(list, DEFAULT_BATCH_SIZE);
}
/**
* 批量删除(jdbc批量提交)
*
* @param list 主键ID或实体列表(主键ID类型必须与实体类型字段保持一致)
* @param useFill 是否启用填充(为true的情况,会将入参转换实体进行delete删除)
* @return 删除结果
* @since 3.5.0
*/
@Transactional(rollbackFor = Exception.class)
default boolean removeBatchByIds(Collection<?> list, boolean useFill) {
return removeBatchByIds(list, DEFAULT_BATCH_SIZE, useFill);
}
/**
* 批量删除(jdbc批量提交)
*
* @param list 主键ID或实体列表
* @param batchSize 批次大小
* @return 删除结果
* @since 3.5.0
*/
default boolean removeBatchByIds(Collection<?> list, int batchSize) {
throw new UnsupportedOperationException("不支持的方法!");
}
/**
* 批量删除(jdbc批量提交)
*
* @param list 主键ID或实体列表
* @param batchSize 批次大小
* @param useFill 是否启用填充(为true的情况,会将入参转换实体进行delete删除)
* @return 删除结果
* @since 3.5.0
*/
default boolean removeBatchByIds(Collection<?> list, int batchSize, boolean useFill) {
throw new UnsupportedOperationException("不支持的方法!");
}
/**
* 根据 ID 选择修改
*
* @param entity 实体对象
*/
default boolean updateById(T entity) {
return SqlHelper.retBool(getBaseMapper().updateById(entity));
}
/**
* 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
*
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/
default boolean update(Wrapper<T> updateWrapper) {
return update(null, updateWrapper);
}
/**
* 根据 whereEntity 条件,更新记录
*
* @param entity 实体对象
* @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
*/
default boolean update(T entity, Wrapper<T> updateWrapper) {
return SqlHelper.retBool(getBaseMapper().update(entity, updateWrapper));
}
/**
* 根据ID 批量更新
*
* @param entityList 实体对象集合
*/
@Transactional(rollbackFor = Exception.class)
default boolean updateBatchById(Collection<T> entityList) {
return updateBatchById(entityList, DEFAULT_BATCH_SIZE);
}
/**
* 根据ID 批量更新
*
* @param entityList 实体对象集合
* @param batchSize 更新批次数量
*/
boolean updateBatchById(Collection<T> entityList, int batchSize);
/**
* TableId 注解存在更新记录,否插入一条记录
*
* @param entity 实体对象
*/
boolean saveOrUpdate(T entity);
/**
* 根据 ID 查询
*
* @param id 主键ID
*/
default T getById(Serializable id) {
return getBaseMapper().selectById(id);
}
/**
* 查询(根据ID 批量查询)
*
* @param idList 主键ID列表
*/
default List<T> listByIds(Collection<? extends Serializable> idList) {
return getBaseMapper().selectBatchIds(idList);
}
/**
* 查询(根据 columnMap 条件)
*
* @param columnMap 表字段 map 对象
*/
default List<T> listByMap(Map<String, Object> columnMap) {
return getBaseMapper().selectByMap(columnMap);
}
/**
* 根据 Wrapper,查询一条记录 <br/>
* <p>结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")</p>
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default T getOne(Wrapper<T> queryWrapper) {
return getOne(queryWrapper, true);
}
/**
* 根据 Wrapper,查询一条记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
* @param throwEx 有多个 result 是否抛出异常
*/
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
/**
* 根据 Wrapper,查询一条记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
Map<String, Object> getMap(Wrapper<T> queryWrapper);
/**
* 根据 Wrapper,查询一条记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
* @param mapper 转换函数
*/
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
/**
* 查询总记录数
*
* @see Wrappers#emptyWrapper()
*/
default long count() {
return count(Wrappers.emptyWrapper());
}
/**
* 根据 Wrapper 条件,查询总记录数
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default long count(Wrapper<T> queryWrapper) {
return SqlHelper.retCount(getBaseMapper().selectCount(queryWrapper));
}
/**
* 查询列表
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default List<T> list(Wrapper<T> queryWrapper) {
return getBaseMapper().selectList(queryWrapper);
}
/**
* 查询所有
*
* @see Wrappers#emptyWrapper()
*/
default List<T> list() {
return list(Wrappers.emptyWrapper());
}
/**
* 翻页查询
*
* @param page 翻页对象
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
return getBaseMapper().selectPage(page, queryWrapper);
}
/**
* 无条件翻页查询
*
* @param page 翻页对象
* @see Wrappers#emptyWrapper()
*/
default <E extends IPage<T>> E page(E page) {
return page(page, Wrappers.emptyWrapper());
}
/**
* 查询列表
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper) {
return getBaseMapper().selectMaps(queryWrapper);
}
/**
* 查询所有列表
*
* @see Wrappers#emptyWrapper()
*/
default List<Map<String, Object>> listMaps() {
return listMaps(Wrappers.emptyWrapper());
}
/**
* 查询全部记录
*/
default List<Object> listObjs() {
return listObjs(Function.identity());
}
/**
* 查询全部记录
*
* @param mapper 转换函数
*/
default <V> List<V> listObjs(Function<? super Object, V> mapper) {
return listObjs(Wrappers.emptyWrapper(), mapper);
}
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default List<Object> listObjs(Wrapper<T> queryWrapper) {
return listObjs(queryWrapper, Function.identity());
}
/**
* 根据 Wrapper 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
* @param mapper 转换函数
*/
default <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper) {
return getBaseMapper().selectObjs(queryWrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList());
}
/**
* 翻页查询
*
* @param page 翻页对象
* @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper}
*/
default <E extends IPage<Map<String, Object>>> E pageMaps(E page, Wrapper<T> queryWrapper) {
return getBaseMapper().selectMapsPage(page, queryWrapper);
}
/**
* 无条件翻页查询
*
* @param page 翻页对象
* @see Wrappers#emptyWrapper()
*/
default <E extends IPage<Map<String, Object>>> E pageMaps(E page) {
return pageMaps(page, Wrappers.emptyWrapper());
}
/**
* 获取对应 entity 的 BaseMapper
*
* @return BaseMapper
*/
BaseMapper<T> getBaseMapper();
/**
* 获取 entity 的 class
*
* @return {@link Class<T>}
*/
Class<T> getEntityClass();
/**
* 以下的方法使用介绍:
*
* 一. 名称介绍
* 1. 方法名带有 query 的为对数据的查询操作, 方法名带有 update 的为对数据的修改操作
* 2. 方法名带有 lambda 的为内部方法入参 column 支持函数式的
* 二. 支持介绍
*
* 1. 方法名带有 query 的支持以 {@link ChainQuery} 内部的方法名结尾进行数据查询操作
* 2. 方法名带有 update 的支持以 {@link ChainUpdate} 内部的方法名为结尾进行数据修改操作
*
* 三. 使用示例,只用不带 lambda 的方法各展示一个例子,其他类推
* 1. 根据条件获取一条数据: `query().eq("column", value).one()`
* 2. 根据条件删除一条数据: `update().eq("column", value).remove()`
*
*/
/**
* 链式查询 普通
*
* @return QueryWrapper 的包装类
*/
default QueryChainWrapper<T> query() {
return ChainWrappers.queryChain(getBaseMapper());
}
/**
* 链式查询 lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaQueryWrapper 的包装类
*/
default LambdaQueryChainWrapper<T> lambdaQuery() {
return ChainWrappers.lambdaQueryChain(getBaseMapper());
}
/**
* 链式查询 lambda 式
* kotlin 使用
*
* @return KtQueryWrapper 的包装类
*/
default KtQueryChainWrapper<T> ktQuery() {
return ChainWrappers.ktQueryChain(getBaseMapper(), getEntityClass());
}
/**
* 链式查询 lambda 式
* kotlin 使用
*
* @return KtQueryWrapper 的包装类
*/
default KtUpdateChainWrapper<T> ktUpdate() {
return ChainWrappers.ktUpdateChain(getBaseMapper(), getEntityClass());
}
/**
* 链式更改 普通
*
* @return UpdateWrapper 的包装类
*/
default UpdateChainWrapper<T> update() {
return ChainWrappers.updateChain(getBaseMapper());
}
/**
* 链式更改 lambda 式
* <p>注意:不支持 Kotlin </p>
*
* @return LambdaUpdateWrapper 的包装类
*/
default LambdaUpdateChainWrapper<T> lambdaUpdate() {
return ChainWrappers.lambdaUpdateChain(getBaseMapper());
}
/**
* <p>
* 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
* 此次修改主要是减少了此项业务代码的代码量(存在性验证之后的saveOrUpdate操作)
* </p>
*
* @param entity 实体对象
*/
default boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) {
return update(entity, updateWrapper) || saveOrUpdate(entity);
}
}
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
# 3.2 创建Service接口和实现类
/**
* UserService继承IService模板提供的基础功能
*/
public interface UserService extends IService<User> {
}
2
3
4
5
/**
* ServiceImpl实现了IService,提供了IService中基础功能的实现
* 若ServiceImpl无法满足业务需求,则可以使用自定的UserService定义方法,并在实现类中实现
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
2
3
4
5
6
7
# 3.2.1测试查询记录数
@SpringBootTest
public class MyBatisPlusServiceTest {
@Autowired
private UserService userService;
@Test
public void testSelectCount(){
// SELECT COUNT( * ) FROM user
long count = userService.count();
System.out.println("总数为:"+count);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 3.2.2批量添加
// 批量添加
@Test
public void testsaveMoreUser(){
List<User> list = new ArrayList<>();
for (int i = 0; i <10 ; i++) {
User user = new User();
user.setName("dep"+i);
user.setAge(i);
user.setEmail("123@qq.com");
list.add(user);
}
// INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
boolean b = userService.saveBatch(list);
System.out.println(b);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4.常用注解
# 4.1@TableName
经过以上的测试,在使用MyBatis-Plus实现基本的CRUD时,我们并没有指定要操作的表,只是在Mapper接口继承BaseMapper时,设置了泛型User,而操作的表为user表 由此得出结论,MyBatis-Plus在确定操作的表时,由BaseMapper的泛型决定,即实体类型决定,且默认操作的表名和实体类型的类名一致
若实体类类型的类名和要操作的表的表名不一致,会出现什么问题? 我们将表user更名为t_user,测试查询功能 程序抛出异常,Table 'mybatis_plus.user' doesn't exist,因为现在的表名为t_user,而默认操作的表名和实体类型的类名一致,即user表
在实体类类型上添加@TableName("t_user"),标识实体类对应的表,即可成功执行SQL语句
方式一:
方式二:
在开发的过程中,我们经常遇到以上的问题,即实体类所对应的表都有固定的前缀,例如t_或tbl_ 此时,可以使用MyBatis-Plus提供的全局配置,为实体类所对应的表名设置默认的前缀,那么就不需要在每个实体类上通过@TableName标识实体类对应的表
application.xml
mybatis-plus:
configuration:
# 配置mybatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置类型别名
type-aliases-package: com.ep.mybatis_plus.pojo
# 设置mybatis-plus的全局配置
global-config:
db-config:
table-prefix: t_
2
3
4
5
6
7
8
9
10
# 4.2@TableId
经过以上的测试,MyBatis-Plus在实现CRUD时,会默认将id作为主键列,并在插入数据时,默认基于雪花算法的策略生成id
若实体类和表中表示主键的不是id,而是其他字段,例如uid,MyBatis-Plus会自动识别uid为主键列吗? 我们实体类中的属性id改为uid,将表中的字段id也改为uid,测试添加功能程序抛出异常,Field 'uid' doesn't have a default value,说明MyBatis-Plus没有将uid作为主键赋值
若实体类中主键对应的属性为id,而表中表示主键的字段为uid,此时若只在属性id上添加注解@TableId,则抛出异常Unknown column 'id' in 'field list',即MyBatis-Plus仍然会将id作为表的主键操作,而表中表示主键的是字段uid
在实体类中uid属性上通过@TableId将其标识为主键,即可成功执行SQL语句
public class User {
// 将属性对应的字段指定为主键
//@TableId注解的value属性,指定表中的主键字段
// @TableId
@TableId(value = "uid")
private Long id;
private String name;
private Integer age;
private String email;
}
2
3
4
5
6
7
8
9
10
TableId的type属性
type属性用来定义主键策略
常用的主键策略:
值 | 描述 |
---|---|
IdType.ASSIGN_ID(默认) | 基于雪花算法的策略生成数据id,与数据库id是否设置自增无关 |
IdType.AUTO | 使用数据库的自增策略,注意,该类型请确保数据库设置了id自增,否则无效 |
方式一:
方式二:全局配置
application.yml
mybatis-plus:
configuration:
# 配置mybatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置类型别名
type-aliases-package: com.ep.mybatis_plus.pojo
# 设置mybatis-plus的全局配置
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
2
3
4
5
6
7
8
9
10
11
12
13
# 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
经过以上的测试,我们可以发现,MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和表中的字段名一致 如果实体类中的属性名和字段名不一致的情况,会出现什么问题呢?
若实体类中的属性使用的是驼峰命名风格,而表中的字段使用的是下划线命名风格 例如实体类属性userName,表中字段user_name 此时MyBatis-Plus会自动将下划线命名风格转化为驼峰命名风格 相当于在MyBatis中配置
若实体类中的属性和表中的字段不满足上述情况 例如实体类属性name,表中字段username 此时需要在实体类属性上使用@TableField("username")设置属性所对应的字段名
@Data
@NoArgsConstructor
@AllArgsConstructor
//为实体类型表名对应的数据库表名
//@TableName("t_user")
public class User {
// 将属性对应的字段指定为主键
//@TableId(value = "uid",type = IdType.AUTO)
// 类型采用全局配置
@TableId("uid")
private Long id;
// 指定属性所对应的字段名
@TableField("user_name")
private String name;
private Integer age;
private String email;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 4.5@TableLogic
a>逻辑删除 物理删除:真实删除,将对应数据从数据库中删除,之后查询不到此条被删除的数据 逻辑删除:假删除,将对应数据中代表是否被删除字段的状态修改为“被删除状态”,之后在数据库 中仍旧能看到此条数据记录 使用场景:可以进行数据恢复
b>实现逻辑删除
step1:数据库中创建逻辑删除状态列,设置默认值为0
step2:实体类中添加逻辑删除属性
public class User {
//@TableId(value = "uid",type = IdType.AUTO)
// 类型采用全局配置
@TableId("uid")
private Long id;
// 指定属性所对应的字段名
@TableField("user_name")
private String name;
private Integer age;
private String email;
// 设置该字段为逻辑删除属性
@TableLogic
private Integer isDeleted;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
step3:测试 测试删除功能,真正执行的是修改 UPDATE t_user SET is_deleted=1 WHERE uid=? AND is_deleted=0 测试查询功能,被逻辑删除的数据默认不会被查询 SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0
# 5.条件构造器和常用接口
# 5.1wapper介绍
- Wrapper : 条件构造抽象类,最顶端父类
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
- QueryWrapper : 查询条件封装
- UpdateWrapper : Update 条件封装
- AbstractLambdaWrapper : 使用Lambda 语法
- LambdaQueryWrapper :用于Lambda语法使用的查询Wrapper
- LambdaUpdateWrapper : Lambda 更新封装Wrapper
- AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
# 5.2QueryWrapper
可以实现修改和查询
# a>例1:组装查询条件
@Test
public void test01(){
////查询用户名包含a,年龄在20到30之间,并且邮箱不为null的用户信息
// ==> Preparing: SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (user_name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
//==> Parameters: %a%(String), 20(Integer), 30(Integer)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("user_name","a")
.between("age",20,30)
.isNotNull("email");
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
11
12
# b>例2:组装排序条件
@Test
public void test02(){
//按年龄降序查询用户,如果年龄相同则按id升序排列
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("uid");
// ==> Preparing: SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 ORDER BY age DESC,uid ASC
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
2
3
4
5
6
7
8
9
10
# c>例3:组装删除条件
@Test
public void test03(){
//删除email为空的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
// UPDATE t_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)
int delete = userMapper.delete(queryWrapper);
System.out.println(delete);
}
2
3
4
5
6
7
8
9
# d>例4:条件的优先级
@Test void test04(){
//将(年龄大于20并且用户名中包含有a)或邮箱为null的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.ge("age",20)
.like("user_name","a")
.or()
.isNull("email");
User user = new User();
user.setAge(18);
user.setEmail("user@qq.com");
// ==> Preparing: UPDATE t_user SET age=?, email=? WHERE is_deleted=0 AND (age >= ? AND user_name LIKE ? OR email IS NULL)
//==> Parameters: 18(Integer), user@qq.com(String), 20(Integer), %a%(String)
int update = userMapper.update(user, queryWrapper);
System.out.println(update);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# lambda表达式的优先级高
@Test
public void test05(){
//将用户名中包含有a并且(年龄大于20或邮箱为null)的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//lambda表达式内的逻辑优先运算
queryWrapper.like("user_name","a")
.and(i->i.ge("age",20).isNull("email"));
User user = new User();
user.setAge(18);
user.setEmail("update@qq.com");
// ==> Preparing: UPDATE t_user SET age=?, email=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age >= ? AND email IS NULL))
//==> Parameters: 18(Integer), update@qq.com(String), %a%(String), 20(Integer)
int update = userMapper.update(user, queryWrapper);
System.out.println(update);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# e>例5:组装select子句
@Test
public void test06(){
//查询用户信息的username和age字段
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("user_name","age");
//SELECT user_name,age FROM t_user WHERE is_deleted=0
List<User> users = userMapper.selectList(queryWrapper);
System.out.println(users);
}
2
3
4
5
6
7
8
9
# f>例6:实现子查询
@Test
public void test07(){
//查询id小于等于3的用户信息
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("uid","select uid from t_user where uid <= 3");
// SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (uid IN (select uid from t_user where uid <= 3))
List<User> users = userMapper.selectList(queryWrapper);
System.out.println(users);
}
2
3
4
5
6
7
8
9
# 5.3UpdateWrapper
@Test
public void test08() {
//将(年龄大于20或邮箱为null)并且用户名中包含有a的用户信息修改
//组装set子句以及修改条件
UpdateWrapper<User> userUpdateWrapper = new UpdateWrapper<>();
//lambda表达式内的逻辑优先运算
userUpdateWrapper.set("user_name","小黑")
.set("age",30)
.like("user_name","a")
.and(i->i.ge("age",20).isNull("email"));
// ==> Preparing: UPDATE t_user SET user_name=?,age=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age >= ? AND email IS NULL))
//==> Parameters: 小黑(String), 30(Integer), %a%(String), 20(Integer)
int update = userMapper.update(null, userUpdateWrapper);
System.out.println(update);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 5.4 Condition
在真正开发的过程中,组装条件是常见的功能,而这些条件数据来源于用户输入,是可选的,因此我们在组装这些条件时,必须先判断用户是否选择了这些条件,若选择则需要组装该条件,若没有选择则一定不能组装,以免影响SQL执行的结果
思路一:(不推荐)
@Test
public void test09(){
//定义查询条件,有可能为null(用户未输入或未选择)
String username = null;
Integer ageBegin = null;
Integer ageEnd = 24;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//StringUtils.isNotBlank()判断某字符串是否不为空且长度不为0且不由空白符(whitespace)构成
if(StringUtils.isNotBlank(username)){
queryWrapper.like("user_name",username);
}
if (ageBegin != null){
queryWrapper.ge("age",ageBegin);
}
if (ageEnd != null){
queryWrapper.le("age",ageEnd);
}
// SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age <= ?)
List<User> users = userMapper.selectList(queryWrapper);
System.out.println(users);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
思路二:利用condition条件
上面的实现方案没有问题,但是代码比较复杂,我们可以使用带condition参数的重载方法构建查询条件,简化代码的编写
@Test
public void test10(){
//定义查询条件,有可能为null(用户未输入或未选择)
String username = null;
Integer ageBegin = null;
Integer ageEnd = 24;
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username),"user_name",username)
.ge(ageBegin != null, "age",ageBegin)
.le(ageEnd != null,"age",ageEnd);
// SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age <= ?)
List<User> users = userMapper.selectList(queryWrapper);
System.out.println(users);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 5.5LambdaQueryWrapper
防止字段输入错误。使用User::getName 来代替数据库中表的字段
@Test
public void test11(){
//定义查询条件,有可能为null(用户未输入或未选择)
String username = null;
Integer ageBegin = null;
Integer ageEnd = 24;
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username),User::getName,username)
.ge(ageBegin != null, User::getAge,ageBegin)
.le(ageEnd != null, User::getAge,ageEnd);
// SELECT uid AS id,user_name AS name,age,email,is_deleted FROM t_user WHERE is_deleted=0 AND (age <= ?)
List<User> users = userMapper.selectList(queryWrapper);
System.out.println(users);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 5.6LambdaUpdateWrapper
@Test
public void test12() {
//将(年龄大于20或邮箱为null)并且用户名中包含有a的用户信息修改
//组装set子句以及修改条件
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.set(User::getName,"小黑")
.set(User::getAge,30)
.like(User::getName,"a")
.and(i->i.ge(User::getAge,20).isNull(User::getEmail));
// ==> Preparing: UPDATE t_user SET user_name=?,age=? WHERE is_deleted=0 AND (user_name LIKE ? AND (age >= ? AND email IS NULL))
//==> Parameters: 小黑(String), 30(Integer), %a%(String), 20(Integer)
int update = userMapper.update(null, lambdaUpdateWrapper);
System.out.println(update);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 6.插件
# 6.1分页插件
MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能
a>添加配置类
config/MybatisPlusConfig
@Configuration
@MapperScan("com.ep.mybatis_plus.mapper")//可以将springboot启动类中的注解移到此处
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
2
3
4
5
6
7
8
9
10
11
b>测试
@Test
public void test01(){
Page<User> page = new Page<>(1,5);
userMapper.selectPage(page,null);
System.out.println("数据:"+page.getRecords());
System.out.println("当前页:"+page.getCurrent());
System.out.println("每页显示的条数:"+page.getSize());
System.out.println("总记录数:"+page.getTotal());
System.out.println("总页数:"+page.getPages());
System.out.println("是否有上一页:"+page.hasPrevious());
System.out.println("是否有下一页:"+page.hasNext());
}
2
3
4
5
6
7
8
9
10
11
12
数据:[User(id=1, name=Jone, age=18, email=test1@baomidou.com, isDeleted=0), User(id=2, name=Jack, age=18, email=user@qq.com, isDeleted=0), User(id=3, name=Tom, age=28, email=test3@baomidou.com, isDeleted=0), User(id=4, name=lisi, age=20, email=test4@baomidou.com, isDeleted=0), User(id=5, name=Billie, age=24, email=test5@baomidou.com, isDeleted=0)] 当前页:1 每页显示的条数:5 总记录数:7 总页数:2 是否有上一页:false 是否有下一页:true
# 6.2xml自定义分页
a>UserMapper中定义接口方法
/***
* 自定义功能根据年龄分页
* @param page mybatis-plus提供的,必须放在第一位
* @param age
* @return
*/
Page<User> selectUserByAgeVo(Page<User> page,Integer age);
2
3
4
5
6
7
b>UserMapper.xml中编写SQL
<!--Page<User> selectUserByAgeVo(Page<User> page,Integer age);-->
<select id="selectUserByAgeVo" resultType="user">
select uid,user_name,age,email from t_user where age > #{age}
</select>
2
3
4
c>测试
@Test
public void testPage() {
Page<User> page = new Page<>(1,5);
userMapper.selectUserByAgeVo(page,20);
System.out.println(page.getRecords());
}
2
3
4
5
6
# 6.3乐观锁
一件商品,成本价是80元,售价是100元。老板先是通知小李,说你去把商品价格增加50元。小李正在玩游戏,耽搁了一个小时。正好一个小时后,老板觉得商品价格增加到150元,价格太高,可能会影响销量。又通知小王,你把商品价格降低30元。 此时,小李和小王同时操作商品后台系统。小李操作的时候,系统先取出商品价格100元;小王也在操作,取出的商品价格也是100元。小李将价格加了50元,并将100+50=150元存入了数据库;小王将商品减了30元,并将100-30=70元存入了数据库。是的,如果没有锁,小李的操作就完全被小王的覆盖了。 现在商品价格是70元,比成本价低10元。几分钟后,这个商品很快出售了1千多件商品,老板亏1万多。
b>乐观锁与悲观锁
上面的故事,如果是乐观锁,小王保存价格前,会检查下价格是否被人修改过了。如果被修改过了,则重新取出的被修改后的价格,150元,这样他会将120元存入数据库。 如果是悲观锁,小李取出数据后,小王只能等小李操作完之后,才能对价格进行操作,也会保证最终的价格是120元。
c>模拟修改冲突 数据库中增加商品表
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, '外星人笔记本', 100);
2
3
4
5
6
7
8
9
添加实体
package com.ep.mybatis_plus.pojo;
import lombok.Data;
@Data
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
2
3
4
5
6
7
8
9
10
11
添加mapper
package com.ep.mybatis_plus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ep.mybatis_plus.pojo.Product;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
2
3
4
5
6
7
8
9
测试
@Test
public void testConcurrentUpdate(){
//1、小李
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的价格:" + p1.getPrice());
//2、小王
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的价格:" + p2.getPrice());
//3、小李将价格加了50元,存入了数据库
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("小李修改结果:" + result1);
//4、小王将商品减了30元,存入了数据库
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
System.out.println("小王修改结果:" + result2);
//最后的结果
Product p3 = productMapper.selectById(1L);
//价格覆盖,最后的结果:70
System.out.println("最后的结果:" + p3.getPrice());
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
d>乐观锁实现流程
数据库中添加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
e>Mybatis-Plus实现乐观锁
修改实体
@Data
public class Product {
private Long id;
private String name;
private Integer price;
// 设置版本(乐观锁)
@Version
private Integer version;
}
2
3
4
5
6
7
8
9
添加乐观锁插件配置
config/MybatisPlusConfig
@Configuration
@MapperScan("com.ep.mybatis_plus.mapper")//可以将springboot启动类中的注解移到此处
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
测试修改冲突
小李查询商品信息: SELECT id,name,price,version FROM t_product WHERE id=? 小王查询商品信息: SELECT id,name,price,version FROM t_product WHERE id=? 小李修改商品价格,自动将version+1 UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=? Parameters: 外星人笔记本(String), 150(Integer), 1(Integer), 1(Long), 0(Integer) 小王修改商品价格,此时version已更新,条件不成立,修改失败 UPDATE t_product SET name=?, price=?, version=? WHERE id=? AND version=? Parameters: 外星人笔记本(String), 70(Integer), 1(Integer), 1(Long), 0(Integer) 最终,小王修改失败,查询价格:150 SELECT id,name,price,version FROM t_product WHERE id=?
@Test
public void testConcurrentUpdate(){
//1、小李
Product p1 = productMapper.selectById(1L);
System.out.println("小李取出的价格:" + p1.getPrice());
//2、小王
Product p2 = productMapper.selectById(1L);
System.out.println("小王取出的价格:" + p2.getPrice());
//3、小李将价格加了50元,存入了数据库
p1.setPrice(p1.getPrice() + 50);
int result1 = productMapper.updateById(p1);
System.out.println("小李修改结果:" + result1);
//4、小王将商品减了30元,存入了数据库
p2.setPrice(p2.getPrice() - 30);
int result2 = productMapper.updateById(p2);
if(result2 == 0){
// 操作失败
Product productNew = productMapper.selectById(1L);
productNew.setPrice(productNew.getPrice()-30);
int i = productMapper.updateById(productNew);
}
System.out.println("小王修改重试的结果:" + result2);
//最后的结果
Product p3 = productMapper.selectById(1L);
//价格覆盖,最后的结果:70
System.out.println("最后的结果:" + p3.getPrice());
}
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
# 7.通用枚举
表中的有些字段值是固定的,例如性别(男或女),此时我们可以使用MyBatis-Plus的通用枚举来实现
a>数据库表添加字段sex
b>创建通用枚举类型
package com.ep.mybatis_plus.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
@Getter
public enum SexEnum {
MALE(1,"男"),
FEMALE(2,"女");
@EnumValue
private Integer sex;
private String sexName;
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
c>配置扫描通用枚举
application.yml
mybatis-plus:
configuration:
# 配置mybatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 设置mybatis-plus的全局配置
global-config:
db-config:
# 配置MyBatis-Plus操作表的默认前缀
table-prefix: t_
# 配置MyBatis-Plus的主键策略
id-type: auto
# 配置类型别名对应的包
type-aliases-package: com.ep.mybatis_plus.pojo
# 配置扫描通用枚举
type-enums-package: com.ep.mybatis_plus.enums
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testEnum(){
User user = new User();
user.setName("Enum");
user.setAge(20);
//设置性别信息为枚举项,会将@EnumValue注解所标识的属性值存储到数据库
user.setSex(SexEnum.MALE);
int insert = userMapper.insert(user);
}
2
3
4
5
6
7
8
9
# 8.代码生成器
# 8.1引入依赖
<!--代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
2
3
4
5
6
7
8
9
10
11
# 8.2快速生成
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Collections;
@SpringBootTest
public class FastAutoGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://127.0.0.1:3306/mybatis_plus? characterEncoding=utf-8&userSSL=false", "root", "root")
.globalConfig(builder -> {
builder.author("dengerpu") // 设置作者
// .enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://@yjs//后端//mybatis_plus_Auto_Code"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.ep") // 设置父包名
.moduleName("mybatisplus") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://@yjs//后端//mybatis_plus_Auto_Code"));
// 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker 引擎模板,默认的是Velocity引擎模板
.execute();
}
}
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
生成后会自动打开文件
# 9.多数据源
适用于多种场景:纯粹多库、 读写分离、 一主多从、 混合模式等 目前我们就来模拟一个纯粹多库的一个场景,其他场景类似 场景说明: 我们创建两个库,分别为:mybatis_plus(以前的库不动)与mybatis_plus_1(新建),将mybatis_plus库的product表移动到mybatis_plus_1库,这样每个库一张表,通过一个测试用例分别获取用户数据与商品数据,如果获取到说明多库模拟成功
# 9.1创建数据库及表
创建数据库mybatis_plus_1和表product
CREATE DATABASE `mybatis_plus_1` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
USE `mybatis_plus_1`;
CREATE TABLE 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 product (id, NAME, price) VALUES (1, '外星人笔记本', 100);
2
3
4
5
6
7
8
9
10
11
12
删除mybatis_plus库product表
use mybatis_plus;
DROP TABLE IF EXISTS product;
2
# 9.2引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
2
3
4
5
# 9.3配置多数据源
说明:注释掉之前的数据库连接,添加新配置
application.yml
spring:
# 配置数据源信息
datasource:
dynamic:
# 设置默认的数据源或者数据源组,默认即为master
primary: master
# 严格匹配数据源,默认为false.true未匹配到指定数据源时抛异常,false使用默认数据源
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/mybatis_plus?characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 9.4创建用户service
service/UserService
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
public interface UserService extends IService<SecurityProperties.User> {
}
2
3
4
5
创建实体
entity/User
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("t_user")
public class User {
@TableId(value = "uid")
private Integer uid;
@TableField("user_name")
private String name;
private Integer age;
private String email;
private Integer isDeleted;
private Integer sex;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
entity/Product
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
@Data
public class Product {
private Integer id;
private String name;
private Integer price;
@Version
private Integer version;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mapper/UserMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ep.mybatisplus_datasource.entity.User;
import org.springframework.stereotype.Repository;
@Repository
public interface UserMapper extends BaseMapper<User> {
}
2
3
4
5
6
7
mapper/ProductMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ep.mybatisplus_datasource.entity.Product;
import org.springframework.stereotype.Repository;
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
2
3
4
5
6
7
service/UserService
import com.baomidou.mybatisplus.extension.service.IService;
import com.ep.mybatisplus_datasource.entity.User;
public interface UserService extends IService<User> {
}
2
3
4
5
6
service/ProductService
import com.baomidou.mybatisplus.extension.service.IService;
import com.ep.mybatisplus_datasource.entity.Product;
public interface ProductService extends IService<Product> {
}
2
3
4
5
6
service/Impl/UserServiceImpl
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ep.mybatisplus_datasource.entity.User;
import com.ep.mybatisplus_datasource.mapper.UserMapper;
import com.ep.mybatisplus_datasource.service.UserService;
import org.springframework.stereotype.Service;
@DS("master")
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
2
3
4
5
6
7
8
9
10
11
service/Impl/ProductServiceImpl
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ep.mybatisplus_datasource.entity.Product;
import com.ep.mybatisplus_datasource.mapper.ProductMapper;
import com.ep.mybatisplus_datasource.service.ProductService;
import org.springframework.stereotype.Service;
@DS("slave_1")
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService{
}
2
3
4
5
6
7
8
9
10
11
测试:
@Autowired
private UserService userService;
@Autowired
private ProductService productService;
@Test
public void testDataSource() {
User user = userService.getById(1);
Product product = productService.getById(1);
System.out.println(user);
System.out.println(product);
}
2
3
4
5
6
7
8
9
10
11
12
结果: 1、都能顺利获取对象,则测试成功 2、如果我们实现读写分离,将写操作方法加上主库数据源,读操作方法加上从库数据源,自动切换,是不是就能实现读写分离?
@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
# 10.MyBatisX插件
MyBatis-Plus为我们提供了强大的mapper和service模板,能够大大的提高开发效率 但是在真正开发过程中,MyBatis-Plus并不能为我们解决所有问题,例如一些复杂的SQL,多表联查,我们就需要自己去编写代码和SQL语句,我们该如何快速的解决这个问题呢,这个时候可以使用MyBatisX插件 MyBatisX一款基于 IDEA 的快速开发插件,为效率而生。
MyBatisX插件用法:https://baomidou.com/pages/ba5b24/
先安装,setting/plugins下载MyBatisX插件