mybatis如何做到执行string形式的sql文件


声明 : 此博客为博主原创,转载请说明出处。

 

1. 项目需求背景 

  有一个这样的功能,前台传递 sql 形式的字符串 (符合mybatis的dtd格式),但是呢,前台是不想转义 大于号、小于号 等等

这些被mybatis的 xml 所引用的特殊字符串, 然后后台我们就可以存取到数据库当中去,保留这个格式的sql,然后在 【执行sql】

这个功能按钮上,前台点击就可以执行这段sql了。

 

  JSON格式数据 

  

{
    "qry_db": "jlerp",
    "class_id": 222,
    "qry_id": null,
    "qry_type" : "sql",
    "qry_name": "查询会计科目余额aaa",
    "qry_file": "查询会计科目余额",
    "qry_db": "ccc",
    "qry_desc": "查询科目余额表中会计科目余额",
    "qry_sql": "select nvl(sum(gb.period_net_dr),0)   >=period_net_dr\n  from gl_balances gb, \n       gl_code_combinations gcc\n where gb.code_combination_id=gcc.code_combination_id\n   and gcc.summary_flag = 'N'\n      and gb.set_of_books_id = 2\n   and gb.period_name = #{period}\n   and gcc.segment1 like '${segment1}%'\n<if test=\"segment2!=''\">\n   and gcc.segment2=#{segment2}\n</if>\n   and gcc.segment3 like '${segment3}%'",
    "in": [
        {    
            "dict_id": undefined,
            "authtype_id": "ou",
            "in_name": "b",
            "dict_name": "供应商aaa",
            "authtype_desc": null,
            "datatype": "number",
            "qry_id": null,
            "in_id": "period_num",
            "validate": null
        },
        {
            "dict_id": null,
            "authtype_id": null,
            "in_name": "a",
            "dict_name": null,
            "authtype_desc": null,
            "datatype": "string",
            "qry_id": null,
            "in_id": "period_year",
            "validate": null
        },
        {
            "dict_id": 3,
            "authtype_id": "ou",
            "in_name": "c",
            "dict_name": "部门",
            "authtype_desc": null,
            "datatype": "date",
            "qry_id": null,
            "in_id": "segment1",
            "validate": null
        },
        {
            "dict_id": 4,
            "authtype_id": "dept",
            "in_name": "d",
            "dict_name": "公司",
            "authtype_desc": null,
            "datatype": "string",
            "qry_id": null,
            "in_id": "segment3",
            "validate": null
        },
        {
            "dict_id": 5,
            "authtype_id": null,
            "in_name": "e",
            "dict_name": "项目",
            "authtype_desc": null,
            "datatype": "string",
            "qry_id": null,
            "in_id": "segment5",
            "validate": null
        }
    ],
    "out": [
        {
            "datatype": null,
            "out_name": "net_bal",
            "link": {"qry_id": null,
    "link_qry_id": "200",
    param: [
        {
        link_in_id:'vendor_id',
        link_in_id_value_type:'out',
        link_in_id_value:'vendor_id'
        },
        {link_in_id:'date',
            link_in_id_value_type:'out',
            link_in_id_value:'vendor_id'
        },
        {link_in_id:'name',
                link_in_id_value_type:'out',
                link_in_id_value:'vendor_id'
        },
    ]},
            "qry_id": null,
            "out_id": "net_bal",
            width: 0.12,
            render: "render1"
        }
    ]
}
JSON格式数据

 

  

2. 分析

  2.1  sql存到xml?

   若我们把sql存放到 一个xml 当中,这样做最简单了,然后直接 刷新当前sqlSession , 即是再启动一次连接 (为什么要这么做?),众所周知,在项目启动的时候,mybatis的 sqlSessionFactory 会加载 configuration,这个configuration 可能是 从 [mybatis-config.xml] 当中读取到的,然后用来构造 sqlSessionFactory , 当然,我在这里就直接自己 new Configuration 来构造了,好了 我想说的是,无论怎样, configuration 会有 mapper 扫描包的信息,所以一旦有变动,我们必须 再次重新加载一次 此配置。 要不然,sqlSession.selectList( statementId,Class clazz ) 这个当中的 statmentId 你怎么找得到呢? 这个id 被放在配置类的 【MappedStatement】 当中了啊。

   然后还要考虑到,前端传递的 sql 字符串当中 有 大于号 小于号,并且有 <if><foreach> 等等 OGNL 表达式,我们要是把其存放到 xml 当中去了,大于号 小于号是要转义成 &gt; &lt; 等的,那么我要洗个 regx 正则来完成吗?  (PS:哎,能力有限,场景无限,这个方法我觉得不行),虽然我们加 <cache> 标签啊,指定sql 为存储过程等啊在xml 当中要方便很多,但是我们发现放进去会存在较大问题,这个还得慎重考虑。

        

  2.2 研究下 注解形式SQL? 

  诶,我们发现啊 现在的 mybatis 其实还支持 注解形式的sql, 类似于 : @Select("<script>SELECT ...</script>")

 在这个 SELECT 当中的可以填写的sql的 标签有 :

       如果nodeHandlers在课堂中检查方法org.apache.ibatis.builder.BuilderException,将注意到支持的元素有:

  • trim
  • where
  • set
  • foreach
  • if
  • choose
  • when
  • otherwise
  • bind

     所以,我们写sql要注意点,这种注解 SQL 带来的不便就是不能与外界绑定 嵌套SQL 。但是这又有何妨呢?顶多我们把sql都写全。

  然后,我们怎么仿照这种形式结合mybatis玩转呢? 下面步骤 3就来解释详细步骤。

 

