发现一个神器-MyBatis-Flex快来尝尝鲜

在这里插入图片描述

前言

公司开发新项目啦,项目是基于SAAS模式的,经过小伙伴们热烈讨论我们采用了MyBatis-Flex作为持久层框架,它和MybatisPlus是属于同类型的框架但是各有千秋吧,相比而言MyBatis-Flex的功能全是免费的,MybatisPlus的很多功能是收费的,比如:数据脱敏,字段加密,多数据源支持等,整体性能是MybatisPlus的5到10倍。今天我们就来研究一下MyBatis-Flex这个框架,下面是一张两个框架的对比图

功能或特点MyBatis-FlexMyBatis-PlusFluent-MyBatis
对 entity 的基本增删改查
分页查询
分页查询之总量缓存
分页查询无 SQL 解析设计(更轻量,及更高性能)
多表查询: from 多张表
多表查询: left join、inner join 等等
多表查询: union,union all
单主键配置
多种 id 生成策略
支持多主键、复合主键
字段的 typeHandler 配置
除了 MyBatis,无其他第三方依赖(更轻量)
QueryWrapper 是否支持在微服务项目下进行 RPC 传输未知
逻辑删除
乐观锁
SQL 审计
数据填充
数据脱敏✔️ (收费)
字段权限✔️ (收费)
字段加密✔️ (收费)
字典回写✔️ (收费)
Db + Row
Entity 监听
多数据源支持借助其他框架或收费
多数据源是否支持 Spring 的事务管理,比如 @TransactionalTransactionTemplate
多数据源是否支持 “非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/ ,如果文章对你有帮助,请给个好评哦!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨家巨子@俏如来

你的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值