mybatis 多租户,新增时加注入字段,查改删添加where条件

多租户字段隔离级别 ,新增时加注入字段,查改删添加where条件。 如果是表隔离,或数据库隔离,很好改,在SqlConditionHelper收集表名,然后字符串替换表名即可

定义多租户注解 , 添加多注解的contrler 或者接口 开启多租户模式

/**
 * 开启 地区 多租户
 * @author xiaopeng
 * @date 2021-06-09
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSpace {

}

上下文参数传递工具类

public class ContextHolderUtil {


    /**
     * 获取request
     * @return
     */
    public static HttpServletRequest getRequest() {
        var obj = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
        return obj == null ? null : obj.getRequest();
    }

    /**
     * 获取httpSession
     * @return
     */
    public static HttpSession getSession() {
        var request = getRequest();
        return  request == null ? null : request.getSession();
    }

    /**
     * 获取request中的值
     * @param key request的key
     * @return
     */
    public static Object getRequestAttribute(String key) {
        var request = getRequest();
        return request == null ? null : request.getAttribute(key);
    }

    /**
     * 获取 Token 中的用户 id
     * @return
     */
    public static Integer getAuthedUserId() {
        var requestArr =  getRequestAttribute("id");
        return Integer.parseInt(requestArr.toString());
    }

    //获取角色id
    public static Integer getAuthedUserRoleId() {
        return Integer.parseInt(String.valueOf(getRequestAttribute("roleId")));
    }

    //获取角色权重
    public static Integer getAuthedUserRoleWeight() {
        return Integer.parseInt(String.valueOf(getRequestAttribute("roleWeight")));
    }

    //获取用户信息
    public static UserInfo getAuthedUserInfo() {
        return (UserInfo) getRequestAttribute("userInfo");
    }

    private static String openDataSpace = "open";
    //开启 地区 多租户模式
    public static void openDataSpace(){
        HttpServletRequest request = getRequest();
        request.setAttribute("openDataSpace", "open");
    }
    //判断是否开启 地区 多租户模式
    public static boolean isDataSpace(){
        HttpServletRequest request = getRequest();
        return openDataSpace.equals(request.getAttribute("openDataSpace"));
    }


    //获取前端传过来的 地区id
    public static String getDataSpaceId(){
        HttpServletRequest request = getRequest();
        String dataSpaceId = request.getHeader("SpaceId");
        if (StringUtil.isEmpty(dataSpaceId)){
            //请求头拿不到的话, 去url参数那里试一下
            dataSpaceId = request.getParameter("SpaceId");
        }
        //未选择地区id 强制报错退出
        CommonUtil.expressionThrowResponseCodeAndSetAttribute(StringUtil.isEmpty(dataSpaceId), ResponseCode.NOT_REGION);
        return dataSpaceId;
    }

}

注解切面, 将注解的信息保存进 上下文参数

@Component
@Aspect
@Slf4j
public class DataSpaceAspect {

    @Pointcut("@annotation(com.xxxx.xxxx.common.annotation.DataSpace)")
    public void pointcut(){
    }

    @Pointcut("execution(* com.xxxx.xxxx..*Controller.*(..))")
    public void pointcutClass(){
    }

    @Before("pointcut()")
    public void openDataSpace(){
        ContextHolderUtil.openDataSpace();
    }


    /**
     * 拦截所有control接口
     */
    @Around("pointcutClass()")
    public Object openDataSpace(ProceedingJoinPoint joinPoint) throws Throwable {
        Class<?> clazz = joinPoint.getTarget().getClass();
        //如果类上面带 DataSpace 注解,开启多租户模式
        DataSpace annotation = clazz.getAnnotation(DataSpace.class);
        if (annotation != null) {
            ContextHolderUtil.openDataSpace();
        }
        return joinPoint.proceed();
    }

}

multi-tenant.properties 配置文件 配置多租户字段,以及表(逗号分割)

tenant.id-field=region_id
tenant.table=tfmes_sales_order,tfmes_work_procedure_data

mybatis SqlSessionFactory 添加自己写的mybatis多租户拦截器

@Configuration
@PropertySource("classpath:multi-tenant.properties")
public class TfmesMybatisConfig {


    /**
     * 多租户字段名称
     */
    @Value("${tenant.id-field}")
    private String tenantIdField;

    /**
     * 需要识别多租户字段的表名称列表
     */
    @Value("${tenant.table}")
    private String tableSet;

    @Bean
    public String interceptorTfmes(SqlSessionFactory sqlSessionFactory) {
    //如果多数据源 配置了多个sqlSessionFactory public String interceptorTfmes(@Qualifier("xxxxSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        MultiTenantPlugin multiTenantPlugin = new MultiTenantPlugin();
        Properties properties = new Properties(4);
        properties.put("tenantIdField", tenantIdField);
        properties.put("tableNames", tableSet);

        multiTenantPlugin.setProperties(properties);
        // 可添加多个mybatis拦截器
        sqlSessionFactory.getConfiguration().addInterceptor(multiTenantPlugin);
        return "interceptorTfmes";
    }

}

MultiTenantPlugin mybatis拦截器

@Slf4j
@Intercepts({@Signature(
        type = Executor.class,
        method = "update",
        args = {MappedStatement.class, Object.class}
), @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
        type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})
//@Component    不要这个 MybatisConfig.java 已经注入了
public class MultiTenantPlugin implements Interceptor {

    /**
     * 多租户字段名称
     */
    private String tenantIdField;

    /**
     * 需要识别多租户字段的表名称列表
     */
    private Set<String> tableSet;

    private SqlConditionHelper conditionHelper;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        //如果没有 地区 多租户 不生成新sql
        if (!ContextHolderUtil.isDataSpace()){
            return invocation.proceed();
        }

        //获取前端传过来的 地区id
        String tenantFieldValue = ContextHolderUtil.getDataSpaceId();

        //@Signature 上面拦截的方法 的参数
        final Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        //这个巨重要 不然会 新sql成功生成, 但mybatis运行的是旧sql
        BoundSql boundSql;
        if(args.length == 6){
            // 6 个参数时
            boundSql = (BoundSql) args[5];
        } else {
            boundSql = ms.getBoundSql(parameter);
        }

        String processSql = boundSql.getSql();

        log.info("替换前SQL:{}", processSql);

        //新sql
        String newSql = processSql;
        //如果是新增, 新增字段里加上 多租户保存
        if (SqlCommandType.INSERT == ms.getSqlCommandType()) {
            Statement statement = CCJSqlParserUtil.parse(processSql);
            Insert insertStatement = (Insert) statement;
            String tableName = insertStatement.getTable().getName();

            // 去除反引号
            tableName = tableName.replace("`", "");
            if (tableSet != null && tableSet.contains(tableName)){
                List<Column> columns = insertStatement.getColumns();
                columns.add(new Column(tenantIdField));
                insertStatement.setColumns(columns);

                //单个插入 或者 批量插入
                ItemsList itemsList = insertStatement.getItemsList();
                if (itemsList instanceof ExpressionList){
                    ExpressionList expressionList = (ExpressionList) itemsList;
                    List<Expression> list = new ArrayList<>();
                    list.addAll(expressionList.getExpressions());
                    list.add(CCJSqlParserUtil.parseExpression(tenantFieldValue));
                    expressionList.setExpressions(list);
                    insertStatement.setItemsList(expressionList);
                } else if (itemsList instanceof MultiExpressionList){
                    MultiExpressionList multiExpressionList = (MultiExpressionList) itemsList;
                    List<ExpressionList> expressionListList = multiExpressionList.getExpressionLists();
                    for (ExpressionList expressionList : expressionListList) {
                        List<Expression> list = new ArrayList<>();
                        list.addAll(expressionList.getExpressions());
                        list.add(CCJSqlParserUtil.parseExpression(tenantFieldValue));
                        expressionList.setExpressions(list);
                    }
                    insertStatement.setItemsList(multiExpressionList);
                }

                newSql = insertStatement.toString();
            }
        } else {
            //查询、修改、删除  where条件添加多租户
            newSql = addTenantCondition(processSql, tenantFieldValue);
        }


        //通过反射修改sql字段
        MetaObject boundSqlMeta = MetaObject.forObject(boundSql, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        // 把新sql设置到boundSql
        boundSqlMeta.setValue("sql", newSql);


        // 重新new一个查询语句对象
        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,
                boundSql.getParameterMappings(), boundSql.getParameterObject());
        // 把新的查询放到statement里
        MappedStatement newMs = newMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            String prop = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(prop)) {
                newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
            }
        }
        args[0] = newMs;

        log.info("替换后SQL:{}", newSql);
        return invocation.proceed();
    }





    @Override
    public void setProperties(Properties properties) {
        tenantIdField = properties.getProperty("tenantIdField");
        if (StringUtils.isBlank(tenantIdField)) {
            throw new IllegalArgumentException("MultiTenantPlugin need tenantIdField property value");
        }


        String tableNames = properties.getProperty("tableNames");
        if (!StringUtils.isBlank(tableNames)) {
            tableSet = new HashSet<>(Arrays.asList(StringUtils.split(tableNames, ",")));
        }


        // 多租户条件字段决策器
        TableFieldConditionDecision conditionDecision = new TableFieldConditionDecision() {
            @Override
            public boolean isAllowNullValue() {
                return false;
            }
            @Override
            public boolean adjudge(String tableName, String fieldName) {
                // 去除反引号
                tableName = tableName.replace("`", "");
                return tableSet != null && tableSet.contains(tableName);
            }
        };
        conditionHelper = new SqlConditionHelper(conditionDecision);
    }




    /**
     * 定义一个内部辅助类,作用是包装 SQL
     */
    static class BoundSqlSqlSource implements SqlSource {

        private final BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }

    }


    /**
     * 根据原MappedStatement更新SqlSource生成新MappedStatement
     *
     * @param ms MappedStatement
     * @param newSqlSource 新SqlSource
     * @return
     */
    private MappedStatement newMappedStatement(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();
    }




    /**
     * 给sql语句where添加租户id过滤条件
     *
     * @param sql      要添加过滤条件的sql语句
     * @param tenantId 当前的租户id
     * @return 添加条件后的sql语句
     */
    private String addTenantCondition(String sql, String tenantId) {
        if (StringUtils.isBlank(sql) || StringUtils.isBlank(tenantIdField)) {
            return sql;
        }
        List<SQLStatement> statementList = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
        if (statementList.size() == 0) {
            return sql;
        }

        SQLStatement sqlStatement = statementList.get(0);
        conditionHelper.addStatementCondition(sqlStatement, tenantIdField, tenantId);
        return SQLUtils.toSQLString(statementList, JdbcConstants.MYSQL);
    }

}

sql where 条件 处理工具类

public class SqlConditionHelper {

    private final TableFieldConditionDecision conditionDecision;

    public SqlConditionHelper(TableFieldConditionDecision conditionDecision) {
        this.conditionDecision = conditionDecision;
    }

    /**
     * 为sql语句添加指定where条件
     *
     * @param sqlStatement
     * @param fieldName
     * @param fieldValue
     */
    public void addStatementCondition(SQLStatement sqlStatement, String fieldName, String fieldValue) {
        if (sqlStatement instanceof SQLSelectStatement) {
            SQLSelectQueryBlock queryObject = (SQLSelectQueryBlock) ((SQLSelectStatement) sqlStatement).getSelect().getQuery();
            addSelectStatementCondition(queryObject, queryObject.getFrom(), fieldName, fieldValue);
        } else if (sqlStatement instanceof SQLUpdateStatement) {
            SQLUpdateStatement updateStatement = (SQLUpdateStatement) sqlStatement;
            addUpdateStatementCondition(updateStatement, fieldName, fieldValue);
        } else if (sqlStatement instanceof SQLDeleteStatement) {
            SQLDeleteStatement deleteStatement = (SQLDeleteStatement) sqlStatement;
            addDeleteStatementCondition(deleteStatement, fieldName, fieldValue);
        } else if (sqlStatement instanceof SQLInsertStatement) {
            SQLInsertStatement insertStatement = (SQLInsertStatement) sqlStatement;
            addInsertStatementCondition(insertStatement, fieldName, fieldValue);
        }
    }

    /**
     * 为insert语句添加where条件
     *
     * @param insertStatement
     * @param fieldName
     * @param fieldValue
     */
    private void addInsertStatementCondition(SQLInsertStatement insertStatement, String fieldName, String fieldValue) {
        if (insertStatement != null) {
            SQLSelect sqlSelect = insertStatement.getQuery();
            if (sqlSelect != null) {
                SQLSelectQueryBlock selectQueryBlock = (SQLSelectQueryBlock) sqlSelect.getQuery();
                addSelectStatementCondition(selectQueryBlock, selectQueryBlock.getFrom(), fieldName, fieldValue);
            }
        }
    }


    /**
     * 为delete语句添加where条件
     *
     * @param deleteStatement
     * @param fieldName
     * @param fieldValue
     */
    private void addDeleteStatementCondition(SQLDeleteStatement deleteStatement, String fieldName, String fieldValue) {
        SQLExpr where = deleteStatement.getWhere();
        //添加子查询中的where条件
        addSQLExprCondition(where, fieldName, fieldValue);

        SQLExpr newCondition = newEqualityCondition(deleteStatement.getTableName().getSimpleName(),
            deleteStatement.getTableSource().getAlias(), fieldName, fieldValue, where);
        deleteStatement.setWhere(newCondition);

    }

    /**
     * where中添加指定筛选条件
     *
     * @param where      源where条件
     * @param fieldName
     * @param fieldValue
     */
    private void addSQLExprCondition(SQLExpr where, String fieldName, String fieldValue) {
        if (where instanceof SQLInSubQueryExpr) {
            SQLInSubQueryExpr inWhere = (SQLInSubQueryExpr) where;
            SQLSelect subSelectObject = inWhere.getSubQuery();
            SQLSelectQueryBlock subQueryObject = (SQLSelectQueryBlock) subSelectObject.getQuery();
            addSelectStatementCondition(subQueryObject, subQueryObject.getFrom(), fieldName, fieldValue);
        } else if (where instanceof SQLBinaryOpExpr) {
            SQLBinaryOpExpr opExpr = (SQLBinaryOpExpr) where;
            SQLExpr left = opExpr.getLeft();
            SQLExpr right = opExpr.getRight();
            addSQLExprCondition(left, fieldName, fieldValue);
            addSQLExprCondition(right, fieldName, fieldValue);
        } else if (where instanceof SQLQueryExpr) {
            SQLSelectQueryBlock selectQueryBlock = (SQLSelectQueryBlock) (((SQLQueryExpr) where).getSubQuery()).getQuery();
            addSelectStatementCondition(selectQueryBlock, selectQueryBlock.getFrom(), fieldName, fieldValue);
        }
    }

    /**
     * 为update语句添加where条件
     *
     * @param updateStatement
     * @param fieldName
     * @param fieldValue
     */
    private void addUpdateStatementCondition(SQLUpdateStatement updateStatement, String fieldName, String fieldValue) {
        SQLExpr where = updateStatement.getWhere();
        //添加子查询中的where条件
        addSQLExprCondition(where, fieldName, fieldValue);
        SQLExpr newCondition = newEqualityCondition(updateStatement.getTableName().getSimpleName(),
            updateStatement.getTableSource().getAlias(), fieldName, fieldValue, where);
        updateStatement.setWhere(newCondition);
    }

    /**
     * 给一个查询对象添加一个where条件
     *
     * @param queryObject
     * @param fieldName
     * @param fieldValue
     */
    private void addSelectStatementCondition(SQLSelectQueryBlock queryObject, SQLTableSource from, String fieldName, String fieldValue) {
        if (StringUtils.isBlank(fieldName) || from == null || queryObject == null) {
            return;
        }

        SQLExpr originCondition = queryObject.getWhere();
        // 添加子查询中的where条件
        addSQLExprCondition(originCondition, fieldName, fieldValue);
        if (from instanceof SQLExprTableSource) {
            // TODO 对于JOIN_TABLE支持有问题,待优化
            String tableName = ((SQLIdentifierExpr) ((SQLExprTableSource) from).getExpr()).getName();
            String alias = from.getAlias();
            SQLExpr newCondition = newEqualityCondition(tableName, alias, fieldName, fieldValue, originCondition);
            queryObject.setWhere(newCondition);
        } else if (from instanceof SQLJoinTableSource) {
            SQLJoinTableSource joinObject = (SQLJoinTableSource) from;
            SQLTableSource left = joinObject.getLeft();
            SQLTableSource right = joinObject.getRight();

            addSelectStatementCondition(queryObject, left, fieldName, fieldValue);
            addSelectStatementCondition(queryObject, right, fieldName, fieldValue);

        } else if (from instanceof SQLSubqueryTableSource) {
            SQLSelect subSelectObject = ((SQLSubqueryTableSource) from).getSelect();
            SQLSelectQueryBlock subQueryObject = (SQLSelectQueryBlock) subSelectObject.getQuery();
            addSelectStatementCondition(subQueryObject, subQueryObject.getFrom(), fieldName, fieldValue);
        } else {
            throw new NotImplementedException("未处理的异常");
        }
    }

    /**
     * 根据原来的condition创建一个新的condition
     *
     * @param tableName       表名称
     * @param tableAlias      表别名
     * @param fieldName 字段名
     * @param fieldValue 字段值
     * @param originCondition 原始条件
     * @return
     */
    private SQLExpr newEqualityCondition(String tableName, String tableAlias, String fieldName, String fieldValue, SQLExpr originCondition) {
        //如果不需要设置条件
        if (!conditionDecision.adjudge(tableName, fieldName)) {
            return originCondition;
        }
        //如果条件字段不允许为空
        if (fieldValue == null && !conditionDecision.isAllowNullValue()) {
            return originCondition;
        }


        String filedName = StringUtils.isBlank(tableAlias) ? tableName + "." + fieldName : tableAlias + "." + fieldName;

        // 生成查询条件使用IN进行查询
//        var condition = new SQLInListExpr(new SQLIdentifierExpr(filedName), false);
//        condition.addTarget(new SQLVariantRefExpr(fieldValue));

        //生成查询条件使用 = 进行查询
        var condition = new SQLBinaryOpExpr(
                new SQLIdentifierExpr(filedName),
                SQLBinaryOperator.Equality,
                new SQLVariantRefExpr(fieldValue)
        );
        return SQLUtils.buildCondition(SQLBinaryOperator.BooleanAnd, condition, false, originCondition);
    }


    public static void main(String[] args) {
//        String sql = "select * from user s  ";
//        String sql = "select * from user s where s.name='333'";
//        String sql = "select * from (select * from tab t where id = 2 and name = 'wenshao') s where s.name='333'";
//        String sql="select u.*,g.name from user u join user_group g on u.groupId=g.groupId where u.name='123'";

//        String sql = "update user set name=? where id =(select id from user s)";
//        String sql = "delete from user where id = ( select id from user s )";

        String sql = "insert into user (id,name) select g.id,g.name from user_group g where id=1";

//        String sql = "SELECT id, pw_id, warehouse_top, warehouse_level, warehouse_name  , is_default, can_to, disabled, data_space_id, created_at  , created_by, updated_at, updated_by FROM erp_warehouses_warehouse WHERE disabled = 0 AND is_default = 1 AND warehouse_top = (   SELECT id   FROM erp_warehouses_warehouse   WHERE is_default = 1    AND disabled = 0  )";
        List<SQLStatement> statementList = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
        SQLStatement sqlStatement = statementList.get(0);

        SQLInsertStatement sqlSelectStatement = (SQLInsertStatement) sqlStatement;
        //决策器定义
        SqlConditionHelper helper = new SqlConditionHelper(new TableFieldConditionDecision() {
            @Override
            public boolean adjudge(String tableName, String fieldName) {
                return true;
            }

            @Override
            public boolean isAllowNullValue() {
                return false;
            }
        });
        //添加多租户条件,domain是字段ignc,yay是筛选值
        helper.addStatementCondition(sqlStatement, "data_space_id", "1");
        System.out.println("源sql:" + sql);
        System.out.println("修改后sql:" + SQLUtils.toSQLString(statementList, JdbcConstants.MYSQL));
    }

}

版权声明:本文为weixin_41423378原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>