在中台服务或者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网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算