Mybatis拦截器,解决达梦数据库sql返回类型Map key大写问题
拦截器功能点代码介绍
在代码中有一句invocation.proceed();方法,如果执行了这个方法,就是mybatis自己处理查询结果返给我们用。如果不执行该方法,则我们自己可以拿到结果自己映射到返回类型中去
拦截器入口
import org.apache.ibatis.executor.resultset.DefaultResultSetHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* 拦截特定接口的返回值,转成Map
*/
@Component
//拦截Executor类的query方法
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})
public class MybatisResultIntercept implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
DefaultResultSetHandler defaultResultSetHandler = (DefaultResultSetHandler) invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(defaultResultSetHandler);
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("mappedStatement");
//获取节点属性的集合
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
//上面一堆代码主要是为了获取sql的映射类型
Class<?> resultType = resultMaps.get(0).getType();
//InterceptorFlagInterface这个是我自己定义的一个标记接口,这句代码意思是,如果sql的结果映射类型为map或者实体实现了InterceptorFlagInterface接口的都会走这个if语句
if (Map.class.isAssignableFrom(resultType) || InterceptorFlagInterface.class.isAssignableFrom(resultType)) {
//处理sql返回值是Map类型的
return MapIntercept.interceptMap(invocation);
} else {
return EntityIntercept.intercept(invocation);
}
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties arg0) {
}
}
接收Mybatis处理的结果
这种是接收框架拼接的结果,处理达梦数据库Map key大写问题
这个类是拦截器类,里面有2中业务处理方法,是我自己的业务
import com.alibaba.druid.pool.DruidPooledResultSet;
import com.alibaba.druid.pool.DruidPooledStatement;
import com.alibaba.druid.proxy.jdbc.NClobProxyImpl;
import com.alibaba.druid.proxy.jdbc.PreparedStatementProxyImpl;
import com.alibaba.fastjson.JSONObject;
import dm.jdbc.desc.Column;
import dm.jdbc.driver.DmdbNClob;
import dm.jdbc.driver.DmdbPreparedStatement;
import org.apache.ibatis.plugin.Invocation;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 拦截特定接口的返回值,转成Map
*/
public class MapIntercept {
/**
* 处理拦截map
*
* @param invocation
* @return
* @throws Throwable
*/
public static Object interceptMap(Invocation invocation) throws Throwable {
//执行请求方法,框架所得结果保存到result中
Object result = invocation.proceed();
if (result != null && result instanceof ArrayList) {
ArrayList resultList = (ArrayList) result;
//当前返回值的实体类是实现了InterceptorFlagInterface的才会做转换
if (resultList.size() != 0 && resultList.get(0) instanceof InterceptorFlagInterface) {
//这个方法不用看,是处理Map key大写的备用方案
List<Map> res = new ArrayList<>();
for (int i = 0; i < resultList.size(); i++) {
res.add(JSONObject.parseObject(JSONObject.toJSONString(resultList.get(i)), Map.class));
}
return res;
} else if (resultList.size() != 0 && resultList.get(0) instanceof Map) {
//将key大写的map转为小写
return handelResultMap(resultList, getColumns(invocation));
}
}
return result;
}
private static Object handelResultMap(ArrayList resultList, Column[] columns) {
Map<String, String> columnsToMap = columnsToMap(columns);
List<Map> res = new ArrayList<>();
for (int i = 0; i < resultList.size(); i++) {
Map<String, Object> oldMap = (Map<String, Object>) resultList.get(i);
Map<String, Object> newMap = new HashMap<>(oldMap.size());
for (Map.Entry<String, Object> entry : oldMap.entrySet()) {
Object value = entry.getValue();
//如果数据库类型为clob或blob,sql映射类型为map,需要单独处理
if (value instanceof NClobProxyImpl) {
NClobProxyImpl nClobProxy = (NClobProxyImpl) value;
DmdbNClob rawClob = (DmdbNClob) nClobProxy.getRawClob();
value = rawClob.data;
}/*else if (value instanceof DmdbBlob) {
//处理blob,当前代码实验未成功
DmdbBlob dmdbBlob = (DmdbBlob)value;
try {
byte[] data = dmdbBlob.data;
byte[] bytes = "blob".getBytes();
value = new String(dmdbBlob.data, "UTF-8");
value = bytes2HexString(data);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}*/
if (columnsToMap.get(entry.getKey()) == null) {
newMap.put(entry.getKey(), value);
} else {
//将大写key变为我们正确的列名
newMap.put(columnsToMap.get(entry.getKey()), value);
}
}
res.add(newMap);
}
return res;
}
/**
* 当sql的返回值是Map类型时,需要将大写的key转成小写的key
*
* @param columns
* @return
*/
public static Map<String, String> columnsToMap(Column[] columns) {
if (columns == null || columns.length == 0) {
throw new RuntimeException("Mybatis拦截器未获取到原SQL中的列名");
}
//这个map:当sql的返回值是Map类型时,需要将大写的key转成小写的key
Map<String, String> columnsToMap1 = new HashMap<>(8);
for (Column column : columns) {
String name = column.name;
//LABEL 是个特殊字,会默认大写,就算在sql中用的是小写
if ("LABEL".equals(name)) {
columnsToMap1.put(name.toUpperCase(), "label");
} else {
columnsToMap1.put(name.toUpperCase(), column.name);
}
}
return columnsToMap1;
}
/**
* 获取sql中查询结果中的列名
*
* @param invocation
* @return
*/
public static Column[] getColumns(Invocation invocation) throws SQLException {
PreparedStatement statement = (PreparedStatement) invocation.getArgs()[0];
DruidPooledResultSet generatedKeys = (DruidPooledResultSet) statement.getGeneratedKeys();
DruidPooledStatement poolableStatement = generatedKeys.getPoolableStatement();
PreparedStatementProxyImpl statement1 = (PreparedStatementProxyImpl) poolableStatement.getStatement();
DmdbPreparedStatement rawObject = (DmdbPreparedStatement) statement1.getRawObject();
return rawObject.columns;
}
}
自己处理查询结果
这种是自己处理结果,不让框架去处理了
import com.alibaba.fastjson.JSON;
import org.apache.ibatis.executor.resultset.DefaultResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EntityIntercept {
public static Object intercept(Invocation invocation) throws Throwable {
List resList = new ArrayList();
DefaultResultSetHandler defaultResultSetHandler = (DefaultResultSetHandler) invocation.getTarget();
MetaObject metaStatementHandler = SystemMetaObject.forObject(defaultResultSetHandler);
MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("mappedStatement");
//获取节点属性的集合
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
Class<?> resultType = resultMaps.get(0).getType();
if (resultType == String.class || resultType == int.class) {
//如果走这里,就会执行框架的invocation.proceed();方法处理返回结果
} else {
//之前遇到Mybatis框架无法映射类型LocalDateTime,所以才有这个拦截器的存在,LocalDateTime具体解决方法看另外一篇
//判断当前实体是否需要做LocalDateTime的转换
if (!isLocalDate(resultMaps)) {
return invocation.proceed();
}
//处理返回的实体中有LocalDateTime的
Map<String, String> columnsAndFieldMap = columnsAndFieldMap(resultMaps);
//获取mybatis返回的实体类类型名
int resultMapCount = resultMaps.size();
if (resultMapCount > 0) {
Statement statement = (Statement) invocation.getArgs()[0];
ResultSet resultSet = statement.getResultSet();
if (resultSet != null) {
//获得对应列名
ResultSetMetaData rsmd = resultSet.getMetaData();
List<String> columnList = new ArrayList<>();
for (int i = 1; i <= rsmd.getColumnCount(); i++) {
//这里是拿到所有的列名,在达梦中应该都是大写的
//通过这些列名再去拿当前列的查询结果
columnList.add(rsmd.getColumnName(i));
}
while (resultSet.next()) {
Map resultMap = new HashMap<>();
for (String colName : columnList) {
//通过这些列名再去拿当前列的查询结果,resultSet.getString(colName)
resultMap.put(columnsAndFieldMap.get(colName), resultSet.getString(colName));
}
//最后将我们的结果转成一个实体类resultType
resList.add(JSON.parseObject(JSON.toJSONString(resultMap), resultType));
}
return resList;
}
}
}
return invocation.proceed();
}
/**
* 当sql返回值类型是实体类时,判断字段中是否有LocalDateTime
*
* @param resultMaps
* @return
*/
public static Boolean isLocalDate(List<ResultMap> resultMaps) {
//ResultMap中sql列和实体类字段的映射
Map<String, String> columnsToMap1 = new HashMap<>(8);
ResultMap resultMap1 = resultMaps.get(0);
List<ResultMapping> resultMappings = resultMap1.getResultMappings();
for (ResultMapping resultMapping : resultMappings) {
Class<?> javaType = resultMapping.getJavaType();
if (javaType == LocalDateTime.class || javaType == LocalDate.class) {
return true;
}
}
return false;
}
/**
* 当sql返回值类型是实体类时,获取到自定的ResultMap中sql列和实体类字段的映射
*
* @param resultMaps
* @return
*/
public static Map<String, String> columnsAndFieldMap(List<ResultMap> resultMaps) {
//ResultMap中sql列和实体类字段的映射
Map<String, String> columnsToMap1 = new HashMap<>(8);
ResultMap resultMap1 = resultMaps.get(0);
List<ResultMapping> resultMappings = resultMap1.getResultMappings();
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getJavaType() != List.class) {
columnsToMap1.put(resultMapping.getColumn().toUpperCase(), resultMapping.getProperty());
}
}
return columnsToMap1;
}
}