3. 基于 String 形式的 sql 动态构建

    首先,我们要把 sql 带上 <script> 标签,这样才能被解析为 dynamicSql (因为加上这个标签,才能帮助我们解析出 OGNL表达式的 结果),

   其实,我们再来构建 configuration 的 mappedStatment 。为什么构建这玩意???  大家可以想象一下 selectList 当中的 第一个入参 statementId

   这是个撒玩意? 看源码 

  

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  我们看上面的  来自 org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds) 

  第一个参数是什么? 是 configuration 当中的 mappedStatement 属性的key,这是个map 结构。所以笔者的用心在于 如何构建出这个 mappedStatement 呢?

  不妨直接 看 mappedStatement 这个类的构建方法 -》 

  org.apache.ibatis.mapping.MappedStatement.Builder 

  

public static class Builder {
    private MappedStatement mappedStatement = new MappedStatement();

    public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      mappedStatement.statementType = StatementType.PREPARED;
      mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
      mappedStatement.resultMaps = new ArrayList<ResultMap>();
      mappedStatement.sqlCommandType = sqlCommandType;
      mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      String logId = id;
      if (configuration.getLogPrefix() != null) {
        logId = configuration.getLogPrefix() + id;
      }
      mappedStatement.statementLog = LogFactory.getLog(logId);
      mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
    }

  这个方法的这个 ,哦,不对,是静态内部类了,这个玩意能够构建一些你能想到的mappedStatement,比如你要加cache ,比如你要知道自增长返回主键,你要指定keyGenerate,你想把自己的

 此sql 指定为是执行  CALLABLE  存储过程,这个类都能帮你完成。

  至此,我们的可行性步骤分析出来了 : 

  前台传递 sql -----》  后台 mybatis的configuration 的mappedStatment 创建一个新的 -——》 sqlSession再次执行此 mappedStatment 的ID 即可。

       如何构建 mappedStatent,我们可以直接使用 Builder 静态内部类来完成,并适当的指定自己的方式。那么我们就回到如何构建这个话题了,最基本的 我们需要一个 SqlSource 对吧,还有

configuration对象,以及自己想指定的 statementId , 还有个 此sql的类型,是 select?insert?update? 还是其他? 

  嗯,然后构建好了,我们就能 往当前 configuration当中注入了,待会,我们怎么构建sqlSource?  这个就是我们写成 <script> 标签的目的所在了。 

  我们可以使用 

LanguageDriver languageDriver = configuration.getDefaultScriptingLanguageInstance(); 
  来得到我们的sqlSource。OK,说了这么多,下面我就贴出代码,贴出用例图。

4. 分析图
  

 

  这是一个mybatis执行过程图,下面我贴出我的 代码过程图 。
  

 

 5. 代码 
  
package root.report.util;

import org.apache.ibatis.mapping.*;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.apache.poi.ss.formula.functions.T;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Auther: pccw-lxf
 * @Date: 2018/11/9 16:14
 * @Description:
 */
public class ExecuteSqlUtil {

    private static Logger log = Logger.getLogger(ExecuteSqlUtil.class);

    /**
     *
     * 功能描述:
     *      对符合 mybatis.dtd形式的sql进行动态sql解析并执行,返回Map结构的数据集
     * @param: executeSql 要执行的sql , sqlSession 数据库会话,namespace 命名空间,mapper_id mapper的ID,bounds 分页参数
     * @return:
     * @auther:
     * @date: 2018/11/9 16:17
     */
    public static List<?> executeDataBaseSql(String executeSql, SqlSession sqlSession, String namespace, String mapper_id, RowBounds bounds,
                                               Class<?> clazz,Object param){
        // 1. 对executeSql 加上script标签
        StringBuffer sb = new StringBuffer();
        sb.append("<script>");
        sb.append(executeSql);
        sb.append("</script>");
        log.info(sb.toString());
        Configuration configuration = sqlSession.getConfiguration();
        LanguageDriver languageDriver = configuration.getDefaultScriptingLanguageInstance();  // 2. languageDriver 是帮助我们实现dynamicSQL的关键
        SqlSource sqlSource = languageDriver.createSqlSource(configuration,sb.toString(),clazz);  //  泛型化入参
        newSelectMappedStatement(configuration,namespace+"."+mapper_id,sqlSource,clazz);
        List<?> list = sqlSession.selectList(namespace+"."+mapper_id,param,bounds);
        return list;
    }

