【Mybatis】单独使用mybatis的SQL模板解析

技术【Mybatis】单独使用mybatis的SQL模板解析 【Mybatis】单独使用mybatis的SQL模板解析前言
由于公司的项目历史设计问题坑多不见底,新项目没时间改,旧项目改不动。生产存在非

[Mybatis]仅使用Mybatis的SQL模板分析

前言

因为公司的项目历史设计问题层出不穷,没有时间去改变新项目,旧项目也无法改变。有很多非法操作需要生产,只能通过数据库脚本更改数据。

每次在开发过程中,都会抛出一个工作单,让生产每天更改数据和编写脚本。这怎么能容忍?

几个季度后,我受不了了。我一拍桌子,就决定要气.从头开始开发一个脚本执行工具来管理编写的脚本,这些脚本可以相互关联并跨数据库执行。解决临时脚本编写和脚本共享的问题。

在设计中,脚本执行工具的脚本一开始只是一个占位符,比如“{parameter name}”,然后就是replaceAll。想了想,为什么不能像mybatis一样解析脚本,或者直接使用mybatis的xml模板解析功能呢?

————————————————————————————————

正文

通过遍历代码,我们可以发现mybatis在XMLLanguageDriver类的createSqlSource中实现了SQL解析,并且支持使用脚本/脚本来执行字符串模板。

好吧,在这种情况下,试着按照这里的代码来。

如果直接修改XPathParser,太复杂了,还要管理内部逻辑。这里,就拿模拟法来说。

构建配置Configuration

首先,您需要mybatis的xml最低配置,它在这里写成字符串:

字符串EMPTY_XML='xml版本=\'1.0 '编码=\'UTF-8\' \r\n' //

!DOCTYPE配置\r\n' //

PUBLIC \ '-//my batis . org//DTD Config 3.0//EN \ ' \ r \ n '//

\ ' http://mybatis . org/dtd/my batis-3-config . dtd \ ' \ r \ n '//

配置\r\n' //

/configuration ';

然后根据mybatis的实现构建配置。

单击查看代码input stream input stream=new bytearray input stream(empty _ XML . getbytes(standard charset . utf _ 8));

XMLConfigBuilder XMLConfigBuilder=new XMLConfigBuilder(inputStream,null,null);

配置配置=xmlconfigbuilder . parse();

通过配置,您可以创建XPathParser来解析SQL。

单击代码字符串script=' script \从table _ name中选择count (0 ),其中如果test=\' param!=null ' AND num=# { param }/if \ n/script ';

XPath parser parser=new XPath parser(脚本,false,new Properties(),new xmlmapperentitysolver());

SqlSource source=createSqlSource(配置,parser . evalunode('/script '),null);

MapString,String params=new HashMap();

param.put('param ',' 1 ');

BoundSql BoundSql=source . getboundsql(params);

string SQL=BoundSqL . GetSqL();

结果

由上述代码解析的SQL是从表名中选择计数(0),其中1=1,如果有参数号,则num=1。

是一个带有占位符的高度安全的预编译SQL。使用它时,需要构建prepareStatement,然后通过jdbc传入PrepareStatement并手动设置参数。

自动化prepareStatement参数设置

参考Mybatis的默认值。

arameterHandler类的setParameters

创建PrepareStatement可以通过如springJdbc进行构建:
PreparedStatement ps = jdbcTemplate.getDataSource().getConnection().prepareStatement(boundSql.getSql());

然后调用DefaultParameterHandler的setParameters即可

获取完整SQL

参照mybatis-plus的PerformanceInterceptor类,该类可通过Statement获取SQL
需要的代码如下:

点击查看代码
    /**
     * COPY FROM {@link PerformanceInterceptor}
     */
    private static final String DruidPooledPreparedStatement = "com.alibaba.druid.pool.DruidPooledPreparedStatement";
    private static final String T4CPreparedStatement = "oracle.jdbc.driver.T4CPreparedStatement";
    private static final String OraclePreparedStatementWrapper = "oracle.jdbc.driver.OraclePreparedStatementWrapper";
    private Method oracleGetOriginalSqlMethod;
    private Method druidGetSQLMethod;
    /**
     * 获取原始SQL, COPY FROM {@link PerformanceInterceptor}
     */
    private String getOriginSql(PreparedStatement statement) {
        String originalSql = null;
        String stmtClassName = statement.getClass().getName();
        if (DruidPooledPreparedStatement.equals(stmtClassName)) {
            try {
                if (druidGetSQLMethod == null) {
                    Class clazz = Class.forName(DruidPooledPreparedStatement);
                    druidGetSQLMethod = clazz.getMethod("getSql");
                }
                Object stmtSql = druidGetSQLMethod.invoke(statement);
                if (stmtSql instanceof String) {
                    originalSql = (String) stmtSql;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else if (T4CPreparedStatement.equals(stmtClassName) || OraclePreparedStatementWrapper.equals(stmtClassName)) {
            try {
                if (oracleGetOriginalSqlMethod != null) {
                    Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
                    if (stmtSql instanceof String) {
                        originalSql = (String) stmtSql;
                    }
                } else {
                    Class clazz = Class.forName(stmtClassName);
                    oracleGetOriginalSqlMethod = getMethodRegular(clazz, "getOriginalSql");
                    if (oracleGetOriginalSqlMethod != null) {
                        // OraclePreparedStatementWrapper is not a public class, need set this.
                        oracleGetOriginalSqlMethod.setAccessible(true);
                        if (null != oracleGetOriginalSqlMethod) {
                            Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
                            if (stmtSql instanceof String) {
                                originalSql = (String) stmtSql;
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // ignore
            }
        }
        if (originalSql == null) {
            originalSql = statement.toString();
        }
        return originalSql;
    }

获取出来的SQL在mybatis中为com.mxxxx : SELECT....,大概是这样,需要截取:

点击查看代码
    /**
     * 获取sql语句开头部分, COPY FROM {@link PerformanceInterceptor}
     */
    private int indexOfSqlStart(String sql) {
        String upperCaseSql = sql.toUpperCase();
        SetInteger set = new HashSet();
        set.add(upperCaseSql.indexOf("SELECT "));
        set.add(upperCaseSql.indexOf("UPDATE "));
        set.add(upperCaseSql.indexOf("INSERT "));
        set.add(upperCaseSql.indexOf("DELETE "));
        set.remove(-1);
        if (CollectionUtils.isEmpty(set)) {
            return -1;
        }
        ListInteger list = new ArrayList(set);
        list.sort(Comparator.naturalOrder());
        return list.get(0);
    }

这样就获取出来完整的SQL了,可喜可贺。但是!

Oracle数据库使用该方法无效,打印出来还是预编译SQL

完整SQL第二方案

在第一个方案执行失败的情况下(可遍历字符串看有没有),增加该方案:
参照:https://www.cnblogs.com/aipan/p/7237854.html
增加LoggableStatement,改造DefaultParameterHandler的setParameters(Copy出来作为新方法):

点击查看代码
    @SuppressWarnings({"unchecked", "rawtypes"})
    private LoggableStatement buildPreparedStatement(JdbcTemplate jdbcTemplate, BoundSql boundSql) throws SQLException {
        PreparedStatement ps = jdbcTemplate.getDataSource().getConnection().prepareStatement(boundSql.getSql());
        // 改造点
        LoggableStatement ls = new LoggableStatement(ps, boundSql.getSql());
        ListParameterMapping parameterMappings = boundSql.getParameterMappings();
        Object parameterObject = boundSql.getParameterObject();
        if (parameterMappings != null) {
            for (int i = 0; i  parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null  jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    try {
                        typeHandler.setParameter(ls, i + 1, value, jdbcType);
                    } catch (TypeException | SQLException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    }
                }
            }
        }
        return ls;
    }

在获取完整SQL失败后,即可通过LoggableStatement来获取SQL:

if (!isCorrectGetSql(boundSql, originalSql)) {
   originalSql = statement.getQueryString();
}

————————————————————————————

上面提到项目的该类完整代码:

点击查看代码
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.scripting.xmltags.XMLScriptBuilder;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeException;
import org.apache.ibatis.type.TypeHandler;
import org.springframework.jdbc.core.JdbcTemplate;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
/**
 * 使用Mybatis的XML解析来进行SQL构建的执行器
 */
public class MybatisTemplateSqlExcutor {
    /**
     * 脚本模板
     */
    private static final String SCRIPT_TEMPLATE = "script\n%s\n/script";
    /**
     * MP环境配置
     */
    private Configuration configuration;
    /**
     * COPY FROM {@link PerformanceInterceptor}
     */
    private static final String DruidPooledPreparedStatement = "com.alibaba.druid.pool.DruidPooledPreparedStatement";
    private static final String T4CPreparedStatement = "oracle.jdbc.driver.T4CPreparedStatement";
    private static final String OraclePreparedStatementWrapper = "oracle.jdbc.driver.OraclePreparedStatementWrapper";
    private Method oracleGetOriginalSqlMethod;
    private Method druidGetSQLMethod;
    /**
     * 构造器,初始化MP环境配置
     */
    public MybatisTemplateSqlExcutor() {
        InputStream inputStream = new ByteArrayInputStream(EMPTY_XML.getBytes(StandardCharsets.UTF_8));
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(inputStream, null, null);
        configuration = xmlConfigBuilder.parse();
    }
    public String parseSql(JdbcTemplate jdbcTemplate, String sqlTemplate, MapString, Object params) throws SQLException {
        String script = String.format(SCRIPT_TEMPLATE, sqlTemplate);
        XPathParser parser = new XPathParser(script, false, new Properties(), new XMLMapperEntityResolver());
        SqlSource source = createSqlSource(configuration, parser.evalNode("/script"), Map.class);
        BoundSql boundSql = source.getBoundSql(params);
        LoggableStatement statement = buildPreparedStatement(jdbcTemplate, boundSql);
        String originalSql = getOriginSql(statement.getPreparedStatement());
        int index = indexOfSqlStart(originalSql);
        if (index  0) {
            originalSql = originalSql.substring(index);
        }
        if (!isCorrectGetSql(boundSql, originalSql)) {
            originalSql = statement.getQueryString();
        }
        return originalSql;
    }
    /**
     * 从MP复制过来的脚本解析方法
     */
    private SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) {
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        return builder.parseScriptNode();
    }
    /**
     * 根据BoundSql组装PreparedStatement,用于获取实际SQL
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private LoggableStatement buildPreparedStatement(JdbcTemplate jdbcTemplate, BoundSql boundSql) throws SQLException {
        PreparedStatement ps = jdbcTemplate.getDataSource().getConnection().prepareStatement(boundSql.getSql());
        LoggableStatement ls = new LoggableStatement(ps, boundSql.getSql());
        ListParameterMapping parameterMappings = boundSql.getParameterMappings();
        Object parameterObject = boundSql.getParameterObject();
        if (parameterMappings != null) {
            for (int i = 0; i  parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else {
                        MetaObject metaObject = configuration.newMetaObject(parameterObject);
                        value = metaObject.getValue(propertyName);
                    }
                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null  jdbcType == null) {
                        jdbcType = configuration.getJdbcTypeForNull();
                    }
                    try {
                        typeHandler.setParameter(ls, i + 1, value, jdbcType);
                    } catch (TypeException | SQLException e) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
                    }
                }
            }
        }
        return ls;
    }
    /**
     * 获取sql语句开头部分
     */
    private int indexOfSqlStart(String sql) {
        String upperCaseSql = sql.toUpperCase();
        SetInteger set = new HashSet();
        set.add(upperCaseSql.indexOf("SELECT "));
        set.add(upperCaseSql.indexOf("UPDATE "));
        set.add(upperCaseSql.indexOf("INSERT "));
        set.add(upperCaseSql.indexOf("DELETE "));
        set.remove(-1);
        if (CollectionUtils.isEmpty(set)) {
            return -1;
        }
        ListInteger list = new ArrayList(set);
        list.sort(Comparator.naturalOrder());
        return list.get(0);
    }
    /**
     * 获取原始SQL, COPY FROM {@link PerformanceInterceptor}
     */
    private String getOriginSql(PreparedStatement statement) {
        String originalSql = null;
        String stmtClassName = statement.getClass().getName();
        if (DruidPooledPreparedStatement.equals(stmtClassName)) {
            try {
                if (druidGetSQLMethod == null) {
                    Class clazz = Class.forName(DruidPooledPreparedStatement);
                    druidGetSQLMethod = clazz.getMethod("getSql");
                }
                Object stmtSql = druidGetSQLMethod.invoke(statement);
                if (stmtSql instanceof String) {
                    originalSql = (String) stmtSql;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else if (T4CPreparedStatement.equals(stmtClassName) || OraclePreparedStatementWrapper.equals(stmtClassName)) {
            try {
                if (oracleGetOriginalSqlMethod != null) {
                    Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
                    if (stmtSql instanceof String) {
                        originalSql = (String) stmtSql;
                    }
                } else {
                    Class clazz = Class.forName(stmtClassName);
                    oracleGetOriginalSqlMethod = getMethodRegular(clazz, "getOriginalSql");
                    if (oracleGetOriginalSqlMethod != null) {
                        // OraclePreparedStatementWrapper is not a public class, need set this.
                        oracleGetOriginalSqlMethod.setAccessible(true);
                        if (null != oracleGetOriginalSqlMethod) {
                            Object stmtSql = oracleGetOriginalSqlMethod.invoke(statement);
                            if (stmtSql instanceof String) {
                                originalSql = (String) stmtSql;
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // ignore
            }
        }
        if (originalSql == null) {
            originalSql = statement.toString();
        }
        return originalSql;
    }
    /**
     * 获取此方法名的具体 Method
     *
     * @param clazz class 对象
     * @param methodName 方法名
     * @return 方法
     */
    public Method getMethodRegular(Class clazz, String methodName) {
        if (Object.class.equals(clazz)) {
            return null;
        }
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.getName().equals(methodName)) {
                return method;
            }
        }
        return getMethodRegular(clazz.getSuperclass(), methodName);
    }
    /**
     * 判断是否正确的获取了SQL
     */
    private boolean isCorrectGetSql(BoundSql boundSql, String originSql) {
        return countQuestionMark(boundSql.getSql())  countQuestionMark(originSql);
    }
    /**
     * 统计占位符
     */
    private int countQuestionMark(String sql) {
        int result = 0;
        for (char c : sql.toCharArray())
            if (c == '')
                result++;
        return result;
    }
    /**
     * 空MP配置模板,用于构建MP环境配置,(放这里是由于博客的编辑器识别问题,会导致高亮错误)
     */
    private static final String EMPTY_XML = "xml version=\"1.0\" encoding=\"UTF-8\" \r\n"
            + "!DOCTYPE configuration\r\n"
            + " PUBLIC \"-//mybatis.org//DTD Config 3.0//EN\"\r\n"
            + " \"http://mybatis.org/dtd/mybatis-3-config.dtd\"\r\n"
            + "configuration\r\n"
            + "/configuration";
}

称之为Excutor是因为这其实是个子类,只提供parseSql

其他

若有更好的方法,请务必告诉我(??????)??

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/126072.html

(3)

相关推荐

  • openwrt状态监控(openwrt实现远程管理)

    技术OpenWRT如何实现远程监听小编给大家分享一下OpenWRT如何实现远程监听,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!1、安装命令o

    攻略 2021年12月18日
  • 洛谷 P5858 「SWTR-03」Golden Sword

    技术洛谷 P5858 「SWTR-03」Golden Sword 洛谷 P5858 「SWTR-03」Golden Sword题目链接:
    https://www.luogu.com.cn/problem

    礼包 2021年11月7日
  • 怎么修改linux中rac上IP地址

    技术怎么修改linux中rac上IP地址本篇内容主要讲解“怎么修改linux中rac上IP地址”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么修改linux中rac上IP地

    攻略 2021年11月5日
  • 怎么用Java实现判断括号的合法性

    技术怎么用Java实现判断括号的合法性本篇内容介绍了“怎么用Java实现判断括号的合法性”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,

    攻略 2021年11月15日
  • 如何理解IDEA 的项目配置和Web部署

    技术如何理解IDEA 的项目配置和Web部署这篇文章将为大家详细讲解有关如何理解IDEA 的项目配置和Web部署,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。作者:Dul

    攻略 2021年11月18日
  • Go语言核心36讲,Go语言进阶技术二)--学习笔记

    技术Go语言核心36讲,Go语言进阶技术二)--学习笔记 Go语言核心36讲(Go语言进阶技术二)--学习笔记08 | container包中的那些容器
    我们在上次讨论了数组和切片,当我们提到数组的时候

    礼包 2021年10月20日