MyBatis源码4_扩展运用_借助MyBatis解析动态SQL和参数设置

185 阅读3分钟

在Java代码中借助MyBatis解析在我们常常在Mapper.xml里面写的Sql,包括支持<if,where,foreach...>语法。

ps:做这个的主要目的是我们有个纯后端BI小工具,这个工具存储了从数据源查询的Sql,然后为了提高这个sql的灵活性和复用程度就想到用MyBatis的动态Sql语法直接实现

主要借助MyBatis的两个类实现:XMLScriptBuilderDefaultParameterHandler来实现,前者负责解析Mapper的内容Sql语句,后者负责将解析的结果和传递的参数进行设置。

XMLScriptBuilder:

主要负责根据参数解析sql的动态标签,得出具体的sql语句和解析出来的参数映射信息BoundSql对象。

这个类实际就是MyBatis解析Mapper的sql的实现类,是通过XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)创建的。具体流程可以查看MyBatis系列2_启动分析MyBatis启动

调用MyBatis的XMLScriptBuilder解析sql标签使用范例:

    public static void main(String[] args) {

        final String sql = "select * from sys_menu " +
                "<where>" +
                "<if test="menuName != null and menuName != ''"> " +
                "  AND menu_name like concat('%', #{menuName}, '%') " +
                "</if> " +
                "  <if test="visible != null and visible != ''"> " +
                "  AND visible = #{visible} " +
                " </if>" +
                "</where>";

        final String mapperXmlHeadler = "<?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文件内容
        String mapperContentText = mapperXmlHeadler +
                // 自定义mapper标签和名称空间
                "<mapper namespace="xx.bb">" +
                "<sql id='xxx'> " +
                // 拼接解析sql
                sql +
                "   </sql>" +
                "   </mapper>";

        // 创建Mybatis的默认配置,手动设置配置信息,默认的配置里面带了有各种基础类型处理的Handler一般来说够用了
        Configuration configuration = new Configuration();
        // 创建一个解析器
        XPathParser xPathParser = new XPathParser(mapperContentText, true, configuration.getVariables(), new XMLMapperEntityResolver());
        // 执行解析模拟节点
        XNode xNode = xPathParser.evalNode("/mapper/sql");
        // 创建Builder实例,执行解析
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, xNode);
        SqlSource sqlSource = builder.parseScriptNode();

        // 这是sql执行参数
        Map<String, Object> params = new HashMap<>();
        params.put("menuName", "系统管理");
        params.put("visible", true);

        // 根据参数获取绑定的sql
        BoundSql boundSql = sqlSource.getBoundSql(params);

        // 结果:select * from sys_menu  WHERE  menu_name like concat('%', ?, '%')       AND visible = ?
        System.out.println(boundSql.getSql());
    }

DefaultParameterHandler

这个类是MyBatis中起到给sql语句设置参数的作用,我们知道在MyBatis的实际执行Sql是由Executor实现类执行的,而在Executor执行中都是调用这个类来进行参数设置的。

2024-11-25-21-13-51-image.png

MyBatis中设置动态参数示例:简单来说就是手动创建一个模拟的MappedStatement来执行

        // 获取数据库连接
        Connection connection = DBAccess.getConnection();
        // 创建预编译声明
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
        // 构造一个默认的MappedStatement
        MappedStatement mappedStatement = new MappedStatement.Builder(configuration,"yy",sqlSource, SqlCommandType.SELECT).build();
        // 创建参数Handler
        DefaultParameterHandler defaultParameterHandler = new DefaultParameterHandler(mappedStatement, params, boundSql);
        defaultParameterHandler.setParameters(preparedStatement);// 设置参数

        // 执行
        ResultSet rs = preparedStatement.executeQuery();
        while(rs.next()){
            Object val = rs.getObject(1);
            System.out.println(val);
        }

这个DefaultParameterHandler也是具体调用TypeHandler的入口

2024-11-25-21-22-20-image.png

MyBatisUtils(总结)

public class MyBatisUtils {

    // 创建Mybatis的默认配置,手动设置配置信息,默认的配置里面带了有各种基础类型处理的Handler一般来说够用了
    private static Configuration configuration = new Configuration();

    /**
     * 解析Mapper的Sql语句
     *
     * @param sql
     * @return
     */
    public static SqlSource parseMapperSql(String sql) {
        final String mapperXmlHeadler = "<?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文件内容
        String mapperContentText = mapperXmlHeadler +
                // 自定义mapper标签和名称空间
                "<mapper namespace="xx.bb">" +
                "<sql id='xxx'> " +
                // 拼接解析sql
                sql +
                "   </sql>" +
                "   </mapper>";
        // 创建一个解析器
        XPathParser xPathParser = new XPathParser(mapperContentText, true, configuration.getVariables(), new XMLMapperEntityResolver());
        // 执行解析模拟节点
        XNode xNode = xPathParser.evalNode("/mapper/sql");
        // 创建Builder实例,执行解析
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, xNode);
        SqlSource sqlSource = builder.parseScriptNode();
        return sqlSource;
    }

    /**
     * 设置参数
     * @param preparedStatement
     * @param sqlSource
     * @param params
     */
    public static void setParameters(PreparedStatement preparedStatement, SqlSource sqlSource, Object params) {
        // 构造一个默认的MappedStatement
        MappedStatement mappedStatement = new MappedStatement.Builder(configuration, "yy", sqlSource, SqlCommandType.SELECT).build();
        // 创建参数Handler
        DefaultParameterHandler defaultParameterHandler = new DefaultParameterHandler(mappedStatement, params, sqlSource.getBoundSql(params));
        defaultParameterHandler.setParameters(preparedStatement);// 设置参数
    }
}

调用:

        final String sql = "select * from sys_menu " +
                "<where>" +
                "<if test="menuName != null and menuName != ''"> " +
                "  AND menu_name like concat('%', #{menuName}, '%') " +
                "</if> " +
                "  <if test="visible != null and visible != ''"> " +
                "  AND visible = #{visible} " +
                " </if>" +
                "</where>";
        // 这是sql执行参数
        Map<String, Object> params = new HashMap<>();
        params.put("menuName", "系统管理");

        SqlSource sqlSource = MyBatisUtils.parseMapperSql(sql);
        Connection connection = DBAccess.getConnection();
        PreparedStatement preparedStatement = connection.prepareStatement(sqlSource.getBoundSql(params).getSql());
        MyBatisUtils.setParameters(preparedStatement, sqlSource, params);
        ResultSet resultSet = preparedStatement.executeQuery();
        while (resultSet.next()) {
            String text = resultSet.getString(1);
            System.out.println(text);
        }

以上做了个简单的示例,实际中可以进一步封装和扩展自己的方法。

在结果处理这里,也可以复用MyBatis的DefaultResultSetHandler来进行结果映射,但是由于我们不需要映射成对象就没有对再进行处理。

上面的处理均只依赖MyBatis的核心包,不依赖其它而且基本上所有版本都支持,因为这些都是MyBatis的核心代码,所以可移植性也不错,可做简要参考。