背景
最近优化项目功能时,其中有部分老代码在mapper中动态注入了整条sql,而且这些sql都是关联了不少表的,还使用了java.util.Map接收返回结果,刚开始时没有问题,但后面场景多了之后有些关联表的字段名称是一样的,导致了返回的结果不正确,mybatis中文官网也有相关介绍。这篇文章主要通过实例(已上传github和gitee)去介绍了如何解决这些问题,但方案是在老代码改不动,重构没成本之下的无奈之举,能解决当前问题,但不建议采用。如果高人有更好的解决方法,请拯救一下我们项目吧!
模拟场景
假设有如下3张表,special_type和normal_type表是有比较多的重复字段的:
CREATE TABLE `product_info` ( `product_id` int(11) NOT NULL AUTO_INCREMENT, `product_code` varchar(45) DEFAULT NULL, `product_name` varchar(45) DEFAULT NULL, `product_description` varchar(45) DEFAULT NULL, `cost` decimal(16,0) DEFAULT NULL, `price` decimal(16,0) DEFAULT NULL, `data_state` varchar(2) DEFAULT NULL, `created_by` varchar(45) DEFAULT NULL, `updated_by` varchar(45) DEFAULT NULL, `created_date` datetime DEFAULT NULL, `updated_date` datetime DEFAULT NULL, `product_type` varchar(3) DEFAULT NULL, PRIMARY KEY (`product_id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='产品信息' CREATE TABLE `special_type` ( `type_id` int(11) NOT NULL AUTO_INCREMENT, `type` varchar(3) DEFAULT NULL, `description` varchar(45) DEFAULT NULL, `limit` varchar(45) DEFAULT NULL COMMENT '限制', PRIMARY KEY (`type_id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='特殊产品类型表' CREATE TABLE `normal_type` ( `type_id` int(11) NOT NULL AUTO_INCREMENT, `type` varchar(3) DEFAULT NULL, `description` varchar(45) DEFAULT NULL, `level` int(11) DEFAULT NULL COMMENT '层级', PRIMARY KEY (`type_id`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='普通产品类型表'
而mybatis中的mapper查询语句如下,如果不做任何处理,返回的map中的type和description字段很可能不是我们想要的。
解决办法
在sql语句中添加标志性的描述,如9=#{mapFlag, jdbcType=DECIMAL},新增mybatis拦截器,单独特殊处理该条sql的返回结果。我们需要对ResultSetHandler进行拦截,拦截器做不到精准拦截某条statement,所以只能添加特殊描述字符去识别需要处理的结果集。拦截器代码如下:
package com.sherwin.druid.interceptor;
import com.alibaba.druid.pool.DruidPooledStatement;
import com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.*;
import java.util.*;
@Intercepts({@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = {Statement.class})})
public class MybatisInterceptor implements Interceptor {
private static final Logger logger= LoggerFactory.getLogger(MybatisInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object arg0 = invocation.getArgs()[0];
PreparedStatementProxyImpl preparedStatement = null;
//因为使用了Druid,需要从DruidPooledStatement中获取到jdbc的preparedStatement
if(arg0 instanceof DruidPooledStatement){
DruidPooledStatement druidPooledStatement = (DruidPooledStatement) arg0;
if(druidPooledStatement.getStatement() instanceof PreparedStatement)
preparedStatement = (PreparedStatementProxyImpl) druidPooledStatement.getStatement();
}
if(preparedStatement == null)
return invocation.proceed();
String sql = preparedStatement.getSql();
//获取参数,符合标志描述就处理map返回类型
if(!sql.endsWith("9=?")){
return invocation.proceed();
}
List
加上拦截器后可以从结果中看出,类型和类型描述都成功获取到了展示到页面中。
需注意问题
- 有些mybatis版本是支持直接在sql中写“9=9”的,但新版本的会报如下异常,只能传参解决。项目使用了Druid管理链接池,如果使用了更高效的Hikari链接池时,需要修改拦截器的代码。如果使用了xml配置文件去配置mybatis,需要注释掉其他的mybatis的configuration,如下
#mybatis.configuration.map-underscore-to-camel-case=true mybatis.config-location=classpath:mybatis-config.xml



