背景
- 项目使用Mybatis-Plus,开启了全局结果集字段转驼峰
map-underscore-to-camel-case: true
。开启之后如果需要返回下划线需要自定义resultMap
- 现需要返回List<Map>,且返回的字段是动态变化,需要返回原Sql中的AS后的字段
版本
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>2.5.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
解决
- 由于开启了
map-underscore-to-camel-case: true
全局驼峰,在org.apache.ibatis.executor.resultset.DefaultResultSetHandler#createAutomaticMappings方法中使用,且在com.baomidou.mybatisplus.extension.handlers.MybatisMapWrapper#findProperty方法中进行了转换。暂时没有找到其他的扩展点,故只能通过拦截器在结果集中处理
- 通过在拦截器中识别方法的注解,对结果进行拦截
@CancelCamelCase
import java.lang.annotation.*;
/**
* @author wf
* @date 2022年08月10日 11:14
* @description SQL获取
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CancelCamelCase {
}
CancelCamelCaseInterceptor
- 执行SQL之前先保存本身的下划线字段到一个map,然后等执行完SQL之后再还原回来
import com.alibaba.druid.pool.DruidPooledPreparedStatement;
import com.google.common.collect.Maps;
import com.wf.dev.common.annotaions.CancelCamelCase;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.resultset.DefaultResultSetHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.assertj.core.util.Lists;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.List;
import java.util.Map;
import java.util.Properties;
/**
* @author wf
* @date 2023年01月10日 10:40
* @description 对mybatis查询单独取消map-underscore-to-camel-case为true
*/
@Component
@Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}))
public class CancelCamelCaseInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
DefaultResultSetHandler resultSetHandler = (DefaultResultSetHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(resultSetHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("mappedStatement");
String namespace = mappedStatement.getId();
String className = StringUtils.substringBeforeLast(namespace, ".");
Class<?> clazz = Class.forName(className);
if (!clazz.isAnnotationPresent(CancelCamelCase.class)) {
return invocation.proceed();
}
String methodName = StringUtils.substringAfterLast(namespace, ".");
Method[] methods = clazz.getMethods();
Method target = null;
for (Method method : methods) {
if (!method.getName().equals(methodName)) {
continue;
}
target = method;
break;
}
if (target == null || !target.isAnnotationPresent(CancelCamelCase.class)) {
return invocation.proceed();
}
DruidPooledPreparedStatement ms = (DruidPooledPreparedStatement) invocation.getArgs()[0];
ResultSet resultSet = ms.getResultSet();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
Map<String, String> propertyColumnNameMap = Maps.newHashMap();
for (int i = 1; i <= columnCount; i++) {
// 这里必须要用getColumnLabel,不能用getColumnName
String columnName = metaData.getColumnLabel(i);
propertyColumnNameMap.put(com.baomidou.mybatisplus.core.toolkit.StringUtils.underlineToCamel(columnName), columnName);
}
Object proceed = invocation.proceed();
if (!(proceed instanceof List)) {
return proceed;
}
List list = (List) proceed;
List<Map<String, Object>> result = Lists.newArrayList();
for (Object row : list) {
MetaObject metaObjectRow = MetaObject.forObject(row, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
Map<String, Object> newRow = Maps.newHashMap();
for (String property : propertyColumnNameMap.keySet()) {
newRow.put(propertyColumnNameMap.get(property), metaObjectRow.getValue(property));
}
result.add(newRow);
}
return result;
}
@Override
public Object plugin(Object target) {
if (target instanceof ResultSetHandler) {
return Plugin.wrap(target, this);
}
return target;
}
@Override
public void setProperties(Properties properties) {
}
}
Mapper
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* @author wf
* @date 2023年01月10日 15:07
* @description
*/
@CancelCamelCase
public interface Mapper {
/**
* 从sql查询
*
* @param param
* @return
*/
@CancelCamelCase
List<Map<String, Object>> sqlQuery(QueryParam param);
}
小结
- 此方法还是比较死板,增加了for循环,等于效率降低了。需要寻求更多的扩展
参考