基于Mybatis拦截器实现通用Mapper

基于Mybatis拦截器实现通用Mapper


在实际开发中,我们可能会有这样的需求,将业务SQL配置到数据库中,这样在生产环境下,如果我们业务SQL发生改变(查询字段不变),只需要将sql缓存刷新即可,无需修改mapper文件,重启服务器。

下面介绍我自己根据mybatis拦截器实现的方式:

定义通用mapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.oss.monitor.mapper.ExecuteMapper">
    <select id="executeSelectSql" parameterType="hashmap" resultType="com.oss.monitor.base.jdbcParam.ResultType">
        ${sql}
    </select>
</mapper>

注意:这里的resultType是一个自定义类型,方便我们后面拦截器拦截。

/**
 * @program: monitor
 * @description:只为了做通用返回类型标识作用
 * @author: fuqiang
 * @date: 2020-01-09 09:59
 **/
public class ResultType {
}
@Mapper
public interface ExecuteMapper {

    List<?> executeSelectSql(Map map);

}

定义拦截器

import com.oss.monitor.utils.CaseUtil;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.*;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * @program: hn_monitor
 * @description:
 * @author: fuqiang
 * @date: 2020-01-13 09:16
 **/
@Component
@Intercepts({ @Signature(type= Executor.class, method="query", args={MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
public class SetResultTypeInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();

        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];

        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        int resultMapCount = resultMaps.size();
        //获取当前resutType的类型
        Class<?> resultType = resultMaps.get(0).getType();

        ResultSet resultSet = null;
        PreparedStatement preparedStatement = null;
        Connection connection = null;

        try {
            if(resultMapCount>0 && ("com.oss.monitor.base.jdbcParam.ResultType").equalsIgnoreCase(resultType.getName())){
                //获取全局配置Configuration
                Configuration configuration = mappedStatement.getConfiguration();
                //获取BoundSQl
                Object object =  invocation.getArgs()[1];
                BoundSql boundSql = mappedStatement.getBoundSql(object);
                //获取sql
                String sql = boundSql.getSql();
                //获取参数(取返回值类型)
                Map parameterMap = (Map)boundSql.getParameterObject();
                //如果参数中没有指定返回值类型,则默认返回Object
                Class<?> resultTypeClass = Object.class;
                if (parameterMap.containsKey("resultType")){
                    String resultTypeValue = (String)parameterMap.get("resultType");
                    resultTypeClass = Class.forName(resultTypeValue);
                }

                connection = configuration.getEnvironment().getDataSource().getConnection();
                preparedStatement = connection.prepareStatement(sql);

                ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, object, boundSql);
                parameterHandler.setParameters(preparedStatement);

                //执行获得结果集
                resultSet = preparedStatement.executeQuery();
                List<Object> resList = new ArrayList<>();

                if (resultSet != null) {

                    while (resultSet.next()) {
                        // 遍历一次是一行,也对应一个对象,利用反射new一个对象
                        Object result = resultTypeClass.newInstance();

                        // 要获取每一列的值,然后封装到结果对象中对应的属性名称上
                        ResultSetMetaData metaData = resultSet.getMetaData();
                        int columnCount = metaData.getColumnCount();
                        for (int i = 0; i < columnCount; i++) {
                            // 获取每一列的值
                            Object value = resultSet.getObject(i + 1);

                            if (value instanceof BigDecimal){
                                value = Long.parseLong(value.toString());
                            }

                            // 列的名称
                            String columnName = metaData.getColumnName(i + 1);

                            String newColumnName = CaseUtil.underlineToCamel(columnName,'_');

                            // 列名和属性名称要严格一致
                            Field field = resultTypeClass.getDeclaredField(newColumnName);

                            field.setAccessible(true);

                            // 给映射的对象赋值
                            field.set(result, value);


                        }
                        resList.add(result);
                    }
                    return resList;
                }

            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

        return invocation.proceed();
    }

    public Object plugin(Object target) {
        // 读取@Signature中的配置,判断是否需要生成代理类
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

说明:mybatis拦截器只能去拦截四大组件,这边拦截的是Executor。执行到这一步,sql解析的工作已经完成,所以我们可以取到相应解析后的值(如返回值类型,sql,入参等信息都是解析好封装在相应的类中)。如果是我们指定的返回类型,也就是意味着这条sql,是用自定义的通用mapper去执行查询的,所以我们拦截下来,自己查询,并将查询的结果,映射到入参中传来的真正返回值类型中去。如果不是自定义的返回类型,则放行,继续走mybatis原来的执行流程。(所以不会影响到mybatis本来的功能。)

这里处理返回值的时候,是通过反射,赋值到真正返回值类型中的,我采用的是驼峰式命名规则,所以我们定义POJO的是,字段与数据库对应的字段必须遵循驼峰式命名规则,否则这里可能反射去POJO中就找不到该字段了。


/**
 * @program: monitor
 * @description:字符串驼峰式互转
 * @author: fuqiang
 * @date: 2020-01-09 10:11
 **/
public class CaseUtil {

    /**
     * 驼峰格式字符串转换为下划线格式字符串
     *
     * @str str
     * @return
     */
    public static String camelToUnderline(String str,char delimiters) {
        if (str == null || "".equals(str.trim())) {
            return "";
        }
        int len = str.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char c = str.charAt(i);
            if (Character.isUpperCase(c)) {
                sb.append(delimiters);
                sb.append(Character.toLowerCase(c));
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 下划线格式字符串转换为驼峰格式字符串
     *
     * @str str
     * @return
     */
    public static String underlineToCamel(String str,char delimiters) {
        if (str == null || "".equals(str.trim())) {
            return "";
        }
        int len = str.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char c = str.charAt(i);
            c = Character.toLowerCase(c);
            if (c == delimiters) {
                if (++i < len) {
                    sb.append(Character.toUpperCase(str.charAt(i)));
                }
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }
}

调用

/**
 * @program: monitor
 * @description:
 * @author: fuqiang
 * @date: 2019-12-26 17:34
 **/
@Service
@DS("sf_base")
public class SFOrderServiceImpl implements ISFOrderService {
    @Autowired
    private ExecuteMapper executeMapper;

    public List<Object> getOrderInfo1() throws Exception {
//从缓存中取SQL
        String sql = CacheUtils.SqlDataUtil.getSqlByTableNameAndSqlTag("SF_ORDER","SF_ORDER");

        Map map = new HashMap();
        //这里的 sql 对应 XML 中的 ${sql}
        map.put("sql", sql);
        map.put("参数1","参数1");
		map.put("参数2","参数2");
        //接口方式调用
        List<Object> list = (List<Object>) executeMapper.executeSelectSql(map);
        /*SqlSession sqlSession = sqlSessionFactory.openSession();
        List<Map> list = sqlSession.selectList("executeSelectSql", map);*/
        return list;

    }

}

这里的入参是HashMap类型(通用mapper中定义的),可以将参数信息全都放入map中,需要保证map的key与sql中#{}中的字段对应相同即可。如果不指定resultType,这里默认返回的就是Object,所以我们一般是需要在map参数中设置resultType的值。

		Map map = new HashMap();
        //这里的 sql 对应 XML 中的 ${sql}
        map.put("sql", sql);
        //这边是需要完整的resultType类路径的,因为在拦截器中是根据类路径加载这个实体类的
        map.put("resultType", "POJO路径");
        map.put("参数1","参数1");
		map.put("参数2","参数2");

为了入参默认传入sql和resultType,可以用下面的类型替换入参Map

/**
 * @program: monitor
 * @description: 使用通用sql查询时传入参数类型
 * @author: fuqiang
 * @date: 2020-01-09 09:44
 **/
public class ParamMap extends HashMap {
    private Map paramMap;

    public ParamMap(String sql,String resultType) {
        this.paramMap = new HashMap();
        this.paramMap.put("sql",sql);
        this.paramMap.put("resultType",resultType);
    }

    public Map getParamMap() {
        return paramMap;
    }

    public void setParamMap(String key,Object value) {
        this.paramMap.put(key,value);
    }
}

注意:这里的sql只能最多包含#{}的sql,如果想要 或 者 动 态 标 签 的 s q l , 可 以 采 用 F r e e M a r k e r 模 板 先 对 s q l 进 行 一 次 解 析 , 将 动 态 标 签 和 {}或者动态标签的sql,可以采用FreeMarker模板先对sql进行一次解析,将动态标签和 sqlFreeMarkersql{}解析掉,然后来这里执行sql。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值