前言
公司开发新项目啦,项目是基于SAAS模式的,经过小伙伴们热烈讨论我们采用了MyBatis-Flex作为持久层框架,它和MybatisPlus是属于同类型的框架但是各有千秋吧,相比而言MyBatis-Flex的功能全是免费的,MybatisPlus的很多功能是收费的,比如:数据脱敏,字段加密,多数据源支持等,整体性能是MybatisPlus的5到10倍。今天我们就来研究一下MyBatis-Flex这个框架,下面是一张两个框架的对比图
功能或特点 | MyBatis-Flex | MyBatis-Plus | Fluent-MyBatis |
---|---|---|---|
对 entity 的基本增删改查 | ✅ | ✅ | ✅ |
分页查询 | ✅ | ✅ | ✅ |
分页查询之总量缓存 | ✅ | ✅ | ❌ |
分页查询无 SQL 解析设计(更轻量,及更高性能) | ✅ | ❌ | ✅ |
多表查询: from 多张表 | ✅ | ❌ | ❌ |
多表查询: left join、inner join 等等 | ✅ | ❌ | ✅ |
多表查询: union,union all | ✅ | ❌ | ✅ |
单主键配置 | ✅ | ✅ | ✅ |
多种 id 生成策略 | ✅ | ✅ | ✅ |
支持多主键、复合主键 | ✅ | ❌ | ❌ |
字段的 typeHandler 配置 | ✅ | ✅ | ✅ |
除了 MyBatis,无其他第三方依赖(更轻量) | ✅ | ❌ | ❌ |
QueryWrapper 是否支持在微服务项目下进行 RPC 传输 | ✅ | ❌ | 未知 |
逻辑删除 | ✅ | ✅ | ✅ |
乐观锁 | ✅ | ✅ | ✅ |
SQL 审计 | ✅ | ❌ | ❌ |
数据填充 | ✅ | ✅ | ✅ |
数据脱敏 | ✅ | ✔️ (收费) | ❌ |
字段权限 | ✅ | ✔️ (收费) | ❌ |
字段加密 | ✅ | ✔️ (收费) | ❌ |
字典回写 | ✅ | ✔️ (收费) | ❌ |
Db + Row | ✅ | ❌ | ❌ |
Entity 监听 | ✅ | ❌ | ❌ |
多数据源支持 | ✅ | 借助其他框架或收费 | ❌ |
多数据源是否支持 Spring 的事务管理,比如 @Transactional 和 TransactionTemplate 等 | ✅ | ❌ | ❌ |
多数据源是否支持 “非Spring” 项目 | ✅ | ❌ | ❌ |
多租户 | ✅ | ✅ | ❌ |
动态表名 | ✅ | ✅ | ❌ |
动态 Schema | ✅ | ❌ | ❌ |
环境搭建
先引入相关依赖
- SpringBoot2采用的依赖是:mybatis-flex-spring-boot-starter
- SpringBoot3采用的依赖是:mybatis-flex-spring-boot3-starter
这里我采用的是SpringBoot3,具体如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
<knife4j.version>3.0.3</knife4j.version>
<swagger.version>1.6.2</swagger.version>
</properties>
<dependencies><!-- Swagger -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- 微服务中使用swagger,不包含ui -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-micro-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!-- 网关中使用swagger,包含ui -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.23</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.20</version>
</dependency>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-codegen</artifactId>
<version>1.10.1</version>
</dependency>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<version>1.10.2</version>
</dependency>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-processor</artifactId>
<version>1.10.2</version>
<scope>provided</scope>
</dependency>
<!-- JSON 解析器和生成器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
连接池我选用的是:druid ,数据库使用的是PGSQL ,Web层采用的是webflux,你可以换成你自己熟悉的框架。
- mybatis-flex-codegen : 代码生成器依赖
- mybatis-flex-spring-boot3-starter :mybatis-flex整合SpringBoot依赖
然后编写好启动类和application.yaml配置文件,常规配置就好,这里不多介绍
# DataSource Config
spring:
datasource:
url: jdbc:postgresql://localhost:5432/postgres #修改成你的数据库连接
username: postgres
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.postgresql.Driver
mybatis-flex:
mapper-locations: classpath:mapper/**/*.xml
global-config:
logic-delete-column: deleted
normal-value-of-logic-delete: 0
deleted-value-of-logic-delete: 1
- mapper-locations:配置Mapper.xml文件的扫描路径
- logic-delete-column :逻辑删除的列名
- normal-value-of-logic-delete:逻辑删除 0 代表正常 ,1代表已删除
在 Spring Boot 启动类中添加 @MapperScan 注解,扫描 Mapper 文件夹:
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class,args);
}
}
编写实体类和 Mapper 接口,这里使用了 Lombok 来简化代码
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("用户")
@Table(value = "t_user",onInsert = CustomInsertListener.class)
public class User implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* ID
*/
@Id(keyType = KeyType.Generator, value = "snowFlakeId")
@ApiModelProperty("ID")
private Long id;
/**
* 姓名
*/
@ApiModelProperty("姓名")
@ColumnMask(Masks.CHINESE_NAME)
private String name;
/**
* 年龄
*/
@ApiModelProperty("年龄")
private Integer age;
/**
* 机构
*/
@ApiModelProperty("机构")
@Column(tenantId = true)
private Integer tenantId;
/**
* 生日
*/
@ApiModelProperty("生日")
private LocalDate birthday;
/**
* 用户名
*/
@ApiModelProperty("用户名")
private String userName;
/**
* 创建时间
*/
@Column(onInsertValue = "now()")
@ApiModelProperty("创建时间")
private Timestamp createTime;
/**
* 乐观锁
*/
@Column(version = true)
@ApiModelProperty("乐观锁")
private Integer version;
/**
* 删除
*/
@Column(isLogicDelete = true)
@ApiModelProperty("删除")
private Integer deleted;
/**
* 修改时间
*/
@Column(onUpdateValue = "now()")
@ApiModelProperty("修改时间")
private Timestamp updateTime;
}
- @Table(value = “t_user”) : 用来映射表的,value指向表名
- @Id(keyType = KeyType.Generator, value = KeyGenerators.snowFlakeId) :指定ID主健,ID的创建方式有四种,具体看:com.mybatisflex.core.keygen.KeyGenerators类
- @ApiModelProperty(“年龄”) :这个是代码生成器生成的Swagger的注解,可暂时忽略
- @Column(onInsertValue = “now()”) :@Column是用来配置列的,onInsertValue 代表的是插入时指定的值,比如创建时间可以直接指定为当前时间。onUpdateValue 代表的是修改时指定的值
- @Column(version = true) :指定乐观锁列
- @Column(isLogicDelete = true) :指定逻辑删除
- @ColumnMask(Masks.CHINESE_NAME) :这个是脱敏,CHINESE_NAME代表的是脱敏模式:中文名字
接下来编写 Mapper 接口继承 BaseMapper 接口:BaseMapper 接口提供了基础的CRUD能力
public interface UserMapper extends BaseMapper<User> {
}
如果需要自定义SQL那么可以在resource下编写mapper/UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
</mapper>
然后就可以编写测试类进行测试了,
...
@SpringBootTest
class MybatisFlexTestApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
void query() {
QueryWrapper queryWrapper = QueryWrapper.create().eq(User::getAge, 1);
List<User> users = userMapper.selectListByQuery(queryWrapper);
users.stream().forEach(System.out::println);
}
@Test
void update() {
User user = userMapper.selectOneById(222542487581552640L);
user.setUserName("张22三");
int update = userMapper.update(user);
System.out.println("update = "+update);
}
@Test
void delete() {
int i = userMapper.deleteById(1L);
System.out.println("delete = "+i);
}
@Test
void save() {
User user = new User();
user.setName("zs");
user.setAge(0);
user.setBirthday(LocalDate.now());
user.setUserName("ls"+ ThreadLocalRandom.current().nextInt(100));
int i = userMapper.insert(user);
System.out.println("delete = "+i);
}
}
代码生成器
在 mybatis-flex 中,有了一个名称为 mybatis-flex-codegen 的模块,提供了可以通过数据库表,生成代码的功能。当我们把数据库表设计完成后, 就可以使用其快速生成 Entity、 Mapper、 Service、 Controller 等产物。
另外:MyBatis-Flex 也提供了一个在线的 AI 代码生成器,可以通过您的产品(或项目)需求描述,自动帮你生成完整的 SpringBoot + MyBatisFlex 项目代码以及 SQL 脚本,下载导入到开发工具即可使用。内测地址:https://ai.mybatis-flex.com
如果是要开发独立的代码生成器模块的话,除了导入mybatis-flex-codegen依赖,还需要导入相关的数据库驱动和连接池。
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-codegen</artifactId>
<version>1.10.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.23</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.20</version>
</dependency>
我这里在同一个项目中进行测试就无需重复导入了,然后需要编写一个带main方法的类,如下
public class Codegen {
public static void main(String[] args) {
//配置数据源
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:postgresql://localhost:5432/postgres");
dataSource.setDbType(DbType.postgresql);
dataSource.setUsername("postgres");
dataSource.setPassword("123456");
//创建配置内容,两种风格都可以。
GlobalConfig globalConfig = createGlobalConfigUseStyle1();
//GlobalConfig globalConfig = createGlobalConfigUseStyle2();
//通过 datasource 和 globalConfig 创建代码生成器
Generator generator = new Generator(dataSource, globalConfig);
//生成代码
generator.generate();
}
public static GlobalConfig createGlobalConfigUseStyle1() {
//创建配置内容
GlobalConfig globalConfig = new GlobalConfig();
//设置根包
//globalConfig.setSourceDir("");
globalConfig.setBasePackage("com.example.demo");
//设置表前缀和只生成哪些表
globalConfig.setTablePrefix("t_");
globalConfig.setGenerateTable("t_user");
globalConfig.setEntityOverwriteEnable(true);
//设置生成 entity 并启用 Lombok
globalConfig.setEntityGenerateEnable(true);
globalConfig.setEntityWithLombok(true);
//设置项目的JDK版本,项目的JDK为14及以上时建议设置该项,小于14则可以不设置
globalConfig.setEntityJdkVersion(17);
globalConfig.setVersionColumn("version");
globalConfig.setLogicDeleteColumn("deleted");
globalConfig.setEntityWithSwagger(true);
//设置生成 mapper
globalConfig.setMapperGenerateEnable(true);
//设置生成 service
globalConfig.setServiceGenerateEnable(true);
//设置生成 serviceImpl
globalConfig.setServiceImplGenerateEnable(true);
//设置生成 controller
globalConfig.setControllerGenerateEnable(true);
//设置生成 mapperXml
globalConfig.setMapperXmlGenerateEnable(true);
//可以单独配置某个列
ColumnConfig inserColumnConfig = new ColumnConfig();
inserColumnConfig.setColumnName("create_time");
inserColumnConfig.setOnInsertValue("now()");
ColumnConfig updateColumnConfig = new ColumnConfig();
updateColumnConfig.setColumnName("update_time");
updateColumnConfig.setOnUpdateValue("now()");
ColumnConfig idColumnConfig = new ColumnConfig();
idColumnConfig.setColumnName("id");
idColumnConfig.setPrimaryKey(true).setKeyType(KeyType.Generator).setKeyValue(KeyGenerators.snowFlakeId);
globalConfig.setColumnConfig(inserColumnConfig);
globalConfig.setColumnConfig(updateColumnConfig);
globalConfig.setColumnConfig(idColumnConfig);
return globalConfig;
}
执行main方法就可以生成好实体类,mapper,service,controller ,具体的配置和解释可以看一下官网:https://mybatis-flex.com/zh/others/codegen.html ,
动态数据源
有点时候在项目中我们需要做多数据源,或者数据库主从,那么就需要切换数据库,在Mybatis-Flex中这一行为是非常方便的,它分为3步,第一步:配置多数据源
# DataSource Config
spring:
datasource:
url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.postgresql.Driver
master:
url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.postgresql.Driver
slave:
url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.postgresql.Driver
master 和 slave 对应的就是2个数据库的名字,名字可以任意取名。配置好数据源后需要配置数据源路由策略,如下
/**
* 多数据源分片策略
*/
public class DBShardingStrategy implements DataSourceShardingStrategy {
@Override
public String doSharding(String currentDataSourceKey, Object mapper, Method mapperMethod, Object[] methodArgs) {
// 如果 mapper 的方法属于 增删改,使用 master 数据源
if (StringUtils.startsWithIgnoreCase(mapperMethod.getName(),"insert") ||
StringUtils.startsWithIgnoreCase(mapperMethod.getName(),"delete") ||
StringUtils.startsWithIgnoreCase(mapperMethod.getName(),"update") ){
return "master";
}
return "slave";
}
}
编写自己的策略取实现 DataSourceShardingStrategy ,复写doSharding方法,通过判断方法名如果是增删改就走“master”库,否则走slave库。
最后一步就是让策略生效,就是需要把它设置给 DataSourceManager我这里监听了一下项目启动去设置,如下
@Configuration
public class MybatisFlexConfig implements ApplicationRunner {
/**
* 数据源分片
*/
@Override
public void run(ApplicationArguments args) throws Exception {
//注册数据源分片策略
DataSourceManager.setDataSourceShardingStrategy(new DBShardingStrategy());
}
这个方式比较粗暴,也不够精细。MyBatis-Flex 提供了 4 种方式来手动配置数据源:
- 1、编码,使用DataSourceKey.use 方法。
- 2、@UseDataSource(“dataSourceName”) 在 Mapper 类上,添加注解,用于指定使用哪个数据源。
- 3、@UseDataSource(“dataSourceName”) 在 Mapper 方法上,添加注解,用于指定使用哪个数据源。
- 4、@Table(dataSource=“dataSourceName”) 在 Entity 类上添加注解,该 Entity 的增删改查请求默认使用该数据源。
在 SpringBoot 项目上,@UseDataSource(“dataSourceName”) 也可用于在 Controller 或者 Service 上。
更多使用方式请看官网:https://mybatis-flex.com/zh/core/multi-datasource.html
动态表名
动态表名指的是用户在对数据进行 增删改查 的时候,传入表名能够根据上下文信息(比如用户信息、应用信息)等,动态修改当前的表。常用来多租户切换,不同的租户拥有不同的表,以及分库分表,减轻数据压力
在应用启动时,通过调用 TableManager.setDynamicTableProcessor() 配置动态表名处理器 DynamicTableProcessor 即可,如下代码所示:
/**
* @author Administrator
*/
@Slf4j
public class TableShardingStrategy implements DynamicTableProcessor {
/**
* 表的数量 : user_01 ; user_02;
*/
private static final int TABLE_COUNT = 2;
@Override
public String process(String tableName) {
Long teanantId = RequestContext.getTeanantId();
log.info("分片策略获取TenantId = {}",teanantId);
if(teanantId != null){
Long index = (teanantId % TABLE_COUNT) + 1;
String suffix = StringUtils.leftPad(index.toString(), 2, "0");
return tableName+"_"+suffix;
}
return tableName;
}
}
通过以上配置后,我们对数据库进行增删改查,MyBatis-Flex 都会调用 DynamicTableProcessor.process 方法,获得最新的表名进行 SQL 构建操作。因此,我们应该在 process 方法中, 判断当前的上下文(用户信息、应用信息)等,动态的返回对应的表名。然后需要把TableShardingStrategy 配置成Bean
/**
* 动态分表
*/
@Bean
public DynamicTableProcessor dynamicTableProcessor(){
return new TableShardingStrategy();
}
在某些情况下,我们临时修改映射关系,而非通过 DynamicTableProcessor.process 方法获取,可以通过如下配置:
try{
TableManager.setHintTableMapping("tb_account", "tb_account_01");
//这里写您的业务逻辑
} finally {
TableManager.clear();
}
那么此时,当前线程不再通过 DynamicTableProcessor 去获取。
多租户
多租户技术(英语:multi-tenancy technology),是一种软件架构技术,它是在探讨与实现如何于多用户的环境下共用相同的系统或程序组件,并且仍可确保各用户间数据的隔离性。
多租户简单来说是指一个单独的实例可以为多个用户(或组织)服务。多租户技术要求所有用户共用同一个数据中心,但能提供多个客户端相同甚至可定制化的服务,并且仍然可以保障客户的数据隔离。
多租户的数据隔离有许多种方案,但最为常见的是以列进行隔离的方式。MyBatis-Flex 内置的正是通过指定的列(租户ID tenant_id)进行隔离的方案。
MyBatis-Flex 使用多租户需要 2 个步骤:
- step 1:通过 @Column(tenantId = true) 标识租户列。
- step 2:为 TenantManager 配置 TenantFactory。
TenantFactory 是用于生产租户ID的,或者说是用于获取当前租户ID的。
/**
* 租户ID设置
*/
@Bean
public TenantFactory tenantFactory(){
TenantFactory tenantFactory = new TenantFactory() {
@Override
public Object[] getTenantIds() {
Long tenantId = RequestContext.getTeanantId();
return new Object[]{tenantId};
}
};
return tenantFactory;
}
对于租户如何获得,可以通过请求头获取租户ID,或者通过解析域名得到租户标识。然后设置到一个上下文中,可以用ThreadLocal来做,后续就可以直接获取到交给TenantFactory
结束
文章就到这里吧,本文章只是起到一个入门的作用,更多的功能可以看官方去学习:https://mybatis-flex.com/ ,如果文章对你有帮助,请给个好评哦!!!