下划线键值转小写驼峰形式插件

有些人在使用 MyBatis 时,为了方便扩展而使用 Map 类型的返回值。使用 Map 作为返回值时,Map 中的键值就是查询结果中的列名,而列名一般都是大小写字母或者下画线形式,和 Java 中使用的驼峰形式不一致。而且由于不同数据库查询结果列的大小写也并不一致,因此为了保证在使用 Map 时的属性一致,可以对 Map 类型的结果进行特殊处理,即将不同格式的列名转换为 Java 中的驼峰形式。这种情况下,我们就可以使用拦截器,通过拦截 ResultSetHandler 接口中的 handleResultSets 方法去处理 Map 类型的结果。拦截器实现代码如下。

package tk.mybatis.simple.plugin;

import java.sql.Statement;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;

/**
 * MyBatis Map 类型下画线Key 转小写驼峰形式
 *
 * @author liuzenghui
 */
@Intercepts(
    @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
)
@SuppressWarnings({ "unchecked", "rawtypes" })
public class CameHumpInterceptor implements Interceptor {

	@Override
    public Object intercept(Invocation invocation) throws Throwable {
        //先执行得到结果,再对结果进行处理
        List<Object> list = (List<Object>) invocation.proceed();
        for(Object object : list){
        	//如果结果是 Map 类型,就对 Map 的 Key 进行转换
            if(object instanceof Map){
                processMap((Map)object);
            } else {
                break;
            }
        }
        return list;
    }

    /**
     * 处理 Map 类型
     *
     * @param map
     */
    private void processMap(Map<String, Object> map) {
        Set<String> keySet = new HashSet<String>(map.keySet());
        for(String key : keySet){
        	//大写开头的会将整个字符串转换为小写,如果包含下划线也会处理为驼峰
        	if((key.charAt(0) >= 'A' && key.charAt(0) <= 'Z') || key.indexOf("_") >= 0){
        		Object value = map.get(key);
        		map.remove(key);
        		map.put(underlineToCamelhump(key), value);
        	}
        }
    }

    /**
     * 将下划线风格替换为驼峰风格
     *
     * @param inputString
     * @return
     */
    public static String underlineToCamelhump(String inputString) {
        StringBuilder sb = new StringBuilder();

        boolean nextUpperCase = false;
        for (int i = 0; i < inputString.length(); i++) {
            char c = inputString.charAt(i);
            if(c == '_'){
            	if (sb.length() > 0) {
                    nextUpperCase = true;
                }
            } else {
            	if (nextUpperCase) {
                    sb.append(Character.toUpperCase(c));
                    nextUpperCase = false;
                } else {
                    sb.append(Character.toLowerCase(c));
                }
            }
        }
        return sb.toString();
    }

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

    @Override
    public void setProperties(Properties properties) {
    }
}

这个插件的功能很简单,就是循环判断结果。如果是 Map 类型的结果,就对 Map 的 key 进行处理,处理时为了避免把已经是驼峰的值转换为纯小写,因此通过首字母是否为大写或是否包含下画线来判断(实际应用中要根据实际情况修改)。如果符合其中一个条件就转换为驼峰形式,删除对应的 key 值,使用新的 key 值来代替。当数据经过这个拦截器插件处理后,就可以保证在任何数据库中以 Map 作为结果值类型时,都有一致的 key 值,可以统一取值,尤其在 JSP 或者其他模板引擎中取值时,可以很方便地使用。想要使用该插件,需要在 mybatis-config.xml 中配置该插件。

<plugins>
    <plugin interceptor="tk.mybatis.simple.plugin.CameHumpInterceptor" />
</plugins>

虽然这只是一个简单的例子,但是却有一段看似简单实际并不简单的代码,在上面拦截器代码的第 31 行,invocation.proceed() 执行的结果被强制转换为了 List 类型。这是因为拦截器接口 ResultSetHandler 的 handleResultSets 方法的返回值为 List 类型,所以才能在这里直接强制转换。如果不知道这一点,就很难处理这个返回值。许多接口方法的返回值类型都是 List,但是还有很多其他的类型,所以在写拦截器时,要根据被拦截的方法来确定返回值的类型。