欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Fluent Mybatis實現(xiàn)環(huán)境隔離和租戶隔離

 更新時間:2021年08月05日 15:39:49   作者:稻草江南  
我們在實際的業(yè)務(wù)開發(fā)中,經(jīng)常會碰到環(huán)境邏輯隔離和租戶數(shù)據(jù)邏輯隔離的問題。本文就詳細的來介紹一下,感興趣的小伙伴們可以參考一下

什么是環(huán)境隔離和多租戶隔離

我們在實際的業(yè)務(wù)開發(fā)中,經(jīng)常會碰到環(huán)境邏輯隔離和租戶數(shù)據(jù)邏輯隔離的問題。

環(huán)境隔離

我們的開發(fā)系統(tǒng)過程中,經(jīng)常會涉及到日常開發(fā)環(huán)境,測試環(huán)境,預發(fā)環(huán)境和線上環(huán)境,如何區(qū)隔這些環(huán)境,有些方案是采用獨立的數(shù)據(jù)庫,有些是采用同一套數(shù)據(jù)庫(比如線下多個測試環(huán)境使用同一個數(shù)據(jù)庫,預發(fā)環(huán)境和線上環(huán)境使用同一個數(shù)據(jù)庫),然后對數(shù)據(jù)進行打標的辦法,來區(qū)分不同環(huán)境的數(shù)據(jù)。

多租戶管理

在復雜的業(yè)務(wù)系統(tǒng)中,比如SaaS應(yīng)用中,在多用戶環(huán)境下共用相同的系統(tǒng)或程序組件,如何確保各用戶間數(shù)據(jù)的隔離性。簡單講:在一臺服務(wù)器上運行單個應(yīng)用實例,它為多個租戶(客戶)提供服務(wù)。從定義中我們可以理解:多租戶是一種架構(gòu),是為了讓多用戶環(huán)境下使用同一套程序,但要保證用戶間數(shù)據(jù)隔離。那如何進行多租戶的重點就是同一套程序下實現(xiàn)多用戶數(shù)據(jù)的隔離,做法其實和環(huán)境隔離是同一個道理。
這里采用多環(huán)境多租戶共用數(shù)據(jù)表的場景,來探討下FluentMybatis是如何支持多環(huán)境和多租戶管理的。

環(huán)境隔離和多租戶隔離需要做的事情

比如我們有下面表

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 '狀態(tài)(字典)',
    home_county_id  bigint(21)           null comment '家庭所在區(qū)縣',
    home_address_id bigint(21)           null comment 'home_address外鍵',
    address         varchar(200)         null comment '家庭詳細住址',
    version         varchar(200)         null comment '版本號',
    env             varchar(10)          NULL comment '數(shù)據(jù)隔離環(huán)境',
    tenant          bigint               NOT NULL default 0 comment '租戶標識',
    gmt_created     datetime             null comment '創(chuàng)建時間',
    gmt_modified    datetime             null comment '更新時間',
    is_deleted      tinyint(2) default 0 null comment '是否邏輯刪除'
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8
    COMMENT '學生信息表';

注意其中的2個字段

  1. env, 表示應(yīng)用部署的環(huán)境, 環(huán)境的區(qū)隔一般是采用應(yīng)用部署的機器環(huán)境變量。
  2. tenant, 表示數(shù)據(jù)所屬租戶,租戶的隔離一般是通過登錄用戶信息獲取的。

對環(huán)境和租戶的隔離,主要是CRUD過程中,需要帶上環(huán)境變量和租戶信息。如果沒有框架的支持,就需要在構(gòu)造SQL的過程中,手動設(shè)置env和tenant。這就存在一個嚴重的弊端: 在編碼過程中,需要時刻注意sql語句中不要漏了這2個條件,否則就會產(chǎn)生邏輯錯誤和信息泄露。

為了減少錯誤,我們都會將邏輯進行收攏,下面我們演示fluent mybatis如何統(tǒng)一處理。

環(huán)境隔離和租戶隔離工具類

為了進行環(huán)境隔離和租戶隔離,我們一般會統(tǒng)一定義獲取環(huán)境變量和租戶信息的工具類。

環(huán)境隔離工具類

/**
 * 應(yīng)用部署環(huán)境工具類
 */
public class EnvUtils {
    public static String currEnv() {
        // 應(yīng)用啟動時, 讀取的機器部署環(huán)境變量, 這里簡化為返回固定值演示
        return "test1";
    }
}

租戶隔離工具類

/**
 * 獲取用戶所屬租戶信息工具類
 */
public class TenantUtils {
    /**
     * 租戶A
     */
    static final long A_TENANT = 111111L;
    /**
     * 租戶B
     */
    static final long B_TENANT = 222222L;

    /**
     * 租戶信息一般根據(jù)登錄用戶身份來判斷, 這里簡化為偶數(shù)用戶屬于租戶A, 奇數(shù)用戶屬于租戶B
     *
     * @return
     */
    public static long findUserTenant() {
        long userId = loginUserId();
        if (userId % 2 == 0) {
            return A_TENANT;
        } else {
            return B_TENANT;
        }
    }

    /**
     * 當前登錄的用戶id, 一般從Session中獲取
     *
     * @return
     */
    public static long loginUserId() {
        return 1L;
    }
}

隔離前準備工作

Entity隔離屬性基類

為了方便對所有需要隔離的Entity進行統(tǒng)一的環(huán)境和租戶信息的設(shè)置和讀取,我們把Entity的環(huán)境和租戶的屬性的getter和setter方法定義到一個接口上。

/**
 * Entity類隔離屬性基類
 */
public interface IsolateEntity {
    /**
     * 返回entity env屬性值
     *
     * @return
     */
    String getEnv();

    /**
     * 設(shè)置entity env屬性值
     *
     * @param env
     * @return
     */
    IsolateEntity setEnv(String env);

    /**
     * 返回entity 租戶信息
     *
     * @return
     */
    Long getTenant();

    /**
     * 設(shè)置entity 租戶信息
     *
     * @param tenant
     * @return
     */
    IsolateEntity setTenant(Long tenant);
}

這樣所有需要隔離的Entity只要繼承這個接口就可以在需要隔離操作的地方把具體的entity當作IsolateEntity對象來操作。

隔離屬性和默認條件設(shè)置

有了統(tǒng)一的接口,我們還需要一個默認進行設(shè)置的操作,fluent mybatis提供了一個IDefaultSetter 接口,可以對Entity,Query和Update進行攔截操作。

/**
 * 增刪改查中,環(huán)境和租戶隔離設(shè)置
 */
public interface IsolateSetter extends IDefaultSetter {
    /**
     * 插入的entity,如果沒有顯式設(shè)置環(huán)境和租戶,根據(jù)工具類進行默認設(shè)置
     *
     * @param entity
     */
    @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());
        }
    }

    /**
     * 查詢條件追加環(huán)境隔離和租戶隔離
     *
     * @param query
     */
    @Override
    default void setQueryDefault(IQuery query) {
        query.where()
            .apply("env", SqlOp.EQ, EnvUtils.currEnv())
            .apply("tenant", SqlOp.EQ, TenantUtils.findUserTenant());
    }

    /**
     * 更新條件追加環(huán)境隔離和租戶隔離
     *
     * @param updater
     */
    @Override
    default void setUpdateDefault(IUpdate updater) {
        updater.where()
            .apply("env", SqlOp.EQ, EnvUtils.currEnv())
            .apply("tenant", SqlOp.EQ, TenantUtils.findUserTenant());
    }
}

為了避免使用不當導致線程安全問題(變量共享), fluent mybatis只允許在應(yīng)用中定義接口(比如這里的IsolateSetter)繼承IDefaultSetter, 不允許定義成類。

代碼生成設(shè)置

怎么讓fluent mybatis識別到哪些Entity可以繼承IsolateEntity,哪些Entity操作需要進行IsolateSetter統(tǒng)一攔截呢?
在@FluentMybatis上有個屬性defaults(), 我們把defaults值設(shè)置為 IsolateSetter.class就可以了。

public @interface FluentMybatis {
    /**
     * entity, query, updater默認值設(shè)置實現(xiàn)
     *
     * @return
     */
    Class<? extends IDefaultSetter> defaults() default IDefaultSetter.class;
}

當然,我們并不需要手動去修改Entity類,只需要在代碼生成上設(shè)置。

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

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

    @Tables(
        /** 數(shù)據(jù)庫連接信息 **/
        url = url, username = "root", password = "password",
        /** Entity類parent package路徑 **/
        basePack = basePackage,
        /** Entity代碼源目錄 **/
        srcDir = "example/many2many_demo/src/main/java",
        /** 如果表定義記錄創(chuàng)建,記錄修改,邏輯刪除字段 **/
        gmtCreated = "gmt_created", gmtModified = "gmt_modified", logicDeleted = "is_deleted",
        /** 需要生成文件的表 ( 表名稱:對應(yīng)的Entity名稱 ) **/
        tables = @Table(value = {"student"},
            entity = IsolateEntity.class,
            defaults = IsolateSetter.class)
    )
    static class Noting {
    }
}

注意,對比之前的代碼生成,@Table上多了2個屬性設(shè)置

// 標識對應(yīng)的Entity類需要繼承的接口
entity = IsolateEntity.class        
// 標識對應(yīng)的Entity類CRUD過程中需要進行的默認設(shè)置操作
defaults = IsolateSetter.class

執(zhí)行代碼生成,Entity代碼如下:

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

我們看到@FluentMybatis設(shè)置了defaults屬性,Entity類繼承了IsolateEntity接口。
接下來,我們進行具體的增刪改查演示。

增刪改查環(huán)境和租戶隔離演示

新增數(shù)據(jù)

@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

在演示例子中,我們雖然只顯式設(shè)置了userName和address2個屬性,但插入數(shù)據(jù)中設(shè)置了7個屬性,其中包括env和tenant。
注意,這里的查詢條件并沒有帶上環(huán)境變量

查詢數(shù)據(jù)

fluent mybatis提供了2種構(gòu)造查詢器的方式

  1. XyzQuery.query(): 全新的不帶任何條件的查詢。
  2. XyzQuery.defaultQuery(): 按照@FluentMybatis defaults屬性指定的接口,設(shè)置好默認查詢條件。

上面默認插入的例子已經(jīng)演示了不帶條件的query()查詢,我們現(xiàn)在演示下設(shè)置了默認條件的查詢。

@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

我們看到,查詢條件中除了有我們設(shè)置好的user_name,還包括在IsolateSetter接口中設(shè)置好的env和tenant字段。

更新數(shù)據(jù)

和Query一樣,Updater同樣提供了2個方法來構(gòu)造Updater

  • XyzUpdate.updater() : 不帶任何條件的更新。
  • XyzUpdate.defaultUpdater(): 根據(jù)IsolateSetter#setUpdateDefault方法設(shè)置好更新條件。

演示例子

@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

更新條件中自動帶上了設(shè)置好的默認條件 env 和 tenant。

總結(jié)

Fluent Mybatis通過自定義接口繼承IDefaultSetter,賦予了你進行數(shù)據(jù)隔離操作的強大功能。默認值的賦值是通過編譯生成的XyzDefaults類來進行的,大家可以具體查看編譯生成的代碼。
文中示例代碼

到此這篇關(guān)于Fluent Mybatis實現(xiàn)環(huán)境隔離和租戶隔離的文章就介紹到這了,更多相關(guān)Fluent Mybatis環(huán)境隔離和租戶隔離內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論