- 特性
- 支持数据库
- 框架结构
- 快速开始
- 初始化工程
- 添加依赖
- 配置
- 编码
- 开始使用
- 小结
- 安装
- 配置
- 注解
- @TableField
- @Version
- @EnumValue
- @TableLogic
- @SqlParser
- @KeySequence
- 核心功能
- 代码生成器
- CRUD 接口
- Remove
- Update
- Get
- List
- Page
- Count
- Chain
- Mapper CRUD 接口
- Delete
- Update
- Select
- mapper 层 选装件
- 条件构造器
- 分页插件
- XML 自定义分页
- Sequence主键
- 自定义ID生成器
- 插件扩展
- 热加载
- 逻辑删除
- 通用枚举
- 1、声明通用枚举属性
- 2、配置扫描通用枚举
- 如何序列化枚举值为数据库存储值?
- 字段类型处理器
- 自动填充功能
- Sql 注入器
- 攻击 SQL 阻断解析器
- 性能分析插件
- 执行 SQL 分析打印
- 乐观锁插件
- 动态数据源
- 分布式事务
- 多租户 SQL 解析器
- 动态表名 SQL 解析器
- MybatisX 快速开发插件
- 常见问题
- 如何排除非表中字段?
- 排除实体父类属性
- 出现
Invalid bound statement (not found)
异常 - 自定义 SQL 无法执行
- 启动时异常
- 关于 Long 型主键填充不生效的问题
- ID_WORKER 生成主键太长导致 js 精度丢失
- 插入或更新的字段有 空字符串 或者
null
- 字段类型为
bit
、tinyint(1)
时映射为boolean
类型 - 出现 2 个
limit
语句 - insert 后如何返回主键
- MP 如何查指定的几个字段
- mapper 层二级缓存问题
- mapper 层二级缓存刷新问题
Cause: org.apache.ibatis.type.TypeException:Error setting null for parameter #1 with JdbcType OTHER
- 自定义 sql 里使用 Page 对象传参无法获取
- 如何使用:【Map下划线自动转驼峰】
- 在 wrapper 中如何使用
limit
限制 SQL - 通用 insertBatch 为什么放在 service 层处理
- 逻辑删除下 自动填充 功能没有效果
- 3.x数据库关键字如何处理?
- MybatisPlusException: Your property named “xxx” cannot find the corresponding database column name!
- Error attempting to get column ‘create_time’ from result set. Cause: java.sql.SQLFeatureNotSupportedException
- mp版本从3.1.0及以下版本升级到高版本,JDK8日期新类型LocalDateTime等无法映射(报错)
- Failed to bind properties under ‘mybatis-plus.configuration.incomplete-result-maps[0].assistant.configuration.mapped-statements[0].parameter-map.parameter-mappings[0]’ to org.apache.ibatis.mapping.ParameterMapping
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
支持数据库
mysql 、 mariadb 、 oracle 、 db2 、 h2 、 hsql 、 sqlite 、 postgresql 、 sqlserver
-
框架结构
- 文档:https://mybatis.plus/
- MyBatis-Plus 入门 - 视频教程 - 慕课网
-
快速开始
我们将通过一个简单的 Demo 来阐述 MyBatis-Plus 的强大功能,在此之前,我们假设您已经:
拥有 Java 开发环境以及相应 IDE
- 熟悉 Spring Boot
- 熟悉 Maven
现有一张 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(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)
);
其对应的数据库 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');
Question
如果从零开始用 MyBatis-Plus 来实现该表的增删改查我们需要做什么呢?
初始化工程
创建一个空的 Spring Boot 工程(工程将以 H2 作为默认数据库进行演示)
可以使用 Spring Initializer 快速初始化一个 Spring Boot 工程
添加依赖
引入 Spring Boot Starter 父工程:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/>
</parent>
引入 spring-boot-starter
、spring-boot-starter-test
、mybatis-plus-boot-starter
、lombok
、h2
依赖:
<dependencies>
<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>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
配置
在 application.yml
配置文件中添加 H2 数据库的相关配置:
# DataSource Config
spring:
datasource:
driver-class-name: org.h2.Driver
schema: classpath:db/schema-h2.sql
data: classpath:db/data-h2.sql
url: jdbc:h2:mem:test
username: root
password: test
在 Spring Boot 启动类中添加 @MapperScan
注解,扫描 Mapper 文件夹:
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(QuickStartApplication.class, args);
}
}
编码
编写实体类 User.java
(此处使用了 Lombok 简化代码)
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
编写Mapper类 UserMapper.java
public interface UserMapper extends BaseMapper<User> {
}
开始使用
添加测试类,进行功能测试:
@RunWith(SpringRunner.class)
@SpringBootTest
public class SampleTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
System.out.println(("----- selectAll method test ------"));
List<User> userList = userMapper.selectList(null);
Assert.assertEquals(5, userList.size());
userList.forEach(System.out::println);
}
}
UserMapper 中的 selectList()
方法的参数为 MP 内置的条件封装器 Wrapper
,所以不填写就是无任何条件
控制台输出:
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
完整的代码示例请移步:Spring Boot 快速启动示例 | Spring MVC 快速启动示例
小结
通过以上几个简单的步骤,我们就实现了 User 表的 CRUD 功能,甚至连 XML 文件都不用编写!
从以上步骤中,我们可以看到集成MyBatis-Plus
非常的简单,只需要引入 starter 工程,并配置 mapper 扫描路径即可。
但 MyBatis-Plus 的强大远不止这些功能,想要详细了解 MyBatis-Plus 的强大功能?那就继续往下看吧!
安装
全新的 MyBatis-Plus
3.0 版本基于 JDK8,提供了 lambda
形式的调用,所以安装集成 MP3.0 要求如下:
- JDK 8+
- Maven or Gradle
JDK7 以及下的请参考 MP2.0 版本,地址:2.0 文档
Release
Spring Boot
Maven:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.0</version>
</dependency>
Gradle:
compile group: 'com.baomidou', name: 'mybatis-plus-boot-starter', version: '3.3.0'
Spring MVC
Maven:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.3.0</version>
</dependency>
Gradle:
compile group: 'com.baomidou', name: 'mybatis-plus', version: '3.3.0'
引入 MyBatis-Plus
之后请不要再次引入 MyBatis
以及 MyBatis-Spring
,以避免因版本差异导致的问题。
Snapshot
快照 SNAPSHOT 版本需要添加仓库,且版本号为快照版本 点击查看最新快照版本号。
Maven:
<repository>
<id>snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
Gradle:
repositories {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
配置
MyBatis-Plus 的配置异常的简单,我们仅需要一些简单的配置即可使用 MyBatis-Plus 的强大功能!
在讲解配置之前,请确保您已经安装了 MyBatis-Plus,如果您尚未安装,请查看 安装 一章。
Spring Boot 工程:
- 配置 MapperScan 注解
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(QuickStartApplication.class, args);
}
}
- 配置 MapperScan 注解
Spring MVC 工程:
配置 MapperScan
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.baomidou.mybatisplus.samples.quickstart.mapper"/>
</bean>
调整 SqlSessionFactory 为 MyBatis-Plus 的 SqlSessionFactory
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
通常来说,一般的简单工程,通过以上配置即可正常使用 MyBatis-Plus,具体可参考以下项目:Spring Boot 快速启动示例、Spring MVC 快速启动示例。
同时 MyBatis-Plus 提供了大量的个性化配置来满足不同复杂度的工程,大家可根据自己的项目按需取用
注解
介绍
MybatisPlus
注解包相关类详解(更多详细描述可点击查看源码注释)
注解类包:
👉 mybatis-plus-annotation
@TableName
- 描述:表名注解 | 属性 | 类型 | 必须指定 | 默认值 | 描述 | | :—-: | :—-: | :—-: | :—-: | —- | | value | String | 否 | “” | 表名 | | schema | String | 否 | “” | schema | | keepGlobalPrefix | boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值(如果设置了全局 tablePrefix 且自行设置了 value 的值) | | resultMap | String | 否 | “” | xml 中 resultMap 的 id | | autoResultMap | boolean | 否 | false | 是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建并注入) |
关于autoResultMap
的说明:
mp会自动构建一个ResultMap
并注入到mybatis里(一般用不上).下面讲两句: 因为mp底层是mybatis,所以一些mybatis的常识你要知道,mp只是帮你注入了常用crud到mybatis里 注入之前可以说是动态的(根据你entity的字段以及注解变化而变化),但是注入之后是静态的(等于你写在xml的东西) 而对于直接指定typeHandler
,mybatis只支持你写在2个地方:
- 定义在resultMap里,只作用于select查询的返回结果封装
- 定义在
insert
和update
sql的#{property}
里的property
后面(例:#{property,typehandler=xxx.xxx.xxx}
),只作用于设置值
而除了这两种直接指定typeHandler
,mybatis有一个全局的扫描你自己的typeHandler
包的配置,这是根据你的property
的类型去找typeHandler
并使用.@TableId
- 描述:主键注解 | 属性 | 类型 | 必须指定 | 默认值 | 描述 | | :—-: | :—-: | :—-: | :—-: | :—-: | | value | String | 否 | “” | 主键字段名 | | type | Enum | 否 | IdType.NONE | 主键类型 |
IdType
值 | 描述 |
---|---|
AUTO | 数据库ID自增 |
NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT | insert前自行set主键值 |
ASSIGN_ID | 分配ID(主键类型为Number(Long和Integer)或String)(since 3.3.0),使用接口IdentifierGenerator 的方法nextId (默认实现类为DefaultIdentifierGenerator 雪花算法) |
ASSIGN_UUID | 分配UUID,主键类型为String(since 3.3.0),使用接口IdentifierGenerator 的方法nextUUID (默认default方法) |
分布式全局唯一ID 长整型类型(please use ASSIGN_ID ) |
|
32位UUID字符串(please use ASSIGN_UUID ) |
|
分布式全局唯一ID 字符串类型(please use ASSIGN_ID ) |
@TableField
- 描述:字段注解(非主键)
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
| :—-: | :—-: | :—-: | :—-: | :—-: |
| value | String | 否 | “” | 字段名 |
| el | String | 否 | “” | 映射为原生
#{ ... }
逻辑,相当于写在 xml 里的#{ ... }
部分 | | exist | boolean | 否 | true | 是否为数据库表字段 | | condition | String | 否 | “” | 字段where
实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的%s=#{%s}
,参考 | | update | String | 否 | “” | 字段update set
部分注入, 例如:update=”%s+1”:表示更新时会set version=version+1(该属性优先级高于el
属性) | | insertStrategy | Enum | N | DEFAULT | 举例:NOT_NULL:insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>)
| | updateStrategy | Enum | N | DEFAULT | 举例:IGNORED:update table_a set column=#{columnProperty}
| | whereStrategy | Enum | N | DEFAULT | 举例:NOT_EMPTY:where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if>
| | fill | Enum | 否 | FieldFill.DEFAULT | 字段自动填充策略 | | select | boolean | 否 | true | 是否进行 select 查询 | | keepGlobalFormat | boolean | 否 | false | 是否保持使用全局的 format 进行处理 | | jdbcType | JdbcType | 否 | JdbcType.UNDEFINED | JDBC类型 (该默认值不代表会按照该值生效) | | typeHandler | Class<? extends TypeHandler> | 否 | UnknownTypeHandler.class | 类型处理器 (该默认值不代表会按照该值生效) | | numericScale | String | 否 | “” | 指定小数点后保留的位数 |
关于jdbcType
和typeHandler
以及numericScale
的说明:numericScale
只生效于 update 的sql. jdbcType
和typeHandler
如果不配合@TableName#autoResultMap = true
一起使用,也只生效于 update 的sql. 对于typeHandler
如果你的字段类型和set进去的类型为equals
关系,则只需要让你的typeHandler
让Mybatis加载到即可,不需要使用注解
FieldStrategy
值 | 描述 |
---|---|
IGNORED | 忽略判断 |
NOT_NULL | 非NULL判断 |
NOT_EMPTY | 非空判断(只对字符串类型字段,其他类型字段依然为非NULL判断) |
DEFAULT | 追随全局配置 |
FieldFill
值 | 描述 |
---|---|
DEFAULT | 默认不处理 |
INSERT | 插入时填充字段 |
UPDATE | 更新时填充字段 |
INSERT_UPDATE | 插入和更新时填充字段 |
@Version
-
@EnumValue
-
@TableLogic
描述:表字段逻辑处理注解(逻辑删除) | 属性 | 类型 | 必须指定 | 默认值 | 描述 | | :—-: | :—-: | :—-: | :—-: | :—-: | | value | String | 否 | “” | 逻辑未删除值 | | delval | String | 否 | “” | 逻辑删除值 |
@SqlParser
- 描述:租户注解,支持method上以及mapper接口上 | 属性 | 类型 | 必须指定 | 默认值 | 描述 | | :—-: | :—-: | :—-: | :—-: | :—-: | | filter | boolean | 否 | false | true: 表示过滤SQL解析,即不会进入ISqlParser解析链,否则会进解析链并追加例如tenant_id等条件 |
@KeySequence
- 描述:序列主键策略
oracle
- 属性:value、resultMap | 属性 | 类型 | 必须指定 | 默认值 | 描述 | | :—-: | :—-: | :—-: | :—-: | :—-: | | value | String | 否 | “” | 序列名 | | clazz | Class | 否 | Long.class | id的类型, 可以指定String.class,这样返回的Sequence值是字符串”1” |
核心功能
代码生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
特别说明:
自定义模板有哪些可用参数?Github Gitee AbstractTemplateEngine 类中方法 getObjectMap 返回 objectMap 的所有值都可用。
演示效果图:
// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("jobob");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/ant?useUnicode=true&useSSL=false&characterEncoding=utf8");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("密码");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模块名"));
pc.setParent("com.baomidou.ant");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
/*
cfg.setFileCreate(new IFileCreate() {
@Override
public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
// 判断自定义文件夹是否需要创建
checkDir("调用默认方法创建的目录");
return false;
}
});
*/
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
// 配置自定义输出模板
//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// 写于父类中的公共字段
strategy.setSuperEntityColumns("id");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}
使用教程
添加依赖
MyBatis-Plus 从 3.0.3
之后移除了代码生成器与模板引擎的默认依赖,需要手动添加相关依赖:
添加 代码生成器 依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.0</version>
</dependency>
添加 模板引擎 依赖,MyBatis-Plus 支持 Velocity(默认)、Freemarker、Beetl,用户可以选择自己熟悉的模板引擎,如果都不满足您的要求,可以采用自定义模板引擎。
Velocity(默认):<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.1</version>
</dependency>
Freemarker:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.29</version>
</dependency>
Beetl:
<dependency>
<groupId>com.ibeetl</groupId>
<artifactId>beetl</artifactId>
<version>3.0.16.RELEASE</version>
</dependency>
注意!如果您选择了非默认引擎,需要在 AutoGenerator 中 设置模板引擎。
AutoGenerator generator = new AutoGenerator();
// set freemarker engine
generator.setTemplateEngine(new FreemarkerTemplateEngine());
// set beetl engine
generator.setTemplateEngine(new BeetlTemplateEngine());
// set custom engine (reference class is your custom engine class)
generator.setTemplateEngine(new CustomTemplateEngine());
// other config
...
编写配置
MyBatis-Plus 的代码生成器提供了大量的自定义参数供用户选择,能够满足绝大部分人的使用需求。
配置 GlobalConfig
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
globalConfig.setAuthor("jobob");
globalConfig.setOpen(false);
配置 DataSourceConfig
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/ant?useUnicode=true&useSSL=false&characterEncoding=utf8");
dataSourceConfig.setDriverName("com.mysql.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("password");
自定义模板引擎
请继承类 com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine
自定义代码模板
//指定自定义模板路径, 位置:/resources/templates/entity2.java.ftl(或者是.vm)
//注意不要带上.ftl(或者是.vm), 会根据使用的模板引擎自动识别
TemplateConfig templateConfig = new TemplateConfig()
.setEntity("templates/entity2.java");
AutoGenerator mpg = new AutoGenerator();
//配置自定义模板
mpg.setTemplate(templateConfig);
自定义属性注入
InjectionConfig injectionConfig = new InjectionConfig() {
//自定义属性注入:abc
//在.ftl(或者是.vm)模板中,通过${cfg.abc}获取属性
@Override
public void initMap() {
Map<String, Object> map = new HashMap<>();
map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp");
this.setMap(map);
}
};
AutoGenerator mpg = new AutoGenerator();
//配置自定义属性注入
mpg.setCfg(injectionConfig);
entity2.java.ftl
自定义属性注入abc=${cfg.abc}
entity2.java.vm
自定义属性注入abc=$!{cfg.abc}
字段其他信息查询注入
new DataSourceConfig().setDbQuery(new MySqlQuery() {
/**
* 重写父类预留查询自定义字段<br>
* 这里查询的 SQL 对应父类 tableFieldsSql 的查询字段,默认不能满足你的需求请重写它<br>
* 模板中调用: table.fields 获取所有字段信息,
* 然后循环字段获取 field.customMap 从 MAP 中获取注入字段如下 NULL 或者 PRIVILEGES
*/
@Override
public String[] fieldCustom() {
return new String[]{"NULL", "PRIVILEGES"};
}
})
CRUD 接口
Service CRUD 接口
说明:
通用 Service CRUD 封装IService接口,进一步封装 CRUD 采用
get 查询单行
remove 删除
list 查询集合
page 分页
前缀命名方式区分Mapper
层避免混淆,- 泛型
T
为任意实体对象 - 建议如果存在自定义通用 Service 方法的可能,请创建自己的
IBaseService
继承Mybatis-Plus
提供的基类 - 对象
Wrapper
为 条件构造器Save
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
参数说明
| 类型 | 参数名 | 描述 | | :—-: | :—-: | :—-: | | T | entity | 实体对象 | | Collection| entityList | 实体对象集合 | | int | batchSize | 插入批次数量 |
SaveOrUpdate
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
参数说明
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 |
Wrapper |
updateWrapper | 实体对象封装操作类 UpdateWrapper |
Collection |
entityList | 实体对象集合 |
int | batchSize | 插入批次数量 |
Remove
// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
参数说明
类型 | 参数名 | 描述 |
---|---|---|
Wrapper |
queryWrapper | 实体包装类 QueryWrapper |
Serializable | id | 主键ID |
Map |
columnMap | 表字段 map 对象 |
Collection<? extends Serializable> | idList | 主键ID列表 |
Update
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereEntity 条件,更新记录
boolean update(T entity, Wrapper<T> updateWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
参数说明
类型 | 参数名 | 描述 |
---|---|---|
Wrapper |
updateWrapper | 实体对象封装操作类 UpdateWrapper |
T | entity | 实体对象 |
Collection |
entityList | 实体对象集合 |
int | batchSize | 更新批次数量 |
Get
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 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 | 转换函数 |
List
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
参数说明
类型 | 参数名 | 描述 |
---|---|---|
Wrapper |
queryWrapper | 实体对象封装操作类 QueryWrapper |
Collection<? extends Serializable> | idList | 主键ID列表 |
Map<?String, Object> | columnMap | 表字段 map 对象 |
Function<? super Object, V> | mapper | 转换函数 |
Page
// 无条件翻页查询
IPage<T> page(IPage<T> page);
// 翻页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件翻页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 翻页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
参数说明
类型 | 参数名 | 描述 |
---|---|---|
IPage |
page | 翻页对象 |
Wrapper |
queryWrapper | 实体对象封装操作类 QueryWrapper |
Count
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
参数说明
类型 | 参数名 | 描述 |
---|---|---|
Wrapper |
queryWrapper | 实体对象封装操作类 QueryWrapper |
Chain
query
// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery();
// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();
update
// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();
// 示例:
update().eq("column", value).remove();
lambdaUpdate().eq(Entity::getId, value).update(entity);
Mapper CRUD 接口
说明:
- 通用 CRUD 封装BaseMapper接口,为
Mybatis-Plus
启动时自动解析实体表关系映射转换为Mybatis
内部对象注入容器 - 泛型
T
为任意实体对象 - 参数
Serializable
为任意类型主键Mybatis-Plus
不推荐使用复合主键约定每一张表都有自己的唯一id
主键 - 对象
Wrapper
为 条件构造器Insert
// 插入一条记录
int insert(T entity);
参数说明
| 类型 | 参数名 | 描述 | | :—-: | :—-: | :—-: | | T | entity | 实体对象 |
Delete
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
参数说明
类型 | 参数名 | 描述 |
---|---|---|
Wrapper |
wrapper | 实体对象封装操作类(可以为 null) |
Collection<? extends Serializable> | idList | 主键ID列表(不能为 null 以及 empty) |
Serializable | id | 主键ID |
Map |
columnMap | 表字段 map 对象 |
Update
// 根据 whereEntity 条件,更新记录
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);
参数说明
类型 | 参数名 | 描述 |
---|---|---|
T | entity | 实体对象 (set 条件值,可为 null) |
Wrapper |
updateWrapper | 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) |
Select
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
参数说明
类型 | 参数名 | 描述 |
---|---|---|
Serializable | id | 主键ID |
Wrapper |
queryWrapper | 实体对象封装操作类(可以为 null) |
Collection<? extends Serializable> | idList | 主键ID列表(不能为 null 以及 empty) |
Map |
columnMap | 表字段 map 对象 |
IPage |
page | 分页查询条件(可以为 RowBounds.DEFAULT) |
mapper 层 选装件
说明:
选装件位于 com.baomidou.mybatisplus.extension.injector.methods.additional
包下 需要配合Sql 注入器使用案例
使用详细见源码注释
AlwaysUpdateSomeColumnById
int alwaysUpdateSomeColumnById(T entity);
insertBatchSomeColumn
int insertBatchSomeColumn(List<T> entityList);
deleteByIdWithFill
int deleteByIdWithFill(T entity);
条件构造器
说明:
- 以下出现的第一个入参
boolean condition
表示该条件是否加入最后生成的sql中 - 以下代码块内的多个方法均为从上往下补全个别
boolean
类型的入参,默认为true
- 以下出现的泛型
Param
均为Wrapper
的子类实例(均具有AbstractWrapper
的所有方法) - 以下方法在入参中出现的
R
为泛型,在普通wrapper中是String
,在LambdaWrapper中是函数(例:Entity::getId
,Entity
为实体类,getId
为字段id
的getMethod) - 以下方法入参中的
R column
均表示数据库字段,当R
具体类型为String
时则为数据库字段名(字段名是数据库关键字的自己用转义符包裹!)!而不是实体类数据字段名!!!,另当R
具体类型为SFunction
时项目runtime不支持eclipse自家的编译器!!! - 以下举例均为使用普通wrapper,入参为
Map
和List
的均以json
形式表现! - 使用中如果入参的
Map
或者List
为空,则不会加入最后生成的sql中!!! - 有任何疑问就点开源码看,看不懂函数的点击我学习新知识
警告:
不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输
- wrapper 很重
- 传输 wrapper 可以类比为你的 controller 用 map 接收值(开发一时爽,维护火葬场)
- 正确的 RPC 调用姿势是写一个 DTO 进行传输,被调用方再根据 DTO 执行相应的操作
- 我们拒绝接受任何关于 RPC 传输 Wrapper 报错相关的 issue 甚至 pr
AbstractWrapper
说明:
QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类
用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件
注意: entity 生成的 where 条件与 使用各个 api 生成的 where 条件没有任何关联行为allEq
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
个别参数说明:params
: key
为数据库字段名,value
为字段值null2IsNull
: 为true
则在map
的value
为null
时调用 isNull 方法,为false
时则忽略value
为null
的
- 例1:
allEq({id:1,name:"老王",age:null})
—->id = 1 and name = '老王' and age is null
- 例2:
allEq({id:1,name:"老王",age:null}, false)
—->id = 1 and name = '老王'
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
个别参数说明:filter
: 过滤函数,是否允许字段传入比对条件中params
与 null2IsNull
: 同上
- 例1:
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})
—->name = '老王' and age is null
- 例2:
allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)
—->name = '老王'
eq
eq(R column, Object val)
eq(boolean condition, R column, Object val)
- 等于 =
例:
eq("name", "老王")
—->name = '老王'
ne
ne(R column, Object val)
ne(boolean condition, R column, Object val)
不等于 <>
例:
ne("name", "老王")
—->name <> '老王'
gt
gt(R column, Object val)
gt(boolean condition, R column, Object val)
大于 >
- 例:
gt("age", 18)
—->age > 18
ge
ge(R column, Object val)
ge(boolean condition, R column, Object val)
lt(R column, Object val)
lt(boolean condition, R column, Object val)
le(R column, Object val)
le(boolean condition, R column, Object val)
between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
notBetween(R column, Object val1, Object val2)
notBetween(boolean condition, R column, Object val1, Object val2)
like(R column, Object val)
like(boolean condition, R column, Object val)
notLike(R column, Object val)
notLike(boolean condition, R column, Object val)
likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)
likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)
isNull(R column)
isNull(boolean condition, R column)
isNotNull(R column)
isNotNull(boolean condition, R column)
in(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)
- 字段 IN (value.get(0), value.get(1), …)
- 例:
in("age",{1,2,3})
—->age in (1,2,3)
in(R column, Object... values)
in(boolean condition, R column, Object... values)
notIn(R column, Collection<?> value)
notIn(boolean condition, R column, Collection<?> value)
- 字段 IN (value.get(0), value.get(1), …)
- 例:
notIn("age",{1,2,3})
—->age not in (1,2,3)
notIn(R column, Object... values)
notIn(boolean condition, R column, Object... values)
inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
- 字段 IN ( sql语句 )
- 例:
inSql("age", "1,2,3,4,5,6")
—->age in (1,2,3,4,5,6)
- 例:
inSql("id", "select id from table where id < 3")
—->id in (select id from table where id < 3)
notInSql
notInSql(R column, String inValue)
notInSql(boolean condition, R column, String inValue)
- 字段 NOT IN ( sql语句 )
- 例:
notInSql("age", "1,2,3,4,5,6")
—->age not in (1,2,3,4,5,6)
- 例:
notInSql("id", "select id from table where id < 3")
—->age not in (select id from table where id < 3)
groupBy
groupBy(R... columns)
groupBy(boolean condition, R... columns)
orderByAsc(R... columns)
orderByAsc(boolean condition, R... columns)
orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)
- 排序:ORDER BY 字段, … DESC
例:
orderByDesc("id", "name")
—->order by id DESC,name DESC
orderBy
orderBy(boolean condition, boolean isAsc, R... columns)
排序:ORDER BY 字段, …
- 例:
orderBy(true, true, "id", "name")
—->order by id ASC,name ASC
having
having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
- HAVING ( sql语句 )
- 例:
having("sum(age) > 10")
—->having sum(age) > 10
- 例:
having("sum(age) > {0}", 11)
—->having sum(age) > 11
or
or()
or(boolean condition)
- 拼接 OR
注意事项:
主动调用or
表示紧接着下一个方法不是用and
连接!(不调用or
则默认为使用and
连接)
- 例:
eq("id",1).or().eq("name","老王")
—->id = 1 or name = '老王'
or(Consumer<Param> consumer)
or(boolean condition, Consumer<Param> consumer)
and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)
- AND 嵌套
- 例:
and(i -> i.eq("name", "李白").ne("status", "活着"))
—->and (name = '李白' and status <> '活着')
nested
nested(Consumer<Param> consumer)
nested(boolean condition, Consumer<Param> consumer)
- 正常嵌套 不带 AND 或者 OR
- 例:
nested(i -> i.eq("name", "李白").ne("status", "活着"))
—->(name = '李白' and status <> '活着')
apply
apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)
- 拼接 sql
注意事项:
该方法可用于数据库函数 动态入参的params
对应前面applySql
内部的{index}
部分.这样是不会有sql注入风险的,反之会有!
- 例:
apply("id = 1")
—->id = 1
- 例:
apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
—->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
- 例:
apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")
—->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
last
last(String lastSql)
last(boolean condition, String lastSql)
- 无视优化规则直接拼接到 sql 的最后
注意事项:
只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用
exists(String existsSql)
exists(boolean condition, String existsSql)
- 拼接 EXISTS ( sql语句 )
- 例:
exists("select id from table where age = 1")
—->exists (select id from table where age = 1)
notExists
notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)
- 拼接 NOT EXISTS ( sql语句 )
- 例:
notExists("select id from table where age = 1")
—->not exists (select id from table where age = 1)
QueryWrapper
说明:
继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件
及 LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() 方法获取select
select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
- 设置查询字段
说明:
以上方分法为两类.
第二类方法为:过滤查询字段(主键除外),入参不包含 class 的调用前需要wrapper
内的entity
属性有值! 这两类方法重复调用以最后一次为准
- 例:
select("id", "name", "age")
例:
select(i -> i.getProperty().startsWith("test"))
excludeColumns @Deprecated
排除查询字段
UpdateWrapper
说明:
继承自 AbstractWrapper
,自身的内部属性 entity
也用于生成 where 条件
及 LambdaUpdateWrapper
, 可以通过 new UpdateWrapper().lambda()
方法获取!
set
set(String column, Object val)
set(boolean condition, String column, Object val)
- SQL SET 字段
- 例:
set("name", "老李头")
- 例:
set("name", "")
—->数据库字段值变为空字符串 例:
set("name", null)
—->数据库字段值变为null
setSql
setSql(String sql)
设置 SET 部分 SQL
-
lambda
获取
LambdaWrapper
在QueryWrapper
中是获取LambdaQueryWrapper
在UpdateWrapper
中是获取LambdaUpdateWrapper
使用 Wrapper 自定义SQL
需求来源:
在使用了mybatis-plus
之后, 自定义SQL的同时也想使用Wrapper
的便利应该怎么办? 在mybatis-plus
版本3.0.7
得到了完美解决 版本需要大于或等于3.0.7
, 以下两种方案取其一即可Service.java
mysqlMapper.getAll(Wrappers.<MysqlData>lambdaQuery().eq(MysqlData::getGroup, 1));
方案一 注解方式 Mapper.java
@Select("select * from mysql_data ${ew.customSqlSegment}")
List<MysqlData> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);
方案二 XML形式 Mapper.xml
<select id="getAll" resultType="MysqlData">
SELECT * FROM mysql_data ${ew.customSqlSegment}
</select>
分页插件
示例工程:
👉 mybatis-plus-sample-pagination<!-- spring xml 方式 -->
<property name="plugins">
<array>
<bean class="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor">
<property name="sqlParser" ref="自定义解析类、可以没有"/>
<property name="dialectClazz" value="自定义方言类、可以没有"/>
<!-- COUNT SQL 解析.可以没有 -->
<property name="countSqlParser" ref="countSqlParser"/>
</bean>
</array>
</property>
<bean id="countSqlParser" class="com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize">
<!-- 设置为 true 可以优化部分 left join 的sql -->
<property name="optimizeJoin" value="true"/>
</bean>
//Spring boot方式
@EnableTransactionManagement
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
// paginationInterceptor.setOverflow(false);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
// paginationInterceptor.setLimit(500);
// 开启 count 的 join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}
XML 自定义分页
UserMapper.java 方法内容
public interface UserMapper {//可以继承或者不继承BaseMapper
/**
* <p>
* 查询 : 根据state状态查询用户列表,分页显示
* </p>
*
* @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位(你可以继承Page实现自己的分页对象)
* @param state 状态
* @return 分页对象
*/
IPage<User> selectPageVo(Page<?> page, Integer state);
}
UserMapper.xml 等同于编写一个普通 list 查询,mybatis-plus 自动替你分页
<select id="selectPageVo" resultType="com.baomidou.cloud.entity.UserVo">
SELECT id,name FROM user WHERE state=#{state}
</select>
UserServiceImpl.java 调用分页方法
public IPage<User> selectUserPage(Page<User> page, Integer state) {
// 不进行 count sql 优化,解决 MP 无法自动优化 SQL 问题,这时候你需要自己查询 count 部分
// page.setOptimizeCountSql(false);
// 当 total 为小于 0 或者设置 setSearchCount(false) 分页插件不会进行 count 查询
// 要点!! 分页返回的对象与传入的对象是同一个
return userMapper.selectPageVo(page, state);
}
Sequence主键
主键生成策略必须使用INPUT
支持父类定义@KeySequence子类继承使用支持主键类型指定(3.3.0开始自动识别主键类型)
内置支持:DB2KeyGenerator
- H2KeyGenerator
- KingbaseKeyGenerator
- OracleKeyGenerator
- PostgreKeyGenerator
如果内置支持不满足你的需求,可实现IKeyGenerator接口来进行扩展.
举个栗子
@KeySequence(value = "SEQ_ORACLE_STRING_KEY", clazz = String.class)
public class YourEntity {
@TableId(value = "ID_STR", type = IdType.INPUT)
private String idStr;
}
Spring-Boot
方式一:使用配置类
@Bean
public IKeyGenerator keyGenerator() {
return new H2KeyGenerator();
}
方式二:通过MybatisPlusPropertiesCustomizer自定义
@Bean
public MybatisPlusPropertiesCustomizer plusPropertiesCustomizer() {
return plusProperties -> plusProperties.getGlobalConfig().getDbConfig().setKeyGenerator(new H2KeyGenerator());
}
Spring
方式一: XML配置
<bean id="globalConfig" class="com.baomidou.mybatisplus.core.config.GlobalConfig">
<property name="dbConfig" ref="dbConfig"/>
</bean>
<bean id="dbConfig" class="com.baomidou.mybatisplus.core.config.GlobalConfig.DbConfig">
<property name="keyGenerator" ref="keyGenerator"/>
</bean>
<bean id="keyGenerator" class="com.baomidou.mybatisplus.extension.incrementer.H2KeyGenerator"/>
方式二:注解配置
@Bean
public GlobalConfig globalConfig() {
GlobalConfig conf = new GlobalConfig();
conf.setDbConfig(new GlobalConfig.DbConfig().setKeyGenerator(new H2KeyGenerator()));
return conf;
}
自定义ID生成器
自3.3.0开始,默认使用雪花算法+UUID(不含中划线)
自定义示例工程:
- spring-boot示例 :传送门
| 方法 | 主键生成策略 | 主键类型 | 说明 |
| —- | —- | —- | —- |
| nextId | ASSIGN_ID,
ID_WORKER,ID_WORKER_STR| Long,Integer,String | 支持自动转换为String类型,但数值类型不支持自动转换,需精准匹配,例如返回Long,实体主键就不支持定义为Integer | | nextUUID | ASSIGN_UUID,UUID| String | 默认不含中划线的UUID生成 |
Spring-Boot
方式一:声明为Bean供Spring扫描注入
@Component
public class CustomIdGenerator implements IdentifierGenerator {
@Override
public Long nextId(Object entity) {
//可以将当前传入的class全类名来作为bizKey,或者提取参数来生成bizKey进行分布式Id调用生成.
String bizKey = entity.getClass().getName();
//根据bizKey调用分布式ID生成
long id = ....;
//返回生成的id值即可.
return id;
}
}
方式二:使用配置类
@Bean
public IdentifierGenerator idGenerator() {
return new CustomIdGenerator();
}
方式三:通过MybatisPlusPropertiesCustomizer自定义
@Bean
public MybatisPlusPropertiesCustomizer plusPropertiesCustomizer() {
return plusProperties -> plusProperties.getGlobalConfig().setIdentifierGenerator(new CustomIdGenerator());
}
Spring
方式一: XML配置
<bean name="customIdGenerator" class="com.baomidou.samples.incrementer.CustomIdGenerator"/>
<bean id="globalConfig" class="com.baomidou.mybatisplus.core.config.GlobalConfig">
<property name="identifierGenerator" ref="customIdGenerator"/>
</bean>
方式二:注解配置
@Bean
public GlobalConfig globalConfig() {
GlobalConfig conf = new GlobalConfig();
conf.setIdentifierGenerator(new CustomIdGenerator());
return conf;
}
插件扩展
热加载
3.0.6
版本上移除了该功能,不过最新快照版已加回来并打上废弃标识,3.1.0
版本上已完全移除
开启动态加载 mapper.xml
- 多数据源配置多个 MybatisMapperRefresh 启动 bean
默认情况下,eclipse保存会自动编译,idea需自己手动编译一次
参数说明:
sqlSessionFactory:session工厂
mapperLocations:mapper匹配路径
enabled:是否开启动态加载 默认:false
delaySeconds:项目启动延迟加载时间 单位:秒 默认:10s
sleepSeconds:刷新时间间隔 单位:秒 默认:20s
提供了两个构造,挑选一个配置进入spring配置文件即可:
构造1:
<bean class="com.baomidou.mybatisplus.spring.MybatisMapperRefresh">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
<constructor-arg name="mapperLocations" value="classpath*:mybatis/mappers/*/*.xml"/>
<constructor-arg name="enabled" value="true"/>
</bean>
构造2:
<bean class="com.baomidou.mybatisplus.spring.MybatisMapperRefresh">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
<constructor-arg name="mapperLocations" value="classpath*:mybatis/mappers/*/*.xml"/>
<constructor-arg name="delaySeconds" value="10"/>
<constructor-arg name="sleepSeconds" value="20"/>
<constructor-arg name="enabled" value="true"/>
</bean>
逻辑删除
SpringBoot 配置方式:
application.yml 加入配置(如果你的默认值和mp默认的一样,该配置可无):
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
注册 Bean(3.1.1开始不再需要这一步):import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisPlusConfiguration {
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector();
}
}
实体类字段上加上
@TableLogic
注解@TableLogic
private Integer deleted;
效果: 使用mp自带方法删除和查找都会附带逻辑删除功能 (自己写的xml不会)
example
删除时 update user set deleted=1 where id =1 and deleted=0
查找时 select * from user where deleted=0
全局逻辑删除: 3.3.0开始支持
如果公司代码比较规范,比如统一了全局都是flag为逻辑删除字段。
使用此配置则不需要在实体类上添加 @TableLogic。
但如果实体类上有 @TableLogic 则以实体上的为准,忽略全局。 即先查找注解再查找全局,都没有则此表没有逻辑删除。mybatis-plus:
global-config:
db-config:
logic-delete-field: flag #全局逻辑删除字段值
附件说明
逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
- 如果你需要再查出来就不应使用逻辑删除,而是以一个状态去表示。
如: 员工离职,账号被锁定等都应该是一个状态字段,此种场景不应使用逻辑删除。
- 若确需查找删除数据,如老板需要查看历史所有数据的统计汇总信息,请单独手写sql。
通用枚举
解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!自
3.1.0
开始,如果你无需使用原生枚举,可配置默认枚举来省略扫描通用枚举配置 默认枚举配置- 升级说明:
3.1.0
以下版本改变了原生默认行为,升级时请将默认枚举设置为EnumOrdinalTypeHandler
- 影响用户:
实体中使用原生枚举 - 其他说明:
配置枚举包扫描的时候能提前注册使用注解枚举的缓存
- 升级说明:
1、声明通用枚举属性
方式一: 使用 @EnumValue 注解枚举属性 完整示例
public enum GradeEnum {
PRIMARY(1, "小学"), SECONDORY(2, "中学"), HIGH(3, "高中");
GradeEnum(int code, String descp) {
this.code = code;
this.descp = descp;
}
@EnumValue//标记数据库存的值是code
private final int code;
//。。。
}
方式二: 枚举属性,实现 IEnum 接口如下:
public enum AgeEnum implements IEnum<Integer> {
ONE(1, "一岁"),
TWO(2, "二岁"),
THREE(3, "三岁");
private int value;
private String desc;
@Override
public Integer getValue() {
return this.value;
}
}
实体属性使用枚举类型
public class User {
/**
* 名字
* 数据库字段: name varchar(20)
*/
private String name;
/**
* 年龄,IEnum接口的枚举处理
* 数据库字段:age INT(3)
*/
private AgeEnum age;
/**
* 年级,原生枚举(带{@link com.baomidou.mybatisplus.annotation.EnumValue}):
* 数据库字段:grade INT(2)
*/
private GradeEnum grade;
}
2、配置扫描通用枚举
- 注意!! spring mvc 配置参考,安装集成 MybatisSqlSessionFactoryBean 枚举包扫描,spring boot 例子配置如下:
示例工程:
👉 mybatisplus-spring-boot
配置文件 resources/application.yml
mybatis-plus:
# 支持统配符 * 或者 ; 分割
typeEnumsPackage: com.baomidou.springboot.entity.enums
....
如何序列化枚举值为数据库存储值?
Jackson
一、重写toString方法
springboot
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer(){
return builder -> builder.featuresToEnable(
SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
}
jackson
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
以上两种方式任选其一,然后在枚举中复写toString方法即可.
二、注解处理
public enum GradeEnum {
PRIMARY(1, "小学"), SECONDORY(2, "中学"), HIGH(3, "高中");
GradeEnum(int code, String descp) {
this.code = code;
this.descp = descp;
}
@EnumValue
@JsonValue //标记响应json值
private final int code;
}
Fastjson
一、重写toString方法
全局处理方式
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(SerializerFeature.WriteEnumUsingToString);
全局部处理方式
@JSONField(serialzeFeatures= SerializerFeature.WriteEnumUsingToString)
private UserStatus status;
以上两种方式任选其一,然后在枚举中复写toString方法即可.
字段类型处理器
类型处理器,用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值,本文讲解
mybaits-plus
内置常用类型处理器如何通过TableField
注解快速注入到mybatis
容器中。
示例工程:
👉 mybatis-plus-sample-typehandler
JSON 字段类型
@Data
@Accessors(chain = true)
@TableName(autoResultMap = true)
public class User {
private Long id;
...
/**
* 注意!! 必须开启映射注解
*
* @TableName(autoResultMap = true)
*
* 以下两种类型处理器,二选一 也可以同时存在
*
* 注意!!选择对应的 JSON 处理器也必须存在对应 JSON 解析依赖包
*/
@TableField(typeHandler = JacksonTypeHandler.class)
// @TableField(typeHandler = FastjsonTypeHandler.class)
private OtherInfo otherInfo;
}
该注解对应了 XML 中写法为
<result column="other_info" jdbcType="VARCHAR" property="otherInfo" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
自动填充功能
实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
注解填充字段
@TableField(.. fill = FieldFill.INSERT)
生成器策略部分也可以配置!public class User {
// 注意!这里需要标记为填充字段
@TableField(.. fill = FieldFill.INSERT)
private String fillField;
....
}
自定义实现类 MyMetaObjectHandler
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug请升级到之后的版本如`3.3.1.8-SNAPSHOT`)
/* 上面选其一使用,下面的已过时(注意 strictInsertFill 有多个方法,详细查看源码) */
//this.setFieldValByName("operator", "Jerry", metaObject);
//this.setInsertFieldValByName("operator", "Jerry", metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug请升级到之后的版本如`3.3.1.8-SNAPSHOT`)
/* 上面选其一使用,下面的已过时(注意 strictUpdateFill 有多个方法,详细查看源码) */
//this.setFieldValByName("operator", "Tom", metaObject);
//this.setUpdateFieldValByName("operator", "Tom", metaObject);
}
}
注意事项:
字段必须声明
TableField
注解,属性fill
选择对应策略,该声明告知Mybatis-Plus
需要预留注入SQL
字段- 填充处理器
MyMetaObjectHandler
在 Spring Boot 中需要声明@Component
或@Bean
注入 - 要想根据注解
FieldFill.xxx
和字段名
以及字段类型
来区分必须使用父类的strictInsertFill
或者strictUpdateFill
方法 不需要根据任何来区分可以使用父类的
fillStrategy
方法public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
}
Sql 注入器
注入器配置
全局配置sqlInjector
用于注入ISqlInjector
接口的子类,实现自定义方法注入。
参考默认注入器 DefaultSqlInjectorSQL 自动注入器接口
ISqlInjector
public interface ISqlInjector {
/**
* <p>
* 检查SQL是否注入(已经注入过不再注入)
* </p>
*
* @param builderAssistant mapper 信息
* @param mapperClass mapper 接口的 class 对象
*/
void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass);
}
自定义自己的通用方法可以实现接口
ISqlInjector
也可以继承抽象类AbstractSqlInjector
注入通用方法SQL 语句
然后继承BaseMapper
添加自定义方法,全局配置sqlInjector
注入 MP 会自动将类所有方法注入到mybatis
容器中。
攻击 SQL 阻断解析器
作用!阻止恶意的全表更新删除
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
...
List<ISqlParser> sqlParserList = new ArrayList<>();
// 攻击 SQL 阻断解析器、加入解析链
sqlParserList.add(new BlockAttackSqlParser() {
@Override
public void processDelete(Delete delete) {
// 如果你想自定义做点什么,可以重写父类方法像这样子
if ("user".equals(delete.getTable().getName())) {
// 自定义跳过某个表,其他关联表可以调用 delete.getTables() 判断
return ;
}
super.processDelete(delete);
}
});
paginationInterceptor.setSqlParserList(sqlParserList);
...
return paginationInterceptor;
}
性能分析插件
性能分析拦截器,用于输出每条 SQL 语句及其执行时间 该插件
3.2.0
以上版本移除推荐使用第三方扩展 执行SQL分析打印 功能
使用如下:
<plugins>
....
<!-- SQL 执行性能分析,开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长 -->
<plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor">
<property name="maxTime" value="100" />
<!--SQL是否格式化 默认false-->
<property name="format" value="true" />
</plugin>
</plugins>
//Spring boot方式
@EnableTransactionManagement
@Configuration
@MapperScan("com.baomidou.cloud.service.*.mapper*")
public class MybatisPlusConfig {
/**
* SQL执行效率插件
*/
@Bean
@Profile({"dev","test"})// 设置 dev test 环境开启
public PerformanceInterceptor performanceInterceptor() {
return new PerformanceInterceptor();
}
}
注意!参数说明:
参数:maxTime SQL 执行最大时长,超过自动停止运行,有助于发现问题。
- 参数:format SQL SQL是否格式化,默认false。
- 该插件只用于开发环境,不建议生产环境使用。
执行 SQL 分析打印
该功能依赖
p6spy
组件,完美的输出打印 SQL 及执行时长3.1.0
以上版本
示例工程:
👉 mybatis-plus-sample-crud
- p6spy 依赖引入
Maven:
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>最新版本</version>
</dependency>
Gradle:
compile group: 'p6spy', name: 'p6spy', version: '最新版本'
application.yml 配置:
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:h2:mem:test
...
spy.properties 配置:
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
注意!
driver-class-name 为 p6spy 提供的驱动类
- url 前缀为 jdbc:p6spy 跟着冒号为对应数据库连接地址
- 打印出sql为null,在excludecategories增加commit
- 批量操作不打印sql,去除excludecategories中的batch
- 批量操作打印重复的问题请使用MybatisPlusLogFactory (3.2.1新增)
-
乐观锁插件
主要适用场景
意图:
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式: 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时, set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
1.插件配置
spring xml:
<bean class="com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor"/>
spring boot:
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
2.注解实体字段 @Version
必须要!
@Version
private Integer version;
特别说明:
- 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
- 整数类型下
newVersion = oldVersion + 1
newVersion
会回写到entity
中- 仅支持
updateById(id)
与update(entity, wrapper)
方法 在
update(entity, wrapper)
方法下,wrapper
不能复用!!!示例
示例Java代码(参考test case代码)
int id = 100;
int version = 2;
User u = new User();
u.setId(id);
u.setVersion(version);
u.setXXX(xxx);
if(userService.updateById(u)){
System.out.println("Update successfully");
}else{
System.out.println("Update failed due to modified by others");
}
示例SQL原理
update tbl_user set name = 'update',version = 3 where id = 100 and version = 2
动态数据源
一个基于springboot的快速集成多数据源的启动器-
简介
dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。
其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.0.x。最新版为
示例项目 可参考项目下的samples目录。
示例项目 可参考项目下的samples目录。
示例项目 可参考项目下的samples目录。优势
网上关于动态数据源的切换的文档有很多,核心只有两种。
- 构建多套环境,优势是方便控制也容易集成一些简单的分布式事务,缺点是非动态同时代码量较多,配置难度大。
- 基于spring提供原生的
AbstractRoutingDataSource
,参考一些文档自己实现切换。
如果你的数据源较少,场景不复杂,选择以上任意一种都可以。如果你需要更多特性,请尝试本动态数据源。
- 数据源分组,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
- 简单集成Druid数据源监控多数据源,简单集成Mybatis-Plus简化单表,简单集成P6sy格式化sql,简单集成Jndi数据源。
- 简化Druid和HikariCp配置,提供全局参数配置。
- 提供自定义数据源来源(默认使用yml或properties配置)。
- 项目启动后能动态增减数据源。
- 使用spel动态参数解析数据源,如从session,header和参数中获取数据源。(多租户架构神器)
- 多层数据源嵌套切换。(一个业务ServiceA调用ServiceB,ServiceB调用ServiceC,每个Service都是不同的数据源)
-
劣势
不能使用多数据源事务(同一个数据源下能使用事务),网上其他方案也都不能提供。
如果你需要使用到分布式事务,那么你的架构应该到了微服务化的时候了。
如果呼声强烈,项目达到800 star,作者考虑集成分布式事务。
PS: 如果您只是几个数据库但是有强烈的需求分布式事务,建议还是使用传统方式自己构建多套环境集成atomic这类,网上百度很多。约定
本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何CRUD。
- 配置文件所有以下划线
_
分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。 - 切换数据源即可是组名,也可是具体数据源名称,切换时默认采用负载均衡机制切换。
- 默认的数据源名称为 master ,你可以通过spring.datasource.dynamic.primary修改。
-
建议
强烈建议在 主从模式 下遵循普遍的规则,以便他人能更轻易理解你的代码。
主数据库 建议 只执行INSERT
UPDATE
DELETE
操作。
从数据库 建议 只执行SELECT
操作。使用方法
引入dynamic-datasource-spring-boot-starter。
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
配置数据源。
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
datasource:
master:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
slave_1:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
slave_2:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://xx.xx.xx.xx:3308/dynamic
#......省略
#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
# 多主多从 纯粹多库(记得设置primary) 混合配置
spring: spring: spring:
datasource: datasource: datasource:
dynamic: dynamic: dynamic:
datasource: datasource: datasource:
master_1: mysql: master:
master_2: oracle: slave_1:
slave_1: sqlserver: slave_2:
slave_2: postgresql: oracle_1:
slave_3: h2: oracle_2:
使用 @DS 切换数据源。
@DS 可以注解在方法上和类上,同时存在方法注解优先于类上注解。
注解在service实现或mapper接口方法上,但强烈不建议同时在service和mapper注解。 (可能会有问题)
注解 | 结果 |
---|---|
没有@DS | 默认数据源 |
@DS(“dsName”) | dsName可以为组名也可以为具体某个库的名称 |
@Service
@DS("slave")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public List<Map<String, Object>> selectAll() {
return jdbcTemplate.queryForList("select * from user");
}
@Override
@DS("slave_1")
public List<Map<String, Object>> selectByCondition() {
return jdbcTemplate.queryForList("select * from user where age >10");
}
}
在mybatis环境下也可注解在mapper接口层。
@DS("slave")
public interface UserMapper {
@Insert("INSERT INTO user (name,age) values (#{name},#{age})")
boolean addUser(@Param("name") String name, @Param("age") Integer age);
@Update("UPDATE user set name=#{name}, age=#{age} where id =#{id}")
boolean updateUser(@Param("id") Integer id, @Param("name") String name, @Param("age") Integer age);
@Delete("DELETE from user where id =#{id}")
boolean deleteUser(@Param("id") Integer id);
@Select("SELECT * FROM user")
@DS("slave_1")
List<User> selectAll();
}
赶紧集成体验一下吧! 如果需要更多功能请继续往下看!
- Druid集成,MybatisPlus集成,动态增减数据源等等更多更细致的文档在这里 点击查看
- 项目Javadoc一览 点击查看
分布式事务
暂时支持 rabbit 实现可靠消息分布式事务
3.1.1
以上版本
示例工程:
👉 mybatis-plus-sample-dts-rabbit
示例工程:
👉 mybatis-plus-sample-tenant
👉 mybatisplus-spring-boot
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
/*
* 【测试多租户】 SQL 解析处理拦截器<br>
* 这里固定写成住户 1 实际情况你可以从cookie读取,因此数据看不到 【 麻花藤 】 这条记录( 注意观察 SQL )<br>
*/
List<ISqlParser> sqlParserList = new ArrayList<>();
TenantSqlParser tenantSqlParser = new TenantSqlParser();
tenantSqlParser.setTenantHandler(new TenantHandler() {
@Override
public Expression getTenantId(boolean where) {
// 该 where 条件 3.2.0 版本开始添加的,用于分区是否为在 where 条件中使用
// 此判断用于支持返回多个租户 ID 场景,具体使用查看示例工程
return new LongValue(1L);
}
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public boolean doTableFilter(String tableName) {
// 这里可以判断是否过滤表
/*
if ("user".equals(tableName)) {
return true;
}*/
return false;
}
});
sqlParserList.add(tenantSqlParser);
paginationInterceptor.setSqlParserList(sqlParserList);
paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
@Override
public boolean doFilter(MetaObject metaObject) {
MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
// 过滤自定义查询此时无租户信息约束【 麻花藤 】出现
if ("com.baomidou.springboot.mapper.UserMapper.selectListBySQL".equals(ms.getId())) {
return true;
}
return false;
}
});
return paginationInterceptor;
}
- 相关 SQL 解析如多租户可通过
@SqlParser(filter=true)
排除 SQL 解析,注意!!全局配置 sqlParserCache 设置为 true 才生效。(3.1.1开始不再需要这一步)# 开启 SQL 解析缓存注解生效
mybatis-plus:
global-config:
sql-parser-cache: true
动态表名 SQL 解析器
该功能解决动态表名支持
3.1.1
以上版本
简单示例:
👉 mybatis-plus-sample-dynamic-tablename
源码文件:
👉 DynamicTableNameParser
- 具体使用参考多租户
实现 ITableNameHandler 接口注入到 DynamicTableNameParser 处理器链中,将动态表名解析器注入到 MP 解析链。
注意事项:
- 原理为解析替换设定表名为处理器的返回表名,表名建议可以定义复杂一些避免误替换
例如:真实表名为 user 设定为 mp_dt_user 处理器替换为 user_2019 等
MybatisX 快速开发插件
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入mybatisx
搜索并安装。
如果各位觉得好用,请为该插件打一个五分好评 哦! 源码地址:MybatisX 源码功能
Java 与 XML 调回跳转
-
计划支持
连接数据源之后 xml 里自动提示字段
- sql 增删改查
- 集成 MP 代码生成
-
常见问题
如何排除非表中字段?
以下三种方式选择一种即可:
使用
transient
修饰private transient String noColumn;
使用
static
修饰private static String noColumn;
使用
TableField
注解@TableField(exist=false)
private String noColumn;
排除实体父类属性
/**
* 忽略父类 createTime 字段映射
*/
private transient String createTime;
出现
Invalid bound statement (not found)
异常不要怀疑,正视自己,这个异常肯定是你插入的姿势不对……
检查是不是引入 jar 冲突
检查 Mapper.java 的扫描路径
方法一:在
Configuration
类上使用注解MapperScan
@Configuration
@MapperScan("com.yourpackage.*.mapper")
public class YourConfigClass{
...
}
方法二:在
Configuration
类里面,配置MapperScannerConfigurer
(查看示例)@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer scannerConfigurer = new MapperScannerConfigurer();
//可以通过环境变量获取你的mapper路径,这样mapper扫描可以通过配置文件配置了
scannerConfigurer.setBasePackage("com.yourpackage.*.mapper");
return scannerConfigurer;
}
检查是否指定了主键?如未指定,则会导致
selectById
相关 ID 无法操作,请用注解@TableId
注解表 ID 主键。当然@TableId
注解可以没有!但是你的主键必须叫 id(忽略大小写)- SqlSessionFactory不要使用原生的,请使用MybatisSqlSessionFactory
检查是否自定义了SqlInjector,是否复写了getMethodList()方法,该方法里是否注入了你需要的方法(可参考DefaultSqlInjector)
自定义 SQL 无法执行
问题描述:指在 XML 中里面自定义 SQL,却无法调用。本功能同
MyBatis
一样需要配置 XML 扫描路径:Spring MVC 配置(参考mybatisplus-spring-mvc)
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="xxx.entity" />
<property name="mapperLocations" value="classpath*:/mybatis/*/*.xml"/>
...
</bean>
Spring Boot 配置(参考mybatisplus-spring-boot)
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
对于
IDEA
系列编辑器,XML 文件是不能放在 java 文件夹中的,IDEA 默认不会编译源码文件夹中的 XML 文件,可以参照以下方式解决:- 将配置文件放在 resource 文件夹中
- 对于 Maven 项目,可指定 POM 文件的 resource
注意!Maven 多模块项目的扫描路径需以<build>
<resources>
<resource>
<!-- xml放在java目录下-->
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<!--指定资源的位置(xml放在resources下,可以不用指定)-->
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
</build>
classpath*:
开头 (即加载多个 jar 包下的 XML 文件)启动时异常
异常一:
java.lang.ClassCastException: sun.reflect.generics.reflectiveObjects.TypeVariableImpl cannot be cast to java.lang.Class
MapperScan 需要排除 com.baomidou.mybatisplus.mapper.BaseMapper 类 及其 子类(自定义公共 Mapper),比如:
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SuperMapper<T> extends BaseMapper<T> {
// your methods
}
异常二:
Injection of autowired
原因:低版本不支持泛型注入,请升级 Spring 版本到 4+ 以上。
异常三:
java.lang.NoSuchMethodError: org.apache.ibatis.session.Configuration.getDefaultScriptingLanguageInstance() Lorg/apache/ibatis/scripting/LanguageDriver
版本引入问题:3.4.1 版本里没有,3.4.2 里面才有!
关于 Long 型主键填充不生效的问题
检查是不是用了
long
而不是Long
!long
类型默认值为 0,而 MP 只会判断是否为null
ID_WORKER 生成主键太长导致 js 精度丢失
JavaScript 无法处理 Java 的长整型 Long 导致精度丢失,具体表现为主键最后两位永远为 0,解决思路: Long 转为 String 返回
FastJson 处理方式
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter fastJsonConverter = new FastJsonHttpMessageConverter();
FastJsonConfig fjc = new FastJsonConfig();
// 配置序列化策略
fjc.setSerializerFeatures(SerializerFeature.BrowserCompatible);
fastJsonConverter.setFastJsonConfig(fjc);
converters.add(fastJsonConverter);
}
JackJson 处理方式
方式一
// 注解处理,这里可以配置公共 baseEntity 处理
@JsonSerialize(using=ToStringSerializer.class)
public long getId() {
return id;
}
方式二
// 全局配置序列化返回 JSON 处理
final ObjectMapper objectMapper = new ObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(Long.class, ToStringSerializer.instance);
objectMapper.registerModule(simpleModule);
比较一般的处理方式:增加一个
public String getIdStr()
方法,前台获取idStr
插入或更新的字段有 空字符串 或者
null
FieldStrategy 有三种策略:
IGNORED:忽略
- NOT_NULL:非 NULL,默认策略
- NOT_EMPTY:非空
当用户有更新字段为 空字符串 或者 null
的需求时,需要对 FieldStrategy
策略进行调整:
- 方式一:调整全局的验证策略
注入配置 GlobalConfiguration 属性 fieldStrategy 方式二:调整字段验证注解
根据具体情况,在需要更新的字段中调整验证注解,如验证非空:@TableField(strategy=FieldStrategy.NOT_EMPTY)
方式三:使用
UpdateWrapper
(3.x)
使用以下方法来进行更新或插入操作://updateAllColumnById(entity) // 全部字段更新: 3.0已经移除
mapper.update(
new User().setName("mp").setAge(3),
Wrappers.<User>lambdaUpdate()
.set(User::getEmail, null) //把email设置成null
.eq(User::getId, 2)
);
//也可以参考下面这种写法
mapper.update(
null,
Wrappers.<User>lambdaUpdate()
.set(User::getAge, 3)
.set(User::getName, "mp")
.set(User::getEmail, null) //把email设置成null
.eq(User::getId, 2)
);
字段类型为
bit
、tinyint(1)
时映射为boolean
类型默认mysql驱动会把tinyint(1)字段映射为boolean: 0=false, 非0=true
MyBatis 是不会自动处理该映射,如果不想把tinyint(1)映射为boolean类型:修改类型tinyint(1)为tinyint(2)或者int
- 需要修改请求连接添加参数
tinyInt1isBit=false
,如下:jdbc:mysql://127.0.0.1:3306/mp?tinyInt1isBit=false
出现 2 个
原因:配了 2 个分页拦截器! 检查配置文件或者代码,只留一个!limit
语句insert 后如何返回主键
insert 后主键会自动 set 到实体的 ID 字段,所以你只需要 getId() 就好MP 如何查指定的几个字段
EntityWrapper.sqlSelect 配置你想要查询的字段//2.x
EntityWrapper<H2User> ew = new EntityWrapper<>();
ew.setSqlSelect("test_id as id, name, age");//只查询3个字段
List<H2User> list = userService.selectList(ew);
for(H2User u:list){
Assert.assertNotNull(u.getId());
Assert.assertNotNull(u.getName());
Assert.assertNull(u.getPrice()); // 这个字段没有查询出来
}
//3.x
mapper.selectList(
Wrappers.<User>lambdaQuery()
.select(User::getId, User::getName)
);
//或者使用QueryWrapper
mapper.selectList(
new QueryWrapper<User>()
.select("id","name")
);
mapper 层二级缓存问题
我们建议缓存放到 service 层,你可以自定义自己的 BaseServiceImpl 重写注解父类方法,继承自己的实现。
当然如果你是一个极端分子,请使用 CachePaginationInterceptor 替换默认分页,这样支持分页缓存。mapper 层二级缓存刷新问题
如果你按照 mybatis 的方式配置第三方二级缓存,并且使用 2.0.9 以上的版本,则会发现自带的方法无法更新缓存内容,那么请按如下方式解决(二选一):
1.在代码中 mybatis 的 mapper 层添加缓存注释,声明 implementation 或 eviction 的值为 cache 接口的实现类
2.在对应的 mapper.xml 中将原有注释修改为链接式声明,以保证 xml 文件里的缓存能够正常@CacheNamespace(implementation=MybatisRedisCache.class,eviction=MybatisRedisCache.class)
public interface DataResourceMapper extends BaseMapper<DataResource>{}
<cache-ref namespace="com.mst.cms.dao.DataResourceMapper"></cache-ref>
Cause: org.apache.ibatis.type.TypeException:Error setting null for parameter #1 with JdbcType OTHER
配置 jdbcTypeForNull=NULL Spring Bean 配置方式:
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);//开启下划线转驼峰
sqlSessionFactory.setConfiguration(configuration);
yml 配置
mybatis-plus:
configuration:
jdbc-type-for-null: 'null'
自定义 sql 里使用 Page 对象传参无法获取
Page 对象是继承 RowBounds,是 Mybatis 内置对象,无法在 mapper 里获取 请使用自定义 Map/对象,或者通过@Param(“page”) int page,size 来传参
如何使用:【Map下划线自动转驼峰】
指的是:resultType="java.util.Map"
spring boot
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return i -> i.setObjectWrapperFactory(new MybatisMapWrapperFactory());
}
在 wrapper 中如何使用
limit
限制 SQL// 取 1 条数据
wrapper.last("limit 1");
通用 insertBatch 为什么放在 service 层处理
SQL 长度有限制海量数据量单条 SQL 无法执行,就算可执行也容易引起内存泄露 JDBC 连接超时等
- 不同数据库对于单条 SQL 批量语法不一样不利于通用
目前的解决方案:循环预处理批量提交,虽然性能比单 SQL 慢但是可以解决以上问题。
逻辑删除下 自动填充 功能没有效果
自动填充的实现方式是填充到入参的
entity
内,由于baseMapper
提供的删除接口入参不是entity
所以逻辑删除无效- 如果你想要使用自动填充有效:
- 方式一: 使用update方法:
UpdateWrapper.set("logicDeleteColumn","deleteValue")
- 方式二: 配合Sql注入器
并使用我们提供的com.baomidou.mybatisplus.extension.injector.methods.LogicDeleteByIdWithFill
类
注意该类只能填充指定了自动填充的字段,其他字段无效
- 方式一: 使用update方法:
方式2下: Java Config Bean 配置
配置自定义的 SqlInjector
@Bean
public LogicSqlInjector logicSqlInjector(){
return new LogicSqlInjector() {
/**
* 注入自定义全局方法
*/
@Override
public List<AbstractMethod> getMethodList() {
List<AbstractMethod> methodList = super.getMethodList();
methodList.add(new LogicDeleteByIdWithFill());
return methodList;
}
};
}
配置自己的全局 baseMapper 并使用
public interface MyBaseMapper<T> extends BaseMapper<T> {
/**
* 自定义全局方法
*/
int deleteByIdWithFill(T entity);
}
3.x数据库关键字如何处理?
在以前的版本是自动识别关键字进行处理的,但是3.x移除了这个功能。
- 不同的数据库对关键字的处理不同,很难维护。
- 在数据库设计时候本身不建议使用关键字。
- 交由用户去处理关键字。
@TableField(value = "`status`")
private Boolean status;
MybatisPlusException: Your property named “xxx” cannot find the corresponding database column name!
针对3.1.1以及后面的版本:
现象: 单元测试没问题,启动服务器进行调试就出现这个问题
原因: dev-tools, 3.1.1+针对字段缓存,使用.class来作为key替换了原来的className, 而使用dev-tools会把.class使用不同的classLoader加载,导致可能出现找不到的情况
解决方案: 去掉dev-tools插件Error attempting to get column ‘create_time’ from result set. Cause: java.sql.SQLFeatureNotSupportedException
3.1.0之前版本没问题,针对3.1.1以及后续版本出现上述问题
现象: 集成druid数据源,使用3.1.0之前版本没问题,升级mp到3.1.1+后,运行时报错:java.sql.SQLFeatureNotSupportedException
原因: mp3.1.1+使用了新版jdbc,LocalDateTime等新日期类型处理方式升级,但druid在1.1.21版本之前不支持,参考issue
解决方案: 1. 升级druid到1.1.21解决这个问题;2.保持mp版本3.1.0;3.紧跟mp版本,换掉druid数据源mp版本从3.1.0及以下版本升级到高版本,JDK8日期新类型LocalDateTime等无法映射(报错)
MP_3.1.0及之前的版本,依赖的是mybatis 3.5.0,
MP_3.1.1升级了mybatis的依赖到3.5.1, 而mybatis 3.5.1 对于新日期类型,需要JDBC driver支持JDBC 4.2 API.
如果你的jdbc驱动版本不支持,那么就会出现新日期类型报错。
参考 blog.mybatis.orgThere is one backward incompatible changes since 3.5.0.
Because of the fix for #1478 , LocalDateTypeHandler, LocalTimeTypeHandler and LocalDateTimeTypeHandler now require a JDBC driver that supports JDBC 4.2 API.
[EDIT] These type handlers no longer work with Druid. Please see #1516 .
Failed to bind properties under ‘mybatis-plus.configuration.incomplete-result-maps[0].assistant.configuration.mapped-statements[0].parameter-map.parameter-mappings[0]’ to org.apache.ibatis.mapping.ParameterMapping
springboot 2.2.0 之前无此问题, springboot 2.2.0 出现此问题
现象: 1.本地启动无问题,打成war包部署到服务器报此问题
原因:springboot 2.2.0 构造器注入的问题, mybatis 私有构造器不能绑定属性, 造成依赖mybatis的框架比如MP报错 [参考issue](https://github.com/spring-projects/spring-boot/issues/18670) 此问题已在springboot2.2.1中修复
解决方案:1.将springboot降级到2.1.x或升级到2.2.1起 (建议springboot2.2.2)