mybatis不使用conf.xml配置文件,纯Java解析mapper.xml文件

987 阅读2分钟

使用场景: 让mybatis帮忙管理mapper.xml文件,使用时感知不到conf.xml的存在; 灵活获取mapper文件中指定动态sql。

使用示例:

@Test
public void test() {
    //只用指定mapper文件的路径即可
    MappersParser parser = new MappersParser("classpath:mappers/*.xml");
    //解析文件
    parser.parse();

	/**
     * class Param{
     *   private Integer id;
     *   
     *     public Integer getId(){
     *         return id;
     *     }
     *     public Integer setId(Integer id){
     *         this.id = id;
     *     }
     * }
     */
    //设置参数
    Param param = new Param();
    param.setId(12);
    //根据mapper文件中的id获取指定的sql, 如:<select id="queryOne" resultType="java.util.Map">
    MappersParser.SqlModel sql = parser.getAndFlatParam("queryOne", param);

    System.out.println("sql="+sql.getSql());
    System.out.println("params="+sql.getParams());
}

目录结构:

|--resources
      |----mappers
             |----demo.xml

demo.xml 文件实例

<?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.test.demo.abc.hello">

    <!--suppress MybatisMapperXmlInspection -->
    <select id="queryOne" resultType="java.util.Map">
    	select * from demo where id=#{id}
    </select>

</mapper>

MapperParser.java 文件内容:

import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.Assert;

import java.io.StringReader;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author mrli
 * @date 2020/8/3
 */

public class MappersParser {

    private static final Logger log = LogManager.getLogger(MappersParser.class);

    private String mappersPath;

    private Configuration configuration;

    public MappersParser(String mappersPath){
        this.mappersPath = mappersPath;
    }

    public MappersParser(Configuration configuration){
        this.configuration = configuration;
    }

    public void parse(){
        try {
            this.configuration = innerParse();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    /**
     * 构造配置文件
     */
    private Configuration innerParse() throws Exception{
        if(StringUtils.isBlank(mappersPath)){
            throw new RuntimeException("mapperLocations is null");
        }

        StringBuilder sbd = new StringBuilder();
        sbd.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>");
        sbd.append("<!DOCTYPE configuration  PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-config.dtd\">");
        sbd.append("<configuration>");

        //下划线转驼峰
        sbd.append("<settings>");
        sbd.append("<setting name=\"mapUnderscoreToCamelCase\" value=\"true\"/>");
        sbd.append("</settings>");

        sbd.append(" <mappers>");

        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        Resource[] res = resourceResolver.getResources(mappersPath);
        for (Resource resource : res) {
            String fileClasspath = pickClasspath(resource.getURI());
            sbd.append("<mapper resource=\""+fileClasspath+"\"/>");
        }
        sbd.append(" </mappers>");

        sbd.append("</configuration>");
        XMLConfigBuilder builder = new XMLConfigBuilder(new StringReader(sbd.toString()));
        return  builder.parse();
    }

    private static String pickClasspath(URI fileUri){
        String uri = fileUri.toString();
        uri = StringUtils.substringAfterLast(uri, "/classes");
        return StringUtils.substringAfter(uri, "/");
    }

    /**
     * 将对象解析为,sql语句中对应的变量
     * @param id
     * @param param
     * @return
     */
    public SqlModel getAndFlatParam(String id, Object param){
        log.debug("[DB] id="+id+", databseId="+configuration.getDatabaseId());
        MappedStatement ms = configuration.getMappedStatement(id);
        Assert.notNull(ms, "未找到id为"+id+"的sql定义");
        BoundSql boundSql = ms.getBoundSql(param);
        Assert.notNull(boundSql, "未找到id为"+id+",且参数类型为"+param.getClass()+"的sql定义");

        String sql = boundSql.getSql();
        Class<?> clazz = param.getClass();

        List<Object> paramList = new ArrayList<>();
        if(Map.class.isAssignableFrom(clazz)){
            Map mapTmp = (Map)param;
            for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                String property = parameterMapping.getProperty();
                paramList.add(mapTmp.get(property));
            }
        }else{
            for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                String property = parameterMapping.getProperty();
                try{
                    Field field = getDeclaredFieldInFamily(clazz, property);
                    if(field==null){
                        throw new RuntimeException("can't found property  "+property+" from Class "+clazz);
                    }
                    field.setAccessible(true);
                    paramList.add(field.get(param));
                }catch (Exception e){
                    throw new RuntimeException(e);
                }
            }
        }
        Object[] params = paramList.toArray(new Object[0]);
        return new SqlModel(sql, params);
    }
    
    //反射获取属性
    public static final Field getDeclaredFieldInFamily(Class target, String fieldName) throws Exception{
        Field field = getDeclaredField(target, fieldName);
        if(field!=null){
            return field;
        }
        Class clazz = target.getSuperclass();
        while (clazz!=Object.class){
            field = getDeclaredField(clazz, fieldName);
            if(field!=null){
                return field;
            }
            clazz = clazz.getSuperclass();
        }
        return null;
    }

    public static final Field getDeclaredField(Class target, String fieldName){
        Field[] fields = target.getDeclaredFields();
        for (Field field : fields) {
            if(field.getName().equals(fieldName)){
                return field;
            }
        }
        return null;
    }

    public static class SqlModel{
        private String sql;
        private Object[] params;

        public SqlModel(String sql, Object[] params) {
            this.sql = sql;
            this.params = params;
        }

        public String getSql() {
            return this.sql;
        }

        public Object[] getParams() {
            return this.params;
        }

        public void setSql(String sql) {
            this.sql = sql;
        }

        public void setParams(Object[] params) {
            this.params = params;
        }

        public String toString() {
            return "MappersParser.SqlModel(sql=" + this.getSql() +
                    ", params=" + java.util.Arrays.deepToString(this.getParams()) + ")";
        }
    }
   
}