在中台服务或者saas服务中,当多租户入驻时,如何保证不同租户的数据隔离性呢?通常的解决方法有三种,分别如下: 通常为了降低成本,一般会选择第三种方案。这时,应该如何快速的实现多租户的数据隔离呢?在每一个查询语句中都添加上不同租户的标识语句么?基于mybatis提供的plugin插件,可以实现多租户过滤语句的横切逻辑,类似于AOP,让我们的业务代码从数据隔离的逻辑中抽离出来,专注于业务开发。 在MyBatis 中,通过其plugin插件机制,可以实现类似于AOP的横切逻辑编程,允许你在映射语句执行过程中的某一点进行拦截调用。定义了Interceptor 接口,实现指定方法的拦截,官网示例代码如下: 实现多租户的拦截过程中,通过对query操作进行拦截,实现了多租户过滤的如下功能: 其大致的流程如下: MultiTenancyQueryInterceptor多租户过滤器 在实现中,定义MultiTenancyQueryInterceptor实现Interceptor实现如上流程的逻辑。其源码如下: 在使用时,定义多租户插件,设置过滤的数据库字段;在查询时,设置过滤的查询值,数据库表的前缀别名,过滤开关。以商品查询为例,可以根据商品code进行查询,定义根据company_id进行数据过滤,定义数据库的前缀别名为g,示例如下:  测试运行结果,包含了多租户g.company_id in(1,2)的过滤查询条件,并且能够查询结果大小为1,结果如图: 解析查询参数MultiTenancyQuery与配置参数MultiTenancyProperties 定义多租户查询条件定义MultiTenancyQuery,可以设置执行过滤操作的开关,过滤查询值,以及数据库查询别名,其源码如下: 定义MultiTenancyProperties,设置多租户中过滤的数据库字段,已经查询条件的设置,现在实现了相等条件和IN条件的查询条件实现方式,其源码如下:  解析查询语句的生成规格ConditionFactory以及条件语句的追加逻辑 定义ConditionFactory接口实现查询sql查询语句的生成,其默认实现类DefaultConditionFactory实现了相等条件和IN条件的查询语句语法,其源码如下: 在原生的sql查询语句新增自定义的查询条件方法,是根据是否存在where查询条件字段进行动态的拼接。如果没有查询条件则直接添加,反之,则添加到第一个查询条件的位置。其源码如下: 多租户拦截器全部源码可以从多租户数据拦截器插件下载。如何实现多租户数据隔离
基于MyBatis插件plugin的实现
 
// ExamplePlugin.java @Intercepts({@Signature(   type= Executor.class,   method = "update",   args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor {   private Properties properties = new Properties();   public Object intercept(Invocation invocation) throws Throwable {     // implement pre processing if need     // 拦截执行方法之前的逻辑处理      Object returnObject = invocation.proceed();      // implement post processing if need      // 拦截执行方法之后的逻辑处理     return returnObject;   }   public void setProperties(Properties properties) {     this.properties = properties;   } }

 
@Intercepts({          // 拦截query查询语句         @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class MultiTenancyQueryInterceptor implements Interceptor {      private static final String WHERE_CONDITION = " where ";     private static final String AND_CONDITION = " and ";     /**      * 条件生成Factory      */     private final ConditionFactory conditionFactory;     /**      * 多组合属性      */     private final MultiTenancyProperties multiTenancyProperties;      public MultiTenancyQueryInterceptor() {         this.conditionFactory = new DefaultConditionFactory();         this.multiTenancyProperties = new MultiTenancyProperties();     }      @Override     public Object intercept(Invocation invocation) throws Throwable {          Object[] args = invocation.getArgs();         Object parameter = args[1];         // 判断是否需要进行多租户过滤         if (!this.isMatchMultiTenancy(parameter)) {              log.info("parameter is not match multi tenancy query!");             return invocation.proceed();         }         // 如果多租户过滤时,多租户过滤数据库字段为空或者查询值为空则抛出异常         MultiTenancyQuery multiTenancyQuery = (MultiTenancyQuery) parameter;         if (StringUtils.isBlank(this.multiTenancyProperties.getMultiTenancyQueryColumn())                 || Objects.isNull(multiTenancyQuery.getMultiTenancyQueryValue())) {              log.error("property {} or parameter {} is invalid!", JSON.toJSONString(this.multiTenancyProperties), JSON.toJSONString(parameter));             throw new RuntimeException("property or parameter is invalid!");         }         MappedStatement mappedStatement = (MappedStatement) args[0];         BoundSql boundSql = mappedStatement.getBoundSql(parameter);         String originSql = boundSql.getSql();         if (!this.matchPreTableName(originSql, multiTenancyQuery.getPreTableName())) {              log.info("pre table name {} is not matched sql {}!", multiTenancyQuery.getPreTableName(), originSql);             return invocation.proceed();         }         // 默认使用In 条件         ConditionFactory.ConditionTypeEnum conditionTypeEnum = ConditionFactory.ConditionTypeEnum.IN;         String conditionType;         if (StringUtils.isNotBlank(conditionType = this.multiTenancyProperties.getConditionType())) {             try {                 conditionTypeEnum = ConditionFactory.ConditionTypeEnum.valueOf(conditionType.toUpperCase());             } catch (Exception e) {                 log.warn("invalid condition type {}!", conditionType);             }         }         // 根据配置的查询条件规格生成过滤查询语句         String multiTenancyQueryCondition = this.conditionFactory.buildCondition(                 conditionTypeEnum, this.multiTenancyProperties.getMultiTenancyQueryColumn(), multiTenancyQuery);         String newSql = this.appendWhereCondition(originSql, multiTenancyQueryCondition);         // 使用反射替换BoundSql的sql语句         Reflections.setFieldValue(boundSql, "sql", newSql);         // 把新的查询放到statement里         MappedStatement newMs = copyFromMappedStatement(mappedStatement, parameterObject -> boundSql);         args[0] = newMs;         // 执行带有过滤查询的语句         return invocation.proceed();     }      /**      * 追加查询过滤查询条件      */     private String appendWhereCondition(String originSql, String condition) {          if (StringUtils.isBlank(originSql) || StringUtils.isBlank(condition)) {             return originSql;         }         String[] sqlSplit = originSql.toLowerCase().split(WHERE_CONDITION.trim());         // 没有查询条件         if (this.noWhereCondition(sqlSplit)) {             return originSql + WHERE_CONDITION + condition;         }         // 包含查询条件,添加到第一个查询条件的位置         else {             String sqlBeforeWhere = sqlSplit[0];             String sqlAfterWhere = sqlSplit[1];             return sqlBeforeWhere + WHERE_CONDITION + condition + AND_CONDITION + sqlAfterWhere;         }     }     /**      * 没有查询条件      */     private boolean noWhereCondition(String[] sqlSplit) {         return ArrayUtils.isNotEmpty(sqlSplit) && 1 == sqlSplit.length;     }      private boolean matchPreTableName(String sql, String preTableName) {          if (StringUtils.isBlank(preTableName)) {             return true;         } else {             return StringUtils.containsIgnoreCase(sql, preTableName);         }     }      private boolean isMatchMultiTenancy(Object parameter) {          return Objects.nonNull(parameter)                 && parameter instanceof MultiTenancyQuery                 && ((MultiTenancyQuery) parameter).isFiltered();     }      @Override     public Object plugin(Object target) {         return Plugin.wrap(target, this);     }      @Override     public void setProperties(Properties properties) {          Object multiTenancyQueryColumn;         if (Objects.nonNull(multiTenancyQueryColumn = properties.get(MULTI_TENANCY_QUERY_COLUMN_PROPERTY))) {             multiTenancyProperties.setMultiTenancyQueryColumn(multiTenancyQueryColumn.toString());         }         Object conditionType;         if (Objects.nonNull(conditionType = properties.get(CONDITION_TYPE_PROPERTY))) {             multiTenancyProperties.setConditionType(conditionType.toString());         }     }      private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {         MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());         builder.resource(ms.getResource());         builder.fetchSize(ms.getFetchSize());         builder.statementType(ms.getStatementType());         builder.keyGenerator(ms.getKeyGenerator());         if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {             builder.keyProperty(ms.getKeyProperties()[0]);         }         builder.timeout(ms.getTimeout());         builder.parameterMap(ms.getParameterMap());         builder.resultMaps(ms.getResultMaps());         builder.resultSetType(ms.getResultSetType());         builder.cache(ms.getCache());         builder.flushCacheRequired(ms.isFlushCacheRequired());         builder.useCache(ms.isUseCache());         return builder.build();     } }  
/**  * mybatis插件配置  **/     //MybatisPlusConfig.class         @Bean     public MultiTenancyQueryInterceptor platformQueryInterceptor() {         MultiTenancyQueryInterceptor platformQueryInterceptor = new MultiTenancyQueryInterceptor();         Properties properties = new Properties();         // 需要过滤的数据库字段         properties.setProperty(MULTI_TENANCY_QUERY_COLUMN_PROPERTY, "company_id");         platformQueryInterceptor.setProperties(properties);         return platformQueryInterceptor;     }  /**  * MultiTenancyQuery 查询参数设置  **/       @Test     public void successToMultiTenancyQuery() {          GoodsQuery goodsQuery = new GoodsQuery();         // 设置商品code查询参数         goodsQuery.setCode("phone");         goodsQuery.setFiltered(true);         // 设置company_id对应的查询值         goodsQuery.setMultiTenancyQueryValue(Lists.list(1, 2));         // 设置sql查询的数据库前缀名         goodsQuery.setPreTableName("g");         List<Goods> goodsList = goodsService.findList(goodsQuery);         Assert.assertNotNull(goodsList);     }        // sql 语句     <select id="findList" resultType="goods">          SELECT         g.id AS 'id',         g.name AS 'name',         g.code AS 'code',         g.size AS 'size',         g.weight AS 'weight',         g.description AS 'description',         g.type AS 'type',         g.state AS 'state'         FROM boutique_goods g         <where>             <!-- 只定义了code查询条件 -->             <if test="null!=code and ''!=code">                 g.code=#{code}             </if>         </where>     </select>
 
public class MultiTenancyQuery implements Serializable {     private static final long serialVersionUID = -5841093611020112607L;     /**      * 多租户过滤值      */     protected Object multiTenancyQueryValue;     /**      * 可以过滤的      */     protected boolean isFiltered;     /**      * 数据库表前缀名      */     protected String preTableName;      public MultiTenancyQuery() {         // 默认不执行多租户过滤         this.isFiltered = false;     } } 
public class MultiTenancyProperties implements Serializable {      private static final long serialVersionUID = -1982635513027523884L;      public static final String MULTI_TENANCY_QUERY_COLUMN_PROPERTY = "multiTenancyQueryColumn";     public static final String CONDITION_TYPE_PROPERTY = "conditionType";     /**      * 租户的字段名称      */     private String multiTenancyQueryColumn;     /**      * 租户字段查询条件      * {@link ConditionFactory.ConditionTypeEnum}      */     private String conditionType;      public MultiTenancyProperties() {         // 默认使用IN 条件,例如 id in(1,2,3)         this.conditionType = ConditionFactory.ConditionTypeEnum.IN.name();     } } 
public class DefaultConditionFactory implements ConditionFactory {      private static final String EQUAL_CONDITION = "=";     private static final String IN_CONDITION = " in ";     private final DBColumnValueFactory columnValueFactory;      public DefaultConditionFactory() {         this.columnValueFactory = new DefaultDBColumnValueFactory();     }       @Override     public String buildCondition(ConditionTypeEnum conditionType, String multiTenancyQueryColumn, MultiTenancyQuery multiTenancyQuery) {          StringBuilder stringBuilder = new StringBuilder();         String columnValue = this.columnValueFactory.buildColumnValue(multiTenancyQuery.getMultiTenancyQueryValue());         // 根据条件类型设置查询条件         switch (conditionType) {             // IN条件             case IN:                 stringBuilder                         .append(multiTenancyQueryColumn)                         .append(IN_CONDITION)                         .append("(")                         .append(columnValue)                         .append(")");                 break;             // 相等条件             case EQUAL:             default:                 stringBuilder                         .append(multiTenancyQueryColumn)                         .append(EQUAL_CONDITION)                         .append(columnValue);                 break;         }         // 设置数据库表别名         String preTableName;         if (StringUtils.isNotBlank(preTableName = multiTenancyQuery.getPreTableName())) {             stringBuilder.insert(0, ".")                     .insert(0, preTableName);         }         return stringBuilder.toString();     } } 
// MultiTenancyQueryInterceptor.class  private String appendWhereCondition(String originSql, String condition) {          if (StringUtils.isBlank(originSql) || StringUtils.isBlank(condition)) {             return originSql;         }         String[] sqlSplit = originSql.toLowerCase().split(WHERE_CONDITION.trim());         // 没有查询条件         if (this.noWhereCondition(sqlSplit)) {             return originSql + WHERE_CONDITION + condition;         }         // 包含查询条件,添加到第一个查询条件的位置         else {             String sqlBeforeWhere = sqlSplit[0];             String sqlAfterWhere = sqlSplit[1];             return sqlBeforeWhere + WHERE_CONDITION + condition + AND_CONDITION + sqlAfterWhere;         }     }
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算
 官方软件产品操作指南 (170)
官方软件产品操作指南 (170)