文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Fluent Mybatis实现环境隔离和租户隔离

2024-04-02 19:55

关注

什么是环境隔离和多租户隔离

我们在实际的业务开发中,经常会碰到环境逻辑隔离和租户数据逻辑隔离的问题。

环境隔离

我们的开发系统过程中,经常会涉及到日常开发环境,测试环境,预发环境和线上环境,如何区隔这些环境,有些方案是采用独立的数据库,有些是采用同一套数据库(比如线下多个测试环境使用同一个数据库,预发环境和线上环境使用同一个数据库),然后对数据进行打标的办法,来区分不同环境的数据。

多租户管理

在复杂的业务系统中,比如SaaS应用中,在多用户环境下共用相同的系统或程序组件,如何确保各用户间数据的隔离性。简单讲:在一台服务器上运行单个应用实例,它为多个租户(客户)提供服务。从定义中我们可以理解:多租户是一种架构,是为了让多用户环境下使用同一套程序,但要保证用户间数据隔离。那如何进行多租户的重点就是同一套程序下实现多用户数据的隔离,做法其实和环境隔离是同一个道理。
这里采用多环境多租户共用数据表的场景,来探讨下FluentMybatis是如何支持多环境和多租户管理的。

环境隔离和多租户隔离需要做的事情

比如我们有下面表


create table student
(
    id              bigint(21) unsigned auto_increment comment '主键id'
        primary key,
    age             int                  null comment '年龄',
    grade           int                  null comment '年级',
    user_name       varchar(45)          null comment '名字',
    gender_man      tinyint(2) default 0 null comment '性别, 0:女; 1:男',
    birthday        datetime             null comment '生日',
    phone           varchar(20)          null comment '电话',
    bonus_points    bigint(21) default 0 null comment '积分',
    status          varchar(32)          null comment '状态(字典)',
    home_county_id  bigint(21)           null comment '家庭所在区县',
    home_address_id bigint(21)           null comment 'home_address外键',
    address         varchar(200)         null comment '家庭详细住址',
    version         varchar(200)         null comment '版本号',
    env             varchar(10)          NULL comment '数据隔离环境',
    tenant          bigint               NOT NULL default 0 comment '租户标识',
    gmt_created     datetime             null comment '创建时间',
    gmt_modified    datetime             null comment '更新时间',
    is_deleted      tinyint(2) default 0 null comment '是否逻辑删除'
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8
    COMMENT '学生信息表';

注意其中的2个字段

  1. env, 表示应用部署的环境, 环境的区隔一般是采用应用部署的机器环境变量。
  2. tenant, 表示数据所属租户,租户的隔离一般是通过登录用户信息获取的。

对环境和租户的隔离,主要是CRUD过程中,需要带上环境变量和租户信息。如果没有框架的支持,就需要在构造SQL的过程中,手动设置env和tenant。这就存在一个严重的弊端: 在编码过程中,需要时刻注意sql语句中不要漏了这2个条件,否则就会产生逻辑错误和信息泄露。

为了减少错误,我们都会将逻辑进行收拢,下面我们演示fluent mybatis如何统一处理。

环境隔离和租户隔离工具类

为了进行环境隔离和租户隔离,我们一般会统一定义获取环境变量和租户信息的工具类。

环境隔离工具类



public class EnvUtils {
    public static String currEnv() {
        // 应用启动时, 读取的机器部署环境变量, 这里简化为返回固定值演示
        return "test1";
    }
}

租户隔离工具类



public class TenantUtils {
    
    static final long A_TENANT = 111111L;
    
    static final long B_TENANT = 222222L;

    
    public static long findUserTenant() {
        long userId = loginUserId();
        if (userId % 2 == 0) {
            return A_TENANT;
        } else {
            return B_TENANT;
        }
    }

    
    public static long loginUserId() {
        return 1L;
    }
}

隔离前准备工作

Entity隔离属性基类

为了方便对所有需要隔离的Entity进行统一的环境和租户信息的设置和读取,我们把Entity的环境和租户的属性的getter和setter方法定义到一个接口上。



public interface IsolateEntity {
    
    String getEnv();

    
    IsolateEntity setEnv(String env);

    
    Long getTenant();

    
    IsolateEntity setTenant(Long tenant);
}

这样所有需要隔离的Entity只要继承这个接口就可以在需要隔离操作的地方把具体的entity当作IsolateEntity对象来操作。

隔离属性和默认条件设置

有了统一的接口,我们还需要一个默认进行设置的操作,fluent mybatis提供了一个IDefaultSetter 接口,可以对Entity,Query和Update进行拦截操作。



public interface IsolateSetter extends IDefaultSetter {
    
    @Override
    default void setInsertDefault(IEntity entity) {
        IsolateEntity isolateEntity = (IsolateEntity) entity;
        if (isolateEntity.getEnv() == null) {
            isolateEntity.setEnv(EnvUtils.currEnv());
        }
        if (isolateEntity.getTenant() == null) {
            isolateEntity.setTenant(TenantUtils.findUserTenant());
        }
    }

    
    @Override
    default void setQueryDefault(IQuery query) {
        query.where()
            .apply("env", SqlOp.EQ, EnvUtils.currEnv())
            .apply("tenant", SqlOp.EQ, TenantUtils.findUserTenant());
    }

    
    @Override
    default void setUpdateDefault(IUpdate updater) {
        updater.where()
            .apply("env", SqlOp.EQ, EnvUtils.currEnv())
            .apply("tenant", SqlOp.EQ, TenantUtils.findUserTenant());
    }
}

为了避免使用不当导致线程安全问题(变量共享), fluent mybatis只允许在应用中定义接口(比如这里的IsolateSetter)继承IDefaultSetter, 不允许定义成类。

代码生成设置

怎么让fluent mybatis识别到哪些Entity可以继承IsolateEntity,哪些Entity操作需要进行IsolateSetter统一拦截呢?
在@FluentMybatis上有个属性defaults(), 我们把defaults值设置为 IsolateSetter.class就可以了。


public @interface FluentMybatis {
    
    Class<? extends IDefaultSetter> defaults() default IDefaultSetter.class;
}

当然,我们并不需要手动去修改Entity类,只需要在代码生成上设置。


public class FluentGenerateMain {
    static final String url = "jdbc:mysql://localhost:3306/fluent_mybatis?useSSL=false&useUnicode=true&characterEncoding=utf-8";
    
    static final String basePackage = "cn.org.fluent.mybatis.many2many.demo";

    public static void main(String[] args) {
        FileGenerator.build(Noting.class);
    }

    @Tables(
        
        url = url, username = "root", password = "password",
        
        basePack = basePackage,
        
        srcDir = "example/many2many_demo/src/main/java",
        
        gmtCreated = "gmt_created", gmtModified = "gmt_modified", logicDeleted = "is_deleted",
        
        tables = @Table(value = {"student"},
            entity = IsolateEntity.class,
            defaults = IsolateSetter.class)
    )
    static class Noting {
    }
}

注意,对比之前的代码生成,@Table上多了2个属性设置


// 标识对应的Entity类需要继承的接口
entity = IsolateEntity.class        

// 标识对应的Entity类CRUD过程中需要进行的默认设置操作
defaults = IsolateSetter.class

执行代码生成,Entity代码如下:


@FluentMybatis(
    table = "student",
    defaults = IsolateSetter.class
)
public class StudentEntity extends RichEntity implements IsolateEntity {
    // ... 省略
}

我们看到@FluentMybatis设置了defaults属性,Entity类继承了IsolateEntity接口。
接下来,我们进行具体的增删改查演示。

增删改查环境和租户隔离演示

新增数据


@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppMain.class)
public class InsertWithEnvDemo {
    @Autowired
    private StudentMapper mapper;

    @Test
    public void insertEntity() {
        mapper.delete(new StudentQuery());
        mapper.insert(new StudentEntity()
            .setAddress("宇宙深处")
            .setUserName("FluentMybatis")
        );
        StudentEntity student = mapper.findOne(StudentQuery.query()
            .where.userName().eq("FluentMybatis").end()
            .limit(1));
        System.out.println(student.getUserName() + ", env:" + student.getEnv() + ", tenant:" + student.getTenant());
    }
}

查看控制台输出log

DEBUG - ==>  Preparing:
    INSERT INTO student(gmt_created, gmt_modified, is_deleted, address, env, tenant, user_name)
    VALUES (now(), now(), 0, ?, ?, ?, ?) 
DEBUG - ==> Parameters: 宇宙深处(String), test1(String), 222222(Long), FluentMybatis(String)
DEBUG - <==    Updates: 1
DEBUG - ==>  Preparing: SELECT id, gmt_created, gmt_modified, is_deleted, address, age, birthday, bonus_points, env, gender_man, grade, home_address_id, home_county_id, phone, status, tenant, user_name, version
    FROM student WHERE user_name = ? LIMIT ?, ? 
DEBUG - ==> Parameters: FluentMybatis(String), 0(Integer), 1(Integer)
DEBUG - <==      Total: 1
FluentMybatis, env:test1, tenant:222222

在演示例子中,我们虽然只显式设置了userName和address2个属性,但插入数据中设置了7个属性,其中包括env和tenant。
注意,这里的查询条件并没有带上环境变量

查询数据

fluent mybatis提供了2种构造查询器的方式

  1. XyzQuery.query(): 全新的不带任何条件的查询。
  2. XyzQuery.defaultQuery(): 按照@FluentMybatis defaults属性指定的接口,设置好默认查询条件。

上面默认插入的例子已经演示了不带条件的query()查询,我们现在演示下设置了默认条件的查询。


@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppMain.class)
public class QueryWithEnvDemo {
    @Autowired
    private StudentMapper mapper;

    @Test
    public void testQueryWithEnv(){
        mapper.delete(new StudentQuery());
        mapper.insert(new StudentEntity()
            .setAddress("宇宙深处")
            .setUserName("FluentMybatis")
        );
        StudentEntity student = mapper.findOne(mapper.defaultQuery()
            .where.userName().eq("FluentMybatis").end()
            .limit(1));
        System.out.println(student.getUserName() + ", env:" + student.getEnv() + ", tenant:" + student.getTenant());
    }
}

查看控制log输出

DEBUG - ==>  Preparing: SELECT id, gmt_created, ... , tenant, user_name, version
    FROM student
    WHERE env = ?
    AND tenant = ?
    AND user_name = ?
    LIMIT ?, ? 
DEBUG - ==> Parameters: test1(String), 222222(Long), FluentMybatis(String), 0(Integer), 1(Integer)
DEBUG - <==      Total: 1
FluentMybatis, env:test1, tenant:222222

我们看到,查询条件中除了有我们设置好的user_name,还包括在IsolateSetter接口中设置好的env和tenant字段。

更新数据

和Query一样,Updater同样提供了2个方法来构造Updater

演示例子


@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppMain.class)
public class UpdateWithEnvDemo {
    @Autowired
    private StudentMapper mapper;

    @Test
    public void testQueryWithEnv() {
        mapper.delete(new StudentQuery());
        mapper.insert(new StudentEntity()
            .setAddress("宇宙深处")
            .setUserName("FluentMybatis")
        );
        mapper.updateBy(StudentUpdate.defaultUpdater()
            .update.address().is("回到地球").end()
            .where.userName().eq("FluentMybatis").end()
        );
    }
}

查看控制台log输出

DEBUG - ==>  Preparing: UPDATE student
    SET gmt_modified = now(), address = ?
    WHERE env = ?
    AND tenant = ?
    AND user_name = ? 
DEBUG - ==> Parameters: 回到地球(String), test1(String), 222222(Long), FluentMybatis(String)
DEBUG - <==    Updates: 1

更新条件中自动带上了设置好的默认条件 env 和 tenant。

总结

Fluent Mybatis通过自定义接口继承IDefaultSetter,赋予了你进行数据隔离操作的强大功能。默认值的赋值是通过编译生成的XyzDefaults类来进行的,大家可以具体查看编译生成的代码。
文中示例代码

到此这篇关于Fluent Mybatis实现环境隔离和租户隔离的文章就介绍到这了,更多相关Fluent Mybatis环境隔离和租户隔离内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