MyBatis 解析 XML 配置文件

2024-04-13  本文已影响0人  Tinyspot

1. 解析XML配置

@Slf4j
@RestController
@RequestMapping("/order")
public class OrderMapperController {

    @Resource
    private OrderMapper orderMapper;

    private static final Configuration configuration = new Configuration();
    private static String startXml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<select>\n ";
    private static String endXml = "</select>";

    @GetMapping("/queryBySql")
    public String queryBySql() {
        String fileName = "third/order-template.txt";
        URL url = Thread.currentThread().getContextClassLoader().getResource(fileName);
        String xmlSQL = FileUtil.readUtf8String(url.getPath());

        Map<String, Object> params = new HashMap<>();
        params.put("dynamicColumns", "tradeId, tpCode");
        params.put("groupBy", " tradeId, tpCode");
        params.put("tpCode", "SF");
        String template = parseSql(xmlSQL, params);

        // 模板解析
        OrderQuery orderQuery = new OrderQuery();
        orderQuery.setSql(template);
        return JSON.toJSONString(orderMapper.queryBySqlSegment(orderQuery));
    }

    /**
     * @param xmlSql 模板
     * @param params 参数
     * @return
     */
    public static String parseSql(String xmlSql, Map<String, Object> params) {
        try {
            // 拼接xml
            xmlSql = startXml.concat(xmlSql).concat(endXml);

            // 基于 MyBatis 配置文件构建 XPathParser 实例
            XPathParser xPathParser = new XPathParser(xmlSql, false);
            XNode xNode = xPathParser.evalNode("/select");

            // BoundSql-预编译SQL,解析引用变量 比如 #{tpCode}
            XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(configuration, xNode);
            SqlSource sqlSource = xmlScriptBuilder.parseScriptNode();
            MappedStatement statement =  new MappedStatement.Builder(configuration, UUID.randomUUID().toString(), sqlSource, null).build();
            BoundSql boundSql = statement.getBoundSql(params);
            String resultSql = boundSql.getSql() + " ";
            /*
             * select ? from boot_order
             * where 1 = 1
             * AND tpCode = '?'
             * GROUP BY ? ORDER BY count(1) DESC
             */

            // 参数绑定
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            resultSql = resultSql.replaceAll("[\\t\\n]", " ")
                    .replaceAll(" +", " ");

            if (parameterMappings == null) {
                return resultSql;
            }

            MetaObject metaObject = null;
            for (ParameterMapping parameterMapping : parameterMappings) {
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value = null;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (params != null) {
                        if (metaObject == null) {
                            metaObject = configuration.newMetaObject(params);
                        }
                        value = metaObject.getValue(propertyName);
                    }
                    
                    // 替换占位符
                    int index = resultSql.indexOf("?");
                    resultSql = resultSql.substring(0, index).concat(value.toString()).concat(resultSql.substring(index + 1));
                }
            }
            return resultSql;
        } catch (Exception e) {
            log.error(e.toString());
        }
        return "";
    }
}

1.1 核心配置 Configuration

public class Configuration {
  public MetaObject newMetaObject(Object object) {
    return MetaObject.forObject(object, objectFactory, objectWrapperFactory, reflectorFactory);
  }
}

Configuration.newMetaObject() 方法的主要作用是创建与给定 Java 对象关联的 MetaObject 实例,从而提供便捷、强大的反射功能,便于在 MyBatis 的各种场景中(如动态 SQL、插件、拦截器等)进行对象属性的访问和操作

1.2 解析器 XPathParser

public XNode evalNode(String expression) {
    return evalNode(document, expression);
}

/select 是一个 XPath 表达式
/ 表示从文档根节点开始查找
select 表示选择名为 select 的元素节点
综上:evalNode("/select") 表示返回 <select> 标签及其所有子节点和属性

1.3 ParameterMode

public enum ParameterMode {
  IN, OUT, INOUT
}

输入参数(IN parameters)
输出参数(OUT parameters)
既能输入又能输出的参数(INOUT parameters)

2. 接口

2.1 配置文件

在 resources 目录新建 third/order-template.txt

select
    <if test="dynamicColumns != null">
        #{dynamicColumns}
    </if>
from
    boot_order
where
    1 = 1
    <if test="tpCode != null">
        AND tpCode = '#{tpCode}'
    </if>
    <if test="condition != null">
        AND #{condition}
    </if>
    <if test="groupBy != null">
        GROUP BY #{groupBy} ORDER BY count(1) DESC
    </if>

2.2 接口

public interface OrderMapper {
    List<Order> queryBySqlSegment(OrderQuery orderQuery);
}

2.3 Mapper 文件

<?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.example.concrete.dao.mapper.OrderMapper">

    <select id="queryBySqlSegment" resultType="Order" parameterType="OrderQuery">
        ${sql}
        LIMIT #{pageStart}, #{pageSize}
    </select>
</mapper>

2.4 参数模型 OrderQuery

@Data
public class OrderQuery implements Serializable {
    private static final long serialVersionUID = 3930335153402169459L;

    /**
     * SQL语句
     */
    private String sql;

    private Integer pageStart = 0;
    private Integer pageSize = 100;
}
上一篇下一篇

猜你喜欢

热点阅读