MybatisPlus动态拼接sql和动态配置表名

最近有个需求,需要支持动态配置表别名和动态逻辑拼接,我是使用mybatis-plus+SpringSecurity完成的。
首先自定义注解@DataColumn
主要属性alias()和operator(),需要设置默认值default,这样可以灵活选择需要使用默认值还是需要动态配置

@Documented
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Repeatable(DataScope.class)
public @interface DataColumn {
    String alias() default "";

    String name() default "";

    String[] deptNames() default {};

    SqlColumnTypeEnum type();

    LogicOperatorEnum operator() default LogicOperatorEnum.OR;
}

添加逻辑运算符枚举类LogicOperatorEnum

/**
 * 逻辑运算符
 *
 * @author zlw
 **/
public enum LogicOperatorEnum {
    AND, OR
}

接下来需要自定义一个mybatis-plus拦截器,拦截参数值并设置到security的公共域,方便在数据权限过滤器sql拼接时获取

/**
 * 获取方法参数
 *
 * @author zlw
 **/
public class ParamInterceptor implements InnerInterceptor {
    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        // 获取 @Param 注解标记的参数值
        if (parameter instanceof Map) {
            Map<String, Object> paramMap = (Map<String, Object>) parameter;
            String alias = (String) paramMap.get("alias");
            LogicOperatorEnum operator = (LogicOperatorEnum) paramMap.get("operator");
            System.out.println("Alias: " + alias);
            System.out.println("Operator: " + operator);
            //存储到SpringSecurityContext
            SecurityConstants.getLocalMap().put("alias", alias);
            SecurityConstants.getLocalMap().put("operator", operator);
        }

    }
}

添加数据权限过滤器,根据方法参数值动态设置表别名,以及动态设置拼接的逻辑运算符。并且如果方法参数没传值的话,是可以用注解里的固定属性的,实现灵活配置。

/**
 * 数据权限过滤器
 *
 * @author zlw
 **/
public class DataScopeInterceptor implements DataPermissionHandler {
    private static final Logger log = LoggerFactory.getLogger(DataScopeInterceptor.class);

    public static final Long APP_ID = 11L;

    private static final int LIST_SIZE = 0;

    @Override
    public Expression getSqlSegment(Expression where, String mappedStatementId) {
        Expression expression = null;
        //获取接口上数据配置
        DataScopeProperty dataScopeProperty = AuthContextHolder.getDataScopeProperty();
        if (null == dataScopeProperty || BeanUtil.isEmpty(dataScopeProperty) || dataScopeProperty.isIgnore()) {
            return where;
        }
        Assert.isFalse(BeanUtil.isEmpty(SecurityConstants.getOperationDTO()), "无法从上下文获取用户信息");
        UserRoleAssist userRoleAssist = new UserRoleAssist();
        if (SecurityConstants.getLocalMap().containsKey("header-role-assist")) {
            userRoleAssist = JSON.parseObject(String.valueOf(SecurityConstants.get("header-role-assist")), UserRoleAssist.class);
        }
        //1.判断上下文权限情况authDeptInfoList -- 当前用户
        AuthDeptInfoVO authDeptInfo = SecurityConstants.getOperationDTO().getAuthDeptInfo();
        List<Long> workerDeptInfoList = authDeptInfo.getDeptId();
        if (workerDeptInfoList.size() <= LIST_SIZE) {
            log.info(mappedStatementId + "该用户没有相关部门权限,返回空值");
            EqualsTo equalsTo = new EqualsTo();
            equalsTo.setLeftExpression(new Column("1"));
            equalsTo.setRightExpression(new LongValue(0));
            if (where != null) {
                return new AndExpression(where, equalsTo);
            } else {
                return equalsTo;
            }
        }
        //2.获取当前用户
        try {
            String employeeNum = SecurityConstants.getOperationDTO().getPersonInfoVO().getEmployeeNum();
            if ("78252".equals(employeeNum) || "98491".equals(employeeNum) || "110485".equals(employeeNum) || "87358".equals(employeeNum) || "55482".equals(employeeNum) || "100772".equals(employeeNum) || "104181".equals(employeeNum) || "61577".equals(employeeNum) || "101883".equals(employeeNum)) {
                return where;
            }
        } catch (Exception e) {
            log.error("DataScopeInterceptor::getSqlSegment::上下文无法获取人员工号!!{}", e.getMessage());
            e.printStackTrace();
        }
        String token = SecurityConstants.getLocalMap().containsKey("header-access-token") ? SecurityConstants.get("header-access-token") : "无token";
        Long userId = SecurityConstants.getOperationDTO().getUserId();
        log.info("该用户:{}-{},返回token信息:{}", userId, SecurityConstants.getOperationDTO().getUserName(), token);
        // 获取动态参数
        String alias = (String) SecurityConstants.getLocalMap().get("alias");
        LogicOperatorEnum operator = (LogicOperatorEnum) SecurityConstants.getLocalMap().get("operator");
        //3.sql组装
        if (null != dataScopeProperty.getColumns() && !dataScopeProperty.getColumns().isEmpty()) {
            for (DataColumnProperty dataColumnProperty : dataScopeProperty.getColumns()) {
                DataColumnProperty col = dataColumnProperty;
                // 如果方法参数中设置了表别名和运算符则动态配置,否则直接用注解固定值
                if (StringUtils.hasText(alias)) {
                    col.setAlias(alias);
                }
                if (ObjectUtils.isNotEmpty(operator)) {
                    col.setOperator(operator);
                }
                StringBuffer strBuf = new StringBuffer();
                String name = strBuf.append(dataColumnProperty.getAlias()).append(".").append(dataColumnProperty.getName()).toString();
                // 执行部门”、“最新所属部门"数组
                String[] nameArr = col.getDeptNames();
                if (SqlColumnTypeEnum.AUTH_DEPT.equals(dataColumnProperty.getType()) && workerDeptInfoList.size() > LIST_SIZE && CollectionUtils.isNotEmpty(Arrays.asList(nameArr))) {
                    //部门权限-new-支持按“执行部门”、“最新所属部门”权限查询,逻辑为或关系
                    ParenthesedExpressionList itemsList = new ParenthesedExpressionList(workerDeptInfoList);
                    log.info("该用户:{}权限配置部门数为:{}-{},转list后个数为:{}", userId, SecurityConstants.getOperationDTO().getUserName(), workerDeptInfoList.size(), workerDeptInfoList.size());
                    for (String colName : nameArr) { // foreach列名
                        Column column = new Column(col.getAlias() + "." + colName);
                        InExpression inExpr = new InExpression(column, itemsList);

                        if (expression == null) {
                            expression = inExpr;
                        } else {
                            if (LogicOperatorEnum.OR.equals(dataColumnProperty.getOperator())) {
                                expression = new OrExpression(expression, inExpr); // OR连接多列
                            } else {
                                expression = new AndExpression(expression, inExpr); // AND连接多列
                            }
//                            expression = new OrExpression(expression, inExpr); // OR连接多列
                        }
                        log.debug("部门列[{}]添加IN条件", colName);
                    }
                    //部门权限-old-废弃
//                    ParenthesedExpressionList itemsList = new ParenthesedExpressionList(workerDeptInfoList);
//                    log.info("该用户:{}权限配置部门数为:{}-{},转list后个数为:{}", userId, SecurityConstants.getOperationDTO().getUserName(), workerDeptInfoList.size(), workerDeptInfoList.size());
//                    InExpression inExpression = new InExpression(new Column(name), itemsList);
//                    expression = ObjectUtils.isNotEmpty(expression) ? new OrExpression(expression, inExpression) : inExpression;
                } 
            }
        }
        if (ObjectUtils.isNotEmpty(expression) && ObjectUtils.isNotEmpty(where)) {
            return new AndSubWhereExpression(where, expression);
        } else if (ObjectUtils.isNotEmpty(expression) && ObjectUtils.isEmpty(where)) {
            return expression;
        }
        return where;
    }
}

最后一步就是配置MP的Bean添加拦截器插件了,这里要注意参数拦截插件要添加在数据权限插件之前,因为要在sql拼接之前设置方法参数,sql拼接才能获取到。

@Configuration
public class MybatisPlusPageConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisResultDataInterceptor interceptor = new MybatisResultDataInterceptor();
        //数据权限
        DataPermissionInterceptor dataPermissionInterceptor = new DataPermissionInterceptor();
        DataScopeInterceptor dataScopeInterceptor = new DataScopeInterceptor();
        dataPermissionInterceptor.setDataPermissionHandler(dataScopeInterceptor);
        //自定义参数拦截插件
        interceptor.addInnerInterceptor(new ParamInterceptor());
        //自定义数据权限插件
        interceptor.addInnerInterceptor(dataPermissionInterceptor);
        //分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
        // 乐观锁
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        // 防止全表更新与删除
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}

使用:
mapper加上注解@DataColumn设置拼接的部门数组
参数添加 @Param(“alias”) String alias, @Param(“operator”) LogicOperatorEnum operator,动态配置表别名和逻辑运算符
mapper.java

@DataScope(value = {@DataColumn(deptNames = {"dept_id", "last_dept_id"}, type = SqlColumnTypeEnum.AUTH_DEPT)})
    List<OperationInsuranceBaseVo> page(@Param("bo") OperationInsuranceBaseBo bo, @Param("alias") String alias, @Param("operator") LogicOperatorEnum operator);

service调用的时候,动态配置表别名和逻辑运算符
service.java

    public String page(OperationInsuranceBaseBo bo) {
        Page<OperationInsuranceBaseVo> page = new Page<>(bo.getPageNum(), bo.getPageSize());
        //如果外部用户,则数据过滤
        if (SecurityConstants.getOperationDTO().getPersonInfoVO().getEmployeeNum().startsWith("WB")) {
            bo.setInsuranceCompany(SecurityConstants.getOperationDTO().getPersonInfoVO().getDisplayName());
        }
        page = operationInsuranceBaseDao.page(page, bo,"base",LogicOperatorEnum.OR);
        return JSONUtil.toJsonStr(R.ok(page));
    }
<think>嗯,用户想用MyBatis-Plus实现动态的LEFT JOIN查询。首先,我需要回忆一下MyBatis-Plus的动态功能。记得MyBatis-Plus有一个TableNameHandler接口,允许在运行时动态改变表。这可能适用于分库分表或者多租户的场景,但用户现在需要的是LEFT JOIN中的动态。 然后,LEFT JOIN查询通常涉及多个表,而动态可能需要在SQL语句中替换某个表的字。比如用户可能有一个主表固定,而关联的表需要根据某些条件变化。这时候,动态处理器可能需要识别特定的表别,并在生成SQL时替换为实际的表。 接下来,我需要考虑如何在MyBatis-Plus中配置动态。通常是通过实现TableNameHandler并注册到配置中。例如,可以创建一个处理器,当表为"dynamic_table"时,替换成用户指定的实际表。但LEFT JOIN中的表可能需要不同的处理方式,比如根据JOIN的条件动态替换关联表的称。 然后,用户可能需要自定义SQL语句,因为MyBatis-Plus的Wrapper可能不太容易处理复杂的JOIN查询。这时候,可以使用@Select注解或者XML文件来编写自定义SQL,并在其中使用${dynamicTableName}这样的占位符,然后通过参数传入动态。不过这样可能不够安全,容易引发SQL注入,需要注意过滤输入。 另外,MyBatis-Plus的动态插件是否支持在JOIN子句中替换表?可能需要测试一下。例如,在SQL语句中,如果关联的表动态的,可以通过TableNameHandler来拦截并替换。例如,主表是tb_bursary,而关联的表tb_student可能需要根据某种规则动态改变表,比如分表的情况。 然后,用户的问题中还提到引用了一些其他内容,比如Flink的时态表函数JOIN动态表的概念,但可能这些MyBatis-Plus关系不大,主要需要关注如何在实际的MyBatis-Plus中实现动态的LEFT JOIN。 可能的步骤是:1. 创建动态处理器,识别需要替换的表;2. 在自定义的SQL语句中使用LEFT JOIN,并让动态处理器替换关联表的称;3. 在Mapper接口中定义方法,传递可能需要的条件参数。 需要注意的是,动态的替换要确保在生成SQL时正确替换,避免语法错误。例如,如果关联的表需要根据年份分表,比如student_2023,那么处理器需要根据当前上下文替换为正确的表。 此外,用户可能需要使用MyBatis动态SQL功能,比如在XML中使用<if>标签来判断参数,动态拼接,但这种方式同样需要注意SQL注入的问题,应该使用参数化的方式或者严格检查输入。 总结一下,可能的解决方案是结合MyBatis-Plus的动态插件来处理LEFT JOIN中的动态,同时在自定义SQL中正确编写JOIN语句,确保表替换正确。另外,可能需要传递参数来指定动态,并在处理器中根据参数进行替换。</think>### MyBatis-Plus实现动态LEFT JOIN查询的解决方案 #### 1. 使用动态处理器 MyBatis-Plus提供${\textsf{\small TableNameHandler}}$接口实现动态。首先创建处理器类: ```java public class DynamicTableNameHandler implements TableNameHandler { @Override public String dynamicTableName(String sql, String tableName) { if ("dynamic_table".equals(tableName)) { // 识别需要动态替换的表 return getActualTableName(); // 根据业务逻辑返回实际表 } return tableName; } } ``` #### 2. 配置插件注册处理器 ```java @Configuration public class MybatisPlusConfig { @Bean public DynamicTableNameParser dynamicTableNameParser() { DynamicTableNameParser parser = new DynamicTableNameParser(); parser.setTableNameHandlerMap(new HashMap<String, TableNameHandler>() {{ put("dynamic_table", new DynamicTableNameHandler()); }}); return parser; } } ``` #### 3. 编写自定义SQL语句 在Mapper.xml中使用动态语法: ```xml <select id="leftJoinQuery" resultType="map"> SELECT t.*, t1.student_name FROM main_table t LEFT JOIN ${dynamicTable} t1 ON t.id = t1.main_id WHERE t.create_time > #{createTime} </select> ``` #### 4. 参数化传递表 通过@Param注解传递动态参数: ```java @Mapper public interface BursaryMapper { List<Map<String, Object>> leftJoinQuery( @Param("dynamicTable") String dynamicTableName, @Param("createTime") Date createTime); } ``` #### 5. 复杂场景处理建议 对于需要动态字段的关联查询,建议: ```sql SELECT t.id, t1.name FROM fixed_table t LEFT JOIN ${dynamicTable} t1 ON t1.id = t.student_id AND t1.create_year = #{year} -- 动态条件 WHERE t.status = 1 ``` ### 注意事项 1. **SQL注入防护**:动态应通过白单校验 2. **性能优化**:对高频访问的动态表建议建立索引 3. **事务管理**:跨表操作需注意事务一致性 4. **分表策略**:建议采用哈希或时间分片策略[^3]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值