    //
    private  static MappedStatement newSelectMappedStatement(Configuration configuration,String msId, SqlSource sqlSource, final Class<?> resultType) {
        // 加强逻辑 : 一定要防止 MappedStatement 重复问题
        MappedStatement msTest = null;
        try{
            synchronized (configuration) {   // 防止并发插入多次
                msTest = configuration.getMappedStatement(msId);
                if (msTest != null) {
                    configuration.getMappedStatementNames().remove(msTest.getId());
                }

            }
        }catch (IllegalArgumentException e){
            log.info("没有此mappedStatment,可以注入此mappedStatement到configuration当中");
        }
        // 构建一个 select 类型的ms ,通过制定SqlCommandType.SELECT
        MappedStatement ms = new MappedStatement.Builder(   // 注意!!-》 这里可以指定你想要的任何配置,比如cache,CALLABLE,
                configuration, msId, sqlSource, SqlCommandType.SELECT)   // -》 注意,这里是SELECT,其他的UPDATE\INSERT 还需要指定成别的
                .resultMaps(new ArrayList<ResultMap>() {
                    {
                        add(new ResultMap.Builder(configuration,
                                "defaultResultMap",
                                resultType,
                                new ArrayList<ResultMapping>(0)).build());
                    }
                })
                .build();
        synchronized (configuration){
            configuration.addMappedStatement(ms); // 加入到此中去
        }
        return ms;
    }

}

 

6. 测试调用结果: 
    

 

  测试所使用的代码是 : 
  
    public static void main(String[] args){

        // ApplicationContext context =
       // PropertiesTest propertiesTest = new  PropertiesTest();
       // System.out.println("aaa"+propertiesTest.getUser());

        SqlSession sqlSession = DbFactory.Open(DbFactory.FORM);
        String sql2 = "<script>select * from func_dict_value where dict_id=#{dict_id}" +
                " and value_code='${value_code}'";
        String sql = "<script>select * from func_dict_value where dict_id=#{dict_id,jdbcType=INTEGER}" +
                " <if  test=\"value_code == null\"></if>" +
                " <if  test=\"value_code != null\">and value_code='${value_code}'</if></script>";
        String sql3 = "<script>select a.dict_id,b.dict_name,a.value_code,a.value_name from func_dict_value a,func_dict b" +
                " where a.dict_id=#{dict_id,jdbcType=INTEGER} and a.dict_id=b.dict_id and a.dict_id>12</script>";
        String sql4 = "<script>select * from func_dict_value where dict_id>=13 "+
                " <if  test=\"value_code == null\"></if>" +
                " <if  test=\"value_code != null\">and value_code='${value_code}'</if></script>";
        Configuration configuration = sqlSession.getConfiguration();
        LanguageDriver languageDriver = configuration.getDefaultScriptingLanguageInstance();
        // languageDriver.createParameterHandler();
        SqlSource sqlSource = languageDriver.createSqlSource(configuration,sql4,Map.class);

        MappedStatement ms = newSelectMappedStatement(configuration,"testId",sqlSource,Map.class);

        SqlSource sqlSource2 = languageDriver.createSqlSource(configuration,sql3,Map.class);
        MappedStatement msCopy = newSelectMappedStatement(configuration,"testId",sqlSource2,Map.class);
       // configuration.setCacheEnabled(true);
       // configuration.addCache(ms.getCache());
        Map<String,Object> parameterMap =  new HashMap<>();
        parameterMap.put("dict_id",13);
        parameterMap.put("value_code","支撑网");
        List<Map<String,Object>> mapList = sqlSession.selectList("testId",parameterMap);
        System.out.println("map1->size"+mapList.size());
        System.out.println("-----------");
        String sql5 = "select a.dict_id,b.dict_name,a.value_code,a.value_name from func_dict_value a,func_dict b" +
                " where a.dict_id=#{dict_id,jdbcType=INTEGER} and a.dict_id=b.dict_id and a.dict_id>12";
        List<Map<String,Object>> mapList2 =
                (List<Map<String,Object>>) ExecuteSqlUtil.executeDataBaseSql(
                        sql5,sqlSession,"testDD","map11",null,Map.class,parameterMap);
        System.out.println("map2->size"+mapList2.size());

      /*  BoundSql  boundSql  = ms.getBoundSql(parameterMap);
        String finalSql = boundSql.getSql();
        BoundSql  boundSqlCopy =  msCopy.getBoundSql(parameterMap);
        System.out.println("boundSqlCopy->"+boundSqlCopy.getSql());
        System.out.println("boundSql->"+finalSql);
        System.out.println("我写的SQL->"+sql);*/
    }
测试代码

 


  


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM