MyBatis Plus
Mybatis-Plus
1 简介
1.1 Mybatis-Plus 概述
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
我们的愿景是成为 MyBatis 最好的搭档,就像 魂斗罗 中的 1P、2P,基友搭配,效率翻倍。
特性:
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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.2 代码及文档发布地址
官方地址:https://baomidou.com/pages/24112f/
代码地址:
2 集成MP
2.1 创建测试表
现有一张 User
表,其表结构如下:
id | name | age | |
---|---|---|---|
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 |
其对应的数据库 Schema 脚本如下:
DROP TABLE IF EXISTS user;
CREATE TABLE `user` (
`id` bigint NOT NULL COMMENT '主键ID',
`name` varchar(30) COLLATE utf8_bin DEFAULT NULL COMMENT '姓名',
`age` int DEFAULT NULL COMMENT '年龄',
`email` varchar(50) COLLATE utf8_bin DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin
其对应的数据库 Data 脚本如下:
DELETE FROM user;
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.2 创建 JavaBean
package com.atguigu.mp.mp01.bean;
import lombok.Data;
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
2.3 依赖配置
依赖配置
在pom.xml
中引入 spring-boot-starter
、spring-boot-starter-test
、mybatis-plus-boot-starter
和数据库连接依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
</dependencies>
使用 Mybatis-Plus 可以节省大量的代码,尽量不要同时导入 Mybatis 和 Mybatis-Plus ,以免版本冲突。因为 MyBatisPlus 的 starter 会自动导入 MyBatis 。 MySQL 依赖的版本根据自己的 MySQL 版本进行修改。
数据源配置
# DataSource Config
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/mp?useSSL=false&useUnicode=true&characterEncoding=utf-8
username: root
password: 12345678
3 入门HelloWorld
3.1 入门CRUD
- 提出问题:
假设我们已存在一张 tbl_employee 表,且已有对应的实体类 Employee ,实现 tbl_employee 表的 CRUD 操作我们需要做什么呢?
- 实现方式
-
基于MyBatis
需要编写 EmployeeMapper 接口,并手动编写 CRUD 方法。提供 EmployeeMapper.xml 映射文件,并手动编写每个方法对应的 SQL语句 -
基于 MP
只需要创建 EmployeeMapper 接口,并继承 BaseMapper 接口。这就是使用 MP 需要完成的所有操作,甚至不需要创建 SQL映射文件。
编写 Mapper 类 UserMapper.java
package com.atguigu.mp.mp01.mapper;
import com.atguigu.mp.mp01.beans.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UserMapper extends BaseMapper<User> {
}
启动类设置
package com.atguigu.mp.mp01;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.atguigu.mp.mp01.mapper")
public class Mp01Application {
public static void main(String[] args) {
SpringApplication.run(Mp01Application.class, args);
}
}
配置日志
# 配置日志
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.2 插入操作
package com.atguigu.mp.mp01;
import com.atguigu.mp.mp01.bean.User;
import com.atguigu.mp.mp01.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void insert() {
userMapper.insert(new User(null, "MP", 22, "mp@atguigu.com"));
}
}
运行结果如下,可以发现,没有设置id的时候,insert方法自动生成了id
2022-01-06 10:44:46.563 INFO 5484 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1684265526 wrapping com.mysql.cj.jdbc.ConnectionImpl@4a4b288a] will not be managed by Spring
==> Preparing: INSERT INTO user ( id, name, age, email ) VALUES ( ?, ?, ?, ? )
==> Parameters: 1478920447641960450(Long), MP(String), 22(Integer), mp@atguigu.com(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1426370c]
3.2.1 主键问题
在主键上使用不同的注解,将会产生不同的ID。如果没有添加注解且主键为Number子类,默认自动生成雪花算法计算得到的ID。因此在主键属性上添加注解至关重要。
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 主键字段名 |
type | Enum | 否 | IdType.NONE | 指定主键类型 |
相关的枚举:
值 | 描述 |
---|---|
AUTO | 数据库 ID 自增 |
NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | insert 前自行 set 主键值 |
ASSIGN_ID | 分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator 的方法nextId (默认实现类为DefaultIdentifierGenerator 雪花算法) |
ASSIGN_UUID | 分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator 的方法nextUUID (默认 default 方法) |
ID_WORKER | 分布式全局唯一 ID 长整型类型(please use ASSIGN_ID ) |
UUID | 32 位 UUID 字符串(please use ASSIGN_UUID ) |
ID_WORKER_STR | 分布式全局唯一 ID 字符串类型(please use ASSIGN_ID ) |
默认生成的方式为NONE,当插入对象ID为空且类型为Number,使用雪花算法自动补充。
/*
* Copyright (c) 2011-2022, baomidou (jobob@qq.com).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.mybatisplus.annotation;
import lombok.Getter;
/**
* 生成ID类型枚举类
*
* @author hubin
* @since 2015-11-10
*/
@Getter
public enum IdType {
/**
* 数据库ID自增
* <p>该类型请确保数据库设置了 ID自增 否则无效</p>
*/
AUTO(0),
/**
* 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
*/
NONE(1),
/**
* 用户输入ID
* <p>该类型可以通过自己注册自动填充插件进行填充</p>
*/
INPUT(2),
/* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
/**
* 分配ID (主键类型为number或string),
* 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(雪花算法)
*
* @since 3.3.0
*/
ASSIGN_ID(3),
/**
* 分配UUID (主键类型为 string)
* 默认实现类 {@link com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator}(UUID.replace("-",""))
*/
ASSIGN_UUID(4);
private final int key;
IdType(int key) {
this.key = key;
}
}
3.2.2 设置主键自增
数据库层面
代码层面
在字段上增加注解
@TableId(value = "id", type = IdType.AUTO)
private Long id;
运行测试方法,可得到恢复正常自增ID
JDBC Connection [HikariProxyConnection@1507181879 wrapping com.mysql.cj.jdbc.ConnectionImpl@5464b97c] will not be managed by Spring
==> Preparing: INSERT INTO user ( name, age, email ) VALUES ( ?, ?, ? )
==> Parameters: MP(String), 22(Integer), mp@atguigu.com(String)
<== Updates: 1
id | name | age | |
---|---|---|---|
6 | MP | 22 | mp@atguigu.com |
3.2.3 数据库全局配置
在application.yml中配置,具体配置:基本配置
3.2.4 相关注解
@TableName
- 描述:表名注解,标识实体类对应的表
- 使用位置:实体类
@TableId
- 描述:主键注解
- 使用位置:实体类主键字段
@TableField
- 描述:字段注解(非主键)
属性 | 类型 | 必须指定 | 默认值 | 描述 |
---|---|---|---|---|
value | String | 否 | "" | 数据库字段名 |
exist | boolean | 否 | true | 是否为数据库表字段 |
具体内容可查看官方文档,这里注意一下exist字段,如果设置成false表示数据库里面没有这个字段。
例如在 User 类中新增一个字段salary,如果不设置该字段会报如下错误:
Error updating database. Cause: java.sql.SQLSyntaxErrorException: Unknown column 'salary' in 'field list'
可以设置如下:
@TableField(exist = false)
private Double salary;
3.2.5 插入数据后获取主键值
MyBatis-Plus在插入后能够自动获取主键值。示例代码如下:
@SpringBootTest
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void insert() {
User user = new User(null, "MP", null, "mp@atguigu.com", 20000d);
userMapper.insert(user);
System.out.println(user.getId());
}
}
控制台打印:
JDBC Connection [HikariProxyConnection@911998047 wrapping com.mysql.cj.jdbc.ConnectionImpl@1a7e799e] will not be managed by Spring
==> Preparing: INSERT INTO user ( name, email ) VALUES ( ?, ? )
==> Parameters: MP(String), mp@atguigu.com(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@506aa618]
10
3.2.6 插入所有字段
可以在配置文件中修改
insertStrategy
- 类型:
com.baomidou.mybatisplus.annotation.FieldStrategy
- 默认值:
NOT_NULL
字段验证策略之 insert,在 insert 的时候的字段验证策略
/**
* 字段策略枚举类
* <p>
* 如果字段是基本数据类型则最终效果等同于 {@link #IGNORED}
*
* @author hubin
* @since 2016-09-09
*/
public enum FieldStrategy {
/**
* 忽略判断
*/
IGNORED,
/**
* 非NULL判断
*/
NOT_NULL,
/**
* 非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断)
*/
NOT_EMPTY,
/**
* 默认的,一般只用于注解里
* <p>1. 在全局里代表 NOT_NULL</p>
* <p>2. 在注解里代表 跟随全局</p>
*/
DEFAULT,
/**
* 不加入 SQL
*/
NEVER
}
如果设置为IGNORE,即使字段为空也进行插入。
3.3 更新操作
@Test
public void update() {
User user = new User(7L, "MP2", null, "mp2@atguigu.com");
userMapper.updateById(user);
}
JDBC Connection [HikariProxyConnection@674247007 wrapping com.mysql.cj.jdbc.ConnectionImpl@da22aa] will not be managed by Spring
==> Preparing: UPDATE user SET name=?, email=? WHERE id=?
==> Parameters: MP2(String), mp2@atguigu.com(String), 7(Long)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4afd65fd]
3.4 查询操作
/**
* 根据 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);
3.5 删除操作
/**
* 根据 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);
3.6 通用 CRUD 小结
- 以上是基本的 CRUD操作,如您所见,我们仅仅需要继承一个 BaseMapper 即可实现大部分单表 CRUD操作。 BaseMapper 提供了很多方法给大家使用,可以极其方便的实现单一、批量、分页等操作。极大的减少开发负担,难道这就是 M P的强大之处了吗?
-
提出需求:
现有一个需求,我们需要分页查询 Employee 表中,年龄在 18 ~ 50之间性别为男且姓名为 xx 的所有用户,这时候我们该如何实现上述需求呢?MyBatis:需要在 SQL映射文件中编写带条件查询的 SQL,并基于 PageHelper 插件完成分页。实现以上—个简单的需求,往往需要我们做很多重复单调的工作。普通的 Mapper 能够解决这类痛点吗?
MP: 依旧不用编写 SQL语句, MP提供了功能强大的条件构造器 EntityWrapper。
4 条件构造器 QueryWrapper
4.1 AbstractWrapper
QueryWrapper(LambdaQueryWrapper) 和
UpdateWrapper(LambdaUpdateWrapper) 的父类
用于生成 sql 的 where 条件,entity 属性也用于生成 sql 的 where 条件
注意:entity 生成的 where 条件与使用各个 api 生成的 where 条件没有任何关联行为
所有的条件参数可以看官方文档,复制一大串在这里也挺没意思的。
4.1.1 继承关系
Wrapper:条件构造抽象类,最顶端父类,抽象类中提供4个方法西面贴源码展示
AbstractWrapper :用于查询条件封装,生成 sql 的 where 条件
AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper
LambdaUpdateWrapper : Lambda 更新封装Wrapper
QueryWrapper : Entity 对象封装操作类,不是用lambda语法
UpdateWrapper : Update 条件封装,用于Entity对象更新操作
4.2 QueryWrapper
继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及 LambdaQueryWrapper ,可以通过 new QueryWrapper().lambda() 方法获取
https://baomidou.com/pages/10c804/#querywrapper
4.2.1 查询分页示例
在查询分页需要进行如下配置
package com.atguigu.mp.mp01.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
给一个简单示例,一看就能明白了
@Test
public void wrapper() {
Page<User> userPage = userMapper.selectPage(new Page<>(1, 2),
new QueryWrapper<User>().like("name", "%MP%")
.between("age", 15, 25)
.orderByDesc("age")
);
System.out.println(JSON.toJSONString(userPage));
}
这个是日志,可以看到打印的SQL
2022-01-07 10:15:40.610 INFO 2573 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-01-07 10:15:40.804 INFO 2573 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1185122508 wrapping com.mysql.cj.jdbc.ConnectionImpl@3e39f08c] will not be managed by Spring
==> Preparing: SELECT COUNT(*) AS total FROM user WHERE (name LIKE ? AND age BETWEEN ? AND ?)
==> Parameters: %%MP%%(String), 15(Integer), 25(Integer)
<== Columns: total
<== Row: 3
<== Total: 1
==> Preparing: SELECT id,name,age,email FROM user WHERE (name LIKE ? AND age BETWEEN ? AND ?) ORDER BY age DESC LIMIT ?
==> Parameters: %%MP%%(String), 15(Integer), 25(Integer), 2(Long)
<== Columns: id, name, age, email
<== Row: 11, MP, 25, mp@atguigu.com
<== Row: 6, MP, 22, mp@atguigu.com
<== Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1d226f27]
如果不配置 MybatisPlusInterceptor ,MP 提供的分页方法是无效的,例如使用mysql,配置了 MybatisPlusInterceptor ,调用MP提供的分页方法,假设使用selectPage方法,他会执行两条sql语句,一条查询总记录数的sql,一条查询当前页记录的sql是带limit分页条件的。如果不配置,调用selectPage只会执行一条查询记录的sql,并且不带limit,有兴趣你可以试试,很容易就能验证出来的。
返回的结果如下:
{
"current":1,
"optimizeCountSql":true,
"orders":[
],
"pages":2,
"records":[
{
"age":25,
"email":"mp@atguigu.com",
"id":11,
"name":"MP"
},
{
"age":22,
"email":"mp@atguigu.com",
"id":6,
"name":"MP"
}
],
"searchCount":true,
"size":2,
"total":3
}
4.2.2 查询列表示例
测试一个带OR条件的例子
@Test
public void wrapper() {
Page<User> userPage = userMapper.selectPage(new Page<>(1, 2),
new QueryWrapper<User>().eq("name", "Jone")
.or(wrapper -> wrapper.eq("email", "test3@baomidou.com")
.and(subWrppaer -> subWrppaer.eq("name", "Tom"))
)
);
System.out.println(JSON.toJSONString(userPage));
}
打印的SQL如下
2022-01-07 10:23:16.429 INFO 2648 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-01-07 10:23:16.612 INFO 2648 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@547647542 wrapping com.mysql.cj.jdbc.ConnectionImpl@70c205bf] will not be managed by Spring
==> Preparing: SELECT COUNT(*) AS total FROM user WHERE (name = ? OR (email = ? AND (name = ?)))
==> Parameters: Jone(String), test3@baomidou.com(String), Tom(String)
<== Columns: total
<== Row: 2
<== Total: 1
==> Preparing: SELECT id,name,age,email FROM user WHERE (name = ? OR (email = ? AND (name = ?))) LIMIT ?
==> Parameters: Jone(String), test3@baomidou.com(String), Tom(String), 2(Long)
<== Columns: id, name, age, email
<== Row: 1, Jone, 18, test1@baomidou.com
<== Row: 3, Tom, 28, test3@baomidou.com
<== Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1bb96449]
{"current":1,"optimizeCountSql":true,"orders":[],"pages":1,"records":[{"age":18,"email":"test1@baomidou.com","id":1,"name":"Jone"},{"age":28,"email":"test3@baomidou.com","id":3,"name":"Tom"}],"searchCount":true,"size":2,"total":2}
4.2.3 删除示例
@Test
public void wrapper() {
userMapper.delete(new QueryWrapper<User>()
.eq("id", 9)
.eq("name", "MP")
);
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e24592e] was not registered for synchronization because synchronization is not active
2022-01-07 10:44:14.443 INFO 2912 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-01-07 10:44:14.863 INFO 2912 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@545851930 wrapping com.mysql.cj.jdbc.ConnectionImpl@70a24f9] will not be managed by Spring
==> Preparing: DELETE FROM user WHERE (id = ? AND name = ?)
==> Parameters: 9(Integer), MP(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@5e24592e]
4.3 UpdateWrapper
说明:
继承自 AbstractWrapper
,自身的内部属性 entity
也用于生成 where 条件及 LambdaUpdateWrapper
,可以通过 new UpdateWrapper().lambda()
方法获取!
public void wrapper() {
/*
根据 whereEntity 条件,更新记录
Params:
entity – 实体对象 (set 条件值,可以为 null)
updateWrapper – 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
*/
userMapper.update(new User(null, "Jerry", 22, "test11@baomidou.com"),
new UpdateWrapper<User>().eq( "id", 11L)
.eq("email", "mp@atguigu.com")
);
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6f0a4e30] was not registered for synchronization because synchronization is not active
2022-01-07 10:35:04.705 INFO 2774 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-01-07 10:35:04.891 INFO 2774 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1370822209 wrapping com.mysql.cj.jdbc.ConnectionImpl@362a6aa5] will not be managed by Spring
==> Preparing: UPDATE user SET name=?, age=?, email=? WHERE (id = ? AND email = ?)
==> Parameters: Jerry(String), 22(Integer), test11@baomidou.com(String), 11(Long), mp@atguigu.com(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6f0a4e30]
5 ActiveRecord 活动记录
Active Record(活动记录 ),是一种领域模型模式,特点是一个模型类对应关系型数据库中的一个表,而模型类的一个实例对应表中的 一行记录。
ActiveRecord 一直广受动态语言( PHP 、 Ruby 等)的喜爱,而 Java 作为准静态语言,对于 ActiveRecord 往往只能感叹其优雅,所以 MP也在 AR 道路上进行了一定的探索
5.1 如何使用 AR 模式
仅仅需要让实体类继承Model类且实现主键制定方法,即刻开启AR之旅。
新版本中不这么干也行
package com.atguigu.mp.mp01.bean;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class User extends Model<User> {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
@Override
public Serializable pkVal() {
return this.id;
}
}
5.2 AR 的使用
这里就用一个新增举例子了,其他的都是一样的,看父类里面的方法和参数就行了 。
@Test
public void arInsert() {
User user = new User();
user.setName("Song");
user.setEmail("sls@atguigu.com");
user.setAge(35);
boolean result = user.insert();
System.out.println("result = " + result);
}
日志长这样
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@64f3ca6] was not registered for synchronization because synchronization is not active
2022-01-07 12:35:12.278 INFO 5830 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-01-07 12:35:12.462 INFO 5830 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@313145375 wrapping com.mysql.cj.jdbc.ConnectionImpl@6115846e] will not be managed by Spring
==> Preparing: INSERT INTO user ( name, age, email ) VALUES ( ?, ?, ? )
==> Parameters: Song(String), 35(Integer), sls@atguigu.com(String)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@64f3ca6]
result = true
5.3 AR 小结
AR模式提供了一种更加便捷的方式实现 CRUD 操作,其本质还是调用的 MyBatis 对应的方法,类似于语法糖。
到此,我们简单领略了 MyBatis-Plus 的魅力与高效率,值得注意的一点是:我们提供了强大的代码生成器,可以快速生成各类代码,真正的做到了即开即用。
6 代码生成器
MP提供了大量的自定义设置,生成的代码完全能够满足各类型的需求
表及字段命名策略选择
在MP中,我们建议数据库表名和表字段名采用驼峰命名方式,如果采用下划线命名方式谙开启全局下划线开关。(如果表名字段名命名方式不一致请注解指定)我们建议最好保持一致。
这么做的原因是为了避免在对应实体类时产生的性能损耗,这样字段不用做映射就能直接和实体类对应。当然如果项目里不用考虎这点性能损耗,那么你采用下划线也是没问题的。只需要在生成代码时配置 dbColumnUnderline 属性就可以。
6.1 快速入门
Generator源码地址:
Github:https://github.com/baomidou/generator
Gitee:https://gitee.com/baomidou/generator
Maven仓库坐标:
https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator
官方文档:https://baomidou.com/pages/779a6e/#安装
6.1.1 构建者模式
在我们展开使用代码生成器之前,有必要简单介绍下构建者模式Builder Pattern
由于生成器涉及大量的配置,之前旧版本普通为普通javabean配置方式,当配置属性足够多时,弊端就显现了代码不易编写、不易阅读。于是引入构建者设计模式来解决以上问题。
其实本质是一样,就是换了一个写法大家来观摩下:https://baomidou.com/guide/generator-new.html
普通的生成方式
package com.atguigu.mp.mp01.builder;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class CodeGenerator {
/**
* 数据源配置
*/
private String dataSource;
/**
* 包配置
*/
private String packageConfig;
/**
* 策略配置
*/
private String strategyConfig;
/**
* 执行代码生成
*/
public void execute() {
System.out.println(
"CodeGenerator [dataSource=" + dataSource + ", packageConfig=" + packageConfig + ", strategyConfig="
+ strategyConfig + "]");
;
}
}
构建者模式
package com.atguigu.mp.mp01.builder;
/**
* 代码生成器构建模式
*/
public class CodeGeneratorBuilder {
/**
* codeGenerator
*/
private CodeGenerator codeGenerator;
public CodeGeneratorBuilder(CodeGenerator codeGenerator) {
this.codeGenerator = codeGenerator;
}
public static CodeGeneratorBuilder create(String dataSource) {
CodeGenerator cg = new CodeGenerator();
cg.setDataSource(dataSource);
return new CodeGeneratorBuilder(cg);
}
public CodeGeneratorBuilder packageConfig(String packageConfig) {
codeGenerator.setPackageConfig(packageConfig);
return this;
}
public CodeGeneratorBuilder strategyConfig(String strategyConfig) {
codeGenerator.setStrategyConfig(strategyConfig);
return this;
}
public void execute() {
codeGenerator.execute();
}
}
测试
package com.atguigu.mp.mp01.builder;
public class Test {
public static void main(String[] args) {
// 普通模式
CodeGenerator codeGenerator = new CodeGenerator();
codeGenerator.setDataSource("mysql");
codeGenerator.setPackageConfig("com.atguigu.mp.mp01");
codeGenerator.setStrategyConfig("驼峰命名");
codeGenerator.execute();
// 构建者模式
CodeGeneratorBuilder.create("mysql")
.packageConfig("com.atguigu.mp.mp01")
.strategyConfig("驼峰命名")
.execute();
}
}
6.1.2 代码生成测试
倒入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
代码
package com.atguigu.mp.mp01.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
public class CodeGenerator {
static final String URL = "jdbc:mysql://localhost:3306/mp?useSSL=false&useUnicode=true&characterEncoding=utf-8";
public static void main(String[] args) {
FastAutoGenerator.create(URL, "root", "12345678")
// 全局配置
.globalConfig(builder -> builder.outputDir("src/main/java"))
// 策略配置
.strategyConfig(builder -> builder.addInclude("user"))
// 执行
.execute();
}
}
6.2 数据库配置
全部配置项地址:数据库配置
- 添加指定数据库驱动依赖jar
- 数据库网络可用,云主机注意防火墙、网络安全组对应端口是否开放
- 严禁使用数据库关键词命名
- 务必表字段添加注释
- 多schema非默认必须指定
6.3 包配置
全部配置项地址:包配置
- 生成当前项目目录
System.getProperty("user.dir") + "/src/main/java"
- 定义父目录
parent
- 自定义输出
pathInfo
- 模块化输出指定
parent
动态路径
例子:
package com.atguigu.mp.mp01.pkg;
import java.text.MessageFormat;
import java.util.Collections;
import com.atguigu.mp.mp01.generator.CodeGenerator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
public class Generater {
public static void main(String[] args) {
String pkgPath = MessageFormat.format("{0}/src/main/java", System.getProperty("user.dir"));
FastAutoGenerator.create(CodeGenerator.URL, "root", "12345678")
// 全局配置
.globalConfig(builder -> builder
.author("Shibuya Kanon")
.commentDate("yyyy-MM-dd hh:mm:ss")
.outputDir(pkgPath)
.fileOverride()
.disableOpenDir())
// 包配置
.packageConfig(builder -> builder.parent("com.atguigu.mp.mp01")
.pathInfo(Collections.singletonMap(OutputFile.mapperXml,
MessageFormat.format("{0}/src/main/resources/mappers",
System.getProperty("user.dir")))))
// 策略配置
.strategyConfig(builder -> builder.addInclude("user"))
// 执行
.execute();
}
}
package com.atguigu.mp.mp01.pkg;
import java.text.MessageFormat;
import com.atguigu.mp.mp01.generator.CodeGenerator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
public class Generater {
public static void main(String[] args) {
String pkgPath = MessageFormat.format("{0}/src/main/java", System.getProperty("user.dir"));
FastAutoGenerator.create(CodeGenerator.URL, "root", "12345678")
// 全局配置
.globalConfig(builder -> builder
.author("Shibuya Kanon")
.commentDate("yyyy-MM-dd hh:mm:ss")
.outputDir(pkgPath)
.fileOverride()
.disableOpenDir())
// 包配置
.packageConfig((scanner, builder) -> builder.parent("com.atguigu.mp" + scanner.apply("请输入你的模块名称:")))
// 策略配置
.strategyConfig((scanner, builder) -> builder.addInclude(scanner.apply("请输入表名:")))
// 执行
.execute();
}
}
6.4 模版配置
全部配置:模版配置
- 禁用模块disable
- 自定义模板,先要了解下什么是模板引擎?
- 渲染模板的参数怎么查看?
package com.atguigu.mp.mp01.template;
import java.text.MessageFormat;
import com.atguigu.mp.mp01.generator.CodeGenerator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.TemplateType;
public class TemplateGenerator {
public static void main(String[] args) {
String pkgPath = MessageFormat.format("{0}/src/main/java", System.getProperty("user.dir"));
FastAutoGenerator.create(CodeGenerator.URL, "root", "12345678")
// 全局配置
.globalConfig(builder -> builder
.author("Shibuya Kanon")
.commentDate("yyyy-MM-dd hh:mm:ss")
.outputDir(pkgPath)
.fileOverride()
.disableOpenDir())
// 模板配置
.templateConfig(builder -> builder.disable(TemplateType.XML, TemplateType.CONTROLLER))
// 包配置
.packageConfig((scanner, builder) -> builder.parent("com.atguigu.mp" + scanner.apply("请输入你的模块名称:")))
// 策略配置
.strategyConfig((scanner, builder) -> builder.addInclude(scanner.apply("请输入表名:")))
// 执行
.execute();
}
}
6.5 注入配置
方法 | 说明 | 示例 |
---|---|---|
beforeOutputFile(BiConsumer<TableInfo, Map<String, Object>>) | 输出文件之前消费者 | |
customMap(Map<String, Object>) | 自定义配置 Map 对象 | Collections.singletonMap("test", "baomidou") |
customFile(Map<String, String>) | 自定义配置模板文件 | Collections.singletonMap("test.txt", "/templates/test.vm") |
new InjectionConfig.Builder()
.beforeOutputFile((tableInfo, objectMap) -> {
System.out.println("tableInfo: " + tableInfo.getEntityName() + " objectMap: " + objectMap.size());
})
.customMap(Collections.singletonMap("test", "baomidou"))
.customFile(Collections.singletonMap("test.txt", "/templates/test.vm"))
.build();
7 插件扩展
7.1 Mybatis插件机制简介
1)插件机制
Mybatis通过插件(Interceptor)可以做到拦截四太对象相关方法的执行,根据需求,完成相关数据的动态改变。
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
2)插件原理
四大对象的每个对象在创建时,都会执行 interceptorChain.pluginAll() , 会经过每个插件对象的 plugin() 方法,目的是为当前的四大对象创建代理。代理对象就可以拦截到四大对象相关方法的执行,因为要执行四太对象的方法需要经过代理。
7.2 分页插件 PaginationInnerInterceptor
启用分页插件
package com.atguigu.mp.mp01.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
具体的使用方法之前已经有过样例了,这里就直接给一下官网的文档了。
7.2.1 支持的数据库
- mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库
👉 如果没有支持你需要的数据库,点击参考 Pull Request 我们会第一时间审核(opens new window)
7.2.2 属性介绍
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
overflow | boolean | false | 溢出总页数后是否进行处理(默认不处理,参见 插件#continuePage 方法) |
maxLimit | Long | 单页分页条数限制(默认无限制,参见 插件#handlerLimit 方法) |
|
dbType | DbType | 数据库类型(根据类型获取应使用的分页方言,参见 插件#findIDialect 方法) |
|
dialect | IDialect | 方言实现类(参见 插件#findIDialect 方法) |
建议单一数据库类型的均设置 dbType
7.2.3 自定义的 mapper#method 使用分页
IPage<UserVo> selectPageVo(IPage<?> page, Integer state);
// or (class MyPage extends Ipage<UserVo>{ private Integer state; })
MyPage selectPageVo(MyPage page);
// or
List<UserVo> selectPageVo(IPage<UserVo> page, Integer state);
<select id="selectPageVo" resultType="xxx.xxx.xxx.UserVo">
SELECT id,name FROM user WHERE state=#{state}
</select>
如果返回类型是 IPage 则入参的 IPage 不能为null,因为 返回的IPage == 入参的IPage
如果返回类型是 List 则入参的 IPage 可以为 null(为 null 则不分页),但需要你手动 入参的IPage.setRecords(返回的 List);
如果 xml 需要从 page 里取值,需要 page.属性
获取
7.2.4 其他
生成 countSql 会在 left join
的表不参与 where
条件的情况下,把 left join
优化掉。所以建议任何带有 left join
的sql,都写标准sql,即给于表一个别名,字段也要 别名.字段
。
7.3 不规范SQL拦截器
IllegalSQLInnerInterceptor:不规范SQL拦截器。
由于开发人员水平参差不齐,即使订了开发规范很多人也不遵守,SQL是影响系统性能最重要的因素,所以拦截掉不规范SQL语句。
拦截SQL类型的场景:
-
必须使用到索引,包含left join连接字段,符合索引最左原则
- 如果因为动态SQL,bug导致update的where条件没有带上,全表更新上万条数据
- 如果检查到使用了索引,SQL性能基本不会太差
-
SQL尽量单表执行,有查询left join的语句,必须在注释里面允许该SQL运行,否则会被拦截,有left join的语句,如果不能拆成单表执行的SQL,请leader商量在做,SQL尽量单表执行的好处:
- 查询条件简单、易于开理解和维护
- 扩展性极强;(可为分库分表做准备)
- 缓存利用率高
-
where条件为空
-
where条件使用了 !=
-
where条件使用了 not 关键字
-
where条件使用了 or 关键字
-
where条件使用子查询
7.3.1 测试案例
配置类添加插件
package com.atguigu.mp.mp01.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.IllegalSQLInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new IllegalSQLInnerInterceptor());
return interceptor;
}
}
测试执行,发现此SQL没有添加条件,被拦截
@Test
void interceptors() {
List<User> users = userMapper.selectList(new QueryWrapper<>());
System.out.println("users = " + users);
}
测试执行,发现此SQL没有添加条件,被拦截
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@320ff86f] was not registered for synchronization because synchronization is not active
2022-01-10 16:11:20.169 INFO 10213 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-01-10 16:11:20.365 INFO 10213 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@47865820 wrapping com.mysql.cj.jdbc.ConnectionImpl@23321be7] will not be managed by Spring
检查SQL是否合规,SQL:SELECT id,name,age,email FROM user
original SQL: SELECT id,name,age,email FROM user
SQL to parse, SQL: SELECT id,name,age,email FROM user
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@320ff86f]
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:
### Error querying database. Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: 非法SQL,必须要有where条件
### The error may exist in com/atguigu/mp/mp01/mapper/UserMapper.java (best guess)
### The error may involve com.atguigu.mp.mp01.mapper.UserMapper.selectList
### The error occurred while executing a query
### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: 非法SQL,必须要有where条件
...
7.4 乐观锁插件
com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor
如果想实现如下需求:当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁的实现原理:
- 取出记录时,获取当前 version
- 更新时,带上这个 version
- 执行更新时,
set version = yourVersion + 1 where version = yourVersion
- 如果 version 不对,就更新失败。
@Version
用于注解实体字段,必须要有。
7.4.1 测试使用
增加数据库字段version
ALTER TABLE user
ADD COLUMN version int DEFAULT NULL COMMENT '乐观锁'
代码中对version字段增加@Version
注解
/**
* 乐观锁
*/
@Version
private Integer version;
测试
@Test
void opLock() {
User user = new User();
user.setId(4L);
user.setAge(50);
user.setName("Sammy");
user.setVersion(1);
userMapper.updateById(user);
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6fde6f05] was not registered for synchronization because synchronization is not active
2022-01-10 16:25:54.141 INFO 10436 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-01-10 16:25:54.330 INFO 10436 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@967022668 wrapping com.mysql.cj.jdbc.ConnectionImpl@4404a6b] will not be managed by Spring
检查SQL是否合规,SQL:UPDATE user SET name=?,
age=?,
version=? WHERE id=? AND version=?
original SQL: UPDATE user SET name=?,
age=?,
version=? WHERE id=? AND version=?
SQL to parse, SQL: UPDATE user SET name=?,
age=?,
version=? WHERE id=? AND version=?
parse the finished SQL: UPDATE user SET name = ?, age = ?, version = ? WHERE id = ? AND version = ?
==> Preparing: UPDATE user SET name=?, age=?, version=? WHERE id=? AND version=?
==> Parameters: Sammy(String), 50(Integer), 2(Integer), 4(Long), 1(Integer)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6fde6f05]
7.5 其他
其余的插件可以在这里看到
8 自定义全局操作
根据 MyBatis Plus 的 AbstractSqlInjector 可以自定义各种你想要的SQL,注入到全局中,相当于自定义 MyBatis Plus 自动注入的方法。
8.1 AbstractSqlInjector
从源代码中可以看到一个抽象方法
/**
* <p>
* 获取 注入的方法
* </p>
*
* @param mapperClass 当前mapper
* @return 注入的方法集合
* @since 3.1.2 add mapperClass
*/
public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass,TableInfo tableInfo);
8.2 自定义注入器的应用1——逻辑删除
增加字段is_deleted
ALTER TABLE `user`
ADD COLUMN `is_deleted` boolean
application.yml增加配置
# 配置日志
mybatis-plus:
global-config:
db-config:
logic-delete-value: 1
logic-not-delete-value: 0
实体类增加注解
/**
* 是否删除
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Boolean isDeleted;
测试
@Test
void logicDelete() {
int delete = userMapper.deleteById(1L);
System.out.println(delete);
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8383a14] was not registered for synchronization because synchronization is not active
2022-01-10 17:56:09.772 INFO 11606 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-01-10 17:56:09.963 INFO 11606 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1640529691 wrapping com.mysql.cj.jdbc.ConnectionImpl@1857fe6c] will not be managed by Spring
检查SQL是否合规,SQL:UPDATE user SET is_deleted=1 WHERE id=? AND is_deleted=0
original SQL: UPDATE user SET is_deleted=1 WHERE id=? AND is_deleted=0
SQL to parse, SQL: UPDATE user SET is_deleted=1 WHERE id=? AND is_deleted=0
parse the finished SQL: UPDATE user SET is_deleted = 1 WHERE id = ? AND is_deleted = 0
==> Preparing: UPDATE user SET is_deleted=1 WHERE id=? AND is_deleted=0
==> Parameters: 1(Long)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@8383a14]
9 公共字段自动填充
9.1 元数据处理器接口
com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
元对象字段填充控制器抽象类,实现公共字段自动写入
所有入参的 MetaObject 必定是 entity 或其子类的 MetaObject
MetaObject:元对象,是 MyBatis 提供的一个用于更加方便、更加优雅的访问对象的属性,给对象的属性设置值的一个对象.还会用于包装对象。支持对 Object、Map、 Collection 等对象进行包装。
9.2 开发步骤
- 注解填充字段
@TableField(fill = FieldFill.INSERT)
查看 TableFill - 自定义公共字段填充处理器。
- MP全局注入自定义公共字段填充处理器,
9.3 测试
在相关字段上增加注解
/**
* 是否删除
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Boolean isDeleted;
/**
* 姓名
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private String name;
实现元数据处理器接口
package com.atguigu.mp.mp01.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
@Slf4j
public class CustomMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("===================insertFill");
// 添加逻辑删除的默认值
this.setFieldValByName("name", "Jasmine", metaObject);
this.setFieldValByName("isDeleted", false, metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("===================updateFill");
this.setFieldValByName("name", "Jasmine", metaObject);
}
}
测试
@Test
void insert() {
User user = new User();
user.setAge(10);
user.setEmail("eddie@qq.com");
user.setVersion(1);
userMapper.insert(user);
}
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3321291a] was not registered for synchronization because synchronization is not active
2022-01-10 18:20:03.184 INFO 11885 --- [ main] c.a.m.m.handler.CustomMetaObjectHandler : ===================insertFill
2022-01-10 18:20:03.188 INFO 11885 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-01-10 18:20:03.376 INFO 11885 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1853751897 wrapping com.mysql.cj.jdbc.ConnectionImpl@646e6d07] will not be managed by Spring
==> Preparing: INSERT INTO user ( name, age, email, version, is_deleted ) VALUES ( ?, ?, ?, ?, ? )
==> Parameters: Jasmine(String), 10(Integer), eddie@qq.com(String), 1(Integer), false(Boolean)
<== Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3321291a]
本文作者:我係死肥宅
本文链接:https://www.cnblogs.com/iamfatotaku/p/15785516.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)