Mybatis注入新的参数

背景说明

一般业务级的SQL会关联用户信息,那么在写SQL是需要将当前用户获取然后注入SQL中,最常见的方法是将用户信息当做条件传入(Map,实体属性、参数值等)

但是这种情况下都会在一定程度上破环了接口的抽象性,不够友好。那么下面考虑使用Mybatis运行时注入参数值。

代码示例

import com.github.pagehelper.util.MetaObjectUtil;
import com.unitechs.framework.context.UserContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * mybatis 动态增加用户id,只针对查询操作有效。
 *
 * @author zhuyuan 2019-10-24 09:24:25
 */
@Component
@Intercepts(
        {
                @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}),
        }
)
@Slf4j
public class MybatisSqlParamInterceptor implements Interceptor {

    private static final String CURRENT_USER_ID_KEY = "currentUserId";
    private static final String CURRENT_USERNAME_KEY = "currentUsername";
    private static final int NEW_ADDING_SIZE = 2;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        RowBounds rowBounds = (RowBounds) args[2];
        ResultHandler resultHandler = (ResultHandler) args[3];
        Executor executor = (Executor) invocation.getTarget();
        CacheKey cacheKey;
        BoundSql boundSql;
        //将参数类型转化为MAP类型,同时将用户信息注入编译SQL
        parameter = processParameterObject(ms, parameter);
        if (args.length == 4) {
            boundSql = ms.getBoundSql(parameter);
            cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
        } else {
            cacheKey = (CacheKey) args[4];
            boundSql = (BoundSql) args[5];
        }
        return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
    }

    private Object processParameterObject(MappedStatement ms, Object parameterObject) {
        Map<String, Object> paramMap;
        if (parameterObject == null) {
            paramMap = new HashMap(NEW_ADDING_SIZE);
        } else if (parameterObject instanceof Map) {
            paramMap = new HashMap(((Map) parameterObject).size() + NEW_ADDING_SIZE);
            paramMap.putAll((Map) parameterObject);
        } else {
            paramMap = new HashMap(5);
            boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
            MetaObject metaObject = MetaObjectUtil.forObject(parameterObject);
            if (!hasTypeHandler) {
                String[] getterNames = metaObject.getGetterNames();
                int getterNamesLength = getterNames.length;

                for (int i = 0; i < getterNamesLength; ++i) {
                    String name = getterNames[i];
                    paramMap.put(name, metaObject.getValue(name));
                }
            } else {
                //注意、只有一个参数情况下只能使用'_parameter'注入了。
                paramMap.put("_parameter", parameterObject);
            }

            return this.processParameter(paramMap);
        }

        return this.processParameter(paramMap);
    }

    private Object processParameter(Map<String, Object> paramMap) {
        String userId = UserContextHolder.getUserId();
        String username = UserContextHolder.getUserName();
        if (StringUtils.isNotBlank(userId)) {
            log.debug("注入当前用户currentUserId=" + userId + "到查询参数中");
            log.debug("注入当前用户currentUsername=" + username + "到查询参数中");
            paramMap.put(CURRENT_USER_ID_KEY, Long.valueOf(userId));
            paramMap.put(CURRENT_USERNAME_KEY, username);
        }
        return paramMap;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

代码说明

上文示例只考虑了查询情况下,其他情况请自行补充。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis是一个开源的持久层框架,它提供了一种将SQL语句与Java代码解耦的方式。尽管MyBatis是一个相对安全的框架,但在使用过程中,仍存在可能发生SQL注入攻击的风险。 要防止MyBatis SQL注入攻击,可以采取以下几种方法: 1. 使用参数化查询(Prepared Statement):确保所有的用户输入参数都通过参数占位符的方式传递到SQL语句中,而不是直接拼接到SQL语句中。这样可以避免恶意用户输入特殊字符来破坏SQL语句结构。 2. 输入验证和过滤:在接收用户输入之前,对输入进行合法性验证和过滤。可以使用正则表达式、白名单或黑名单等方式来限制输入内容的合法范围,并过滤掉可能造成SQL注入的特殊字符。 3. 使用MyBatis动态SQL功能:MyBatis提供了动态SQL功能,可以根据不同的条件动态拼接SQL语句。在使用动态SQL时,应该使用MyBatis提供的安全方法来拼接字符串,而不是直接拼接用户输入。 4. 限制数据库权限:为数据库用户设置合适的权限,最小化其对数据库的操作权限。这样即使发生注入攻击,攻击者也只能对具有限制权限的数据进行操作。 5. 定期更MyBatis版本:及时关注MyBatis的更和安全公告,及时更到最版本,以获取最的安全修复和功能改进。 总之,为了防止MyBatis SQL注入攻击,需要使用参数化查询、输入验证和过滤、动态SQL、限制数据库权限以及定期更MyBatis版本等多种手段来综合保护系统安全。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值