首页 > 数据库 >提取MyBatis中XML语法构造SQL的功能

提取MyBatis中XML语法构造SQL的功能

时间:2023-07-23 15:13:02浏览次数:98  
标签:XML System println SQL MyBatis import null out

提取MyBatis中XML语法构造SQL的功能

MyBatis能够使用 *.xml来编辑XML语法格式的SQL语句,常用的xml标签有<where>, <if>, <foreach>等。

偶然遇到一个场景,只想使用MyBatis的解析XML语法生成SQL的功能,而不需其他功能,于是在@Select打断点,跟踪代码执行,后续发现和XML有关的类主要在包路径org.apache.ibatis.scripting.xmltags

下面只用简单的例子举例如何仅使用MyBaits中XML生成SQL的功能,不做太多抽象/封装逻辑、不考虑 SQL注入 等安全问题,以演示功能为主

1. 数据库表定义

person表定义
create table person
(
    id     int auto_increment comment '主键' primary key,
    name   varchar(255) null comment '名称',
    gender tinyint(1)   null comment '性别, 0 female, 1 man',
    age    int          null comment '年龄, 0~200'
);

2. XML文件中SQL代码

假设有如下XML语法的SQL代码片段,这里`item`故意和`collection`重名,主要是方便后续解析
<script>
  select * from person 
      where name like CONCAT('%', #{name} ,'%') 
      <if test='ageList != null'>
          and age in 
          <foreach collection='ageList' open='(' close =')' item='ageList' separator=','> 
                #{ageList}
          </foreach>
      </if> 
      <if test='gender != null'> 
          and gender > #{gender} 
      </if>
</script>

3. 代码示例和输出

3.1 使用XML语法生成SQL (#{}的版本)

java代码 (#{}的版本)

Java代码
package com.example.springboottest;

import com.google.common.base.Splitter;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;

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

import static org.apache.ibatis.scripting.xmltags.ForEachSqlNode.ITEM_PREFIX;

/**
 * @author : Ashiamd email: [email protected]
 * @date : 2023/7/22 2:44 PM
 */
public class MyBatisSqlTest2 {
    public static void main(String[] args) {

        //1.  这里用 Map 存放查询参数(实际项目中可以用POJO类或其他形式)
        Map<String, Object> paramMap = new HashMap<>();
        List<Integer> ageList = new ArrayList<>();
        ageList.add(18);
        ageList.add(19);
        paramMap.put("ageList", ageList);
        paramMap.put("name", "person");
        paramMap.put("gender", -1);

        // 2. 打印 SQL 中使用到的查询参数
        System.out.println("==== SQL中使用到的参数: ==== start ==");
        paramMap.entrySet().forEach(System.out::println);
        System.out.println("==== SQL中使用到的参数: ==== end ==" + System.lineSeparator());

        // 3. 构造XML语法的SQL (实际项目中可以通过注解等形式封装SQL字符串)
        String anotherSql = "<script>" +
                "select * from person " +
                "<where> " +
                "name like CONCAT('%', #{name} ,'%') " +
                "<if test='ageList != null'>" +
                "and age in " +
                "<foreach collection='ageList' open='(' close =')' item='ageList' separator=','>" +
                "#{ageList}" +
                "</foreach>" +
                "</if>" +
                "<if test='gender != null'> " +
                "and gender > #{gender} " +
                "</if> " +
                "</where>"
                + "</script>";
        Configuration configuration = new Configuration();
        XMLLanguageDriver xmlLanguageDriver = new XMLLanguageDriver();
        SqlSource sqlSource = xmlLanguageDriver.createSqlSource(configuration, anotherSql, Map.class);
        BoundSql boundSql = sqlSource.getBoundSql(paramMap);
        String preparedSQL = boundSql.getSql();

        // 4. 输出 预编译SQL (?表示需要填充传入的参数的位置)
        System.out.println("==== 预编译SQL : ==== start ==");
        System.out.println(preparedSQL);
        System.out.println("==== 预编译SQL : ==== end ==" + System.lineSeparator());

        // 5. 输出 预编译SQL的 参数列表 (之后替代 ? 位置)
        System.out.println("==== 预编译SQL的参数列表 : ==== start ==");
        boundSql.getParameterMappings().forEach(System.out::println);
        System.out.println("==== 预编译SQL的参数列表 : ==== end ==" + System.lineSeparator());

        // 6. 替换预编译SQL的 ?, 传递参数 (或者XML的SQL中直接使用 ${} 则preparedSQL直接是最终的SQL, 下面这边再做替换其实也没啥意义)
        Splitter splitter = Splitter.on("?");
        Iterable<String> splitIterable = splitter.split(preparedSQL);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        StringBuilder stringBuilder = new StringBuilder();
        int i = 0;
        for (String str : splitIterable) {
            if (StringUtils.isBlank(str)) {
                continue;
            }
            stringBuilder.append(str);
            ParameterMapping paramObj = parameterMappings.get(i);
            String fieldName = paramObj.getProperty();
            Object value;
            // 这里 ITEM_PREFIX 见处理<foreach>的 org.apache.ibatis.scripting.xmltags.ForEachSqlNode.ITEM_PREFIX
            if (fieldName.startsWith(ITEM_PREFIX)) {
                fieldName = fieldName.substring(7);
                int indexIndex = fieldName.lastIndexOf('_');
                int valueIndex = NumberUtils.toInt(fieldName.substring(indexIndex + 1));
                fieldName = fieldName.substring(0, indexIndex);
                List listValue = (List) paramMap.get(fieldName);
                value = listValue.get(valueIndex);
            } else {
                value = paramMap.get(fieldName);
            }
            stringBuilder.append(value);
            i++;
        }
        String finalSqlWithParam = stringBuilder.toString();

        // 7. 输出最后传入参数后的 SQL (实际用 ${} 即可免去自己再处理一遍参数的情况)
        System.out.println("==== 最终完整的SQL : ==== start ==");
        System.out.println(finalSqlWithParam);
        System.out.println("==== 最终完整的SQL : ==== end ==");
    }
}

运行输出结果 (#{}的版本)

运行输出结果
==== SQL中使用到的参数: ==== start ==
gender=-1
name=person
ageList=[18, 19]
==== SQL中使用到的参数: ==== end ==

==== 预编译SQL : ==== start ==
select * from person  WHERE name like CONCAT('%', ? ,'%') and age in (?,?) and gender > ?
==== 预编译SQL : ==== end ==

==== 预编译SQL的参数列表 : ==== start ==
ParameterMapping{property='name', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}
ParameterMapping{property='__frch_ageList_0', mode=IN, javaType=class java.lang.Integer, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}
ParameterMapping{property='__frch_ageList_1', mode=IN, javaType=class java.lang.Integer, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}
ParameterMapping{property='gender', mode=IN, javaType=class java.lang.Object, jdbcType=null, numericScale=null, resultMapId='null', jdbcTypeName='null', expression='null'}
==== 预编译SQL的参数列表 : ==== end ==

==== 最终完整的SQL : ==== start ==
select * from person  WHERE name like CONCAT('%', person ,'%') and age in (18,19) and gender > -1
==== 最终完整的SQL : ==== end ==

3.2 使用XML语法生成SQL (${}的版本)

java代码 (${}的版本)

Java代码
package com.example.springboottest;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;

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

/**
 * @author : Ashiamd email: [email protected]
 * @date : 2023/7/22 2:44 PM
 */
public class MyBatisSqlTest2 {
    public static void main(String[] args) {

        //1.  这里用 Map 存放查询参数(实际项目中可以用POJO类或其他形式)
        Map<String, Object> paramMap = new HashMap<>();
        List<Integer> ageList = new ArrayList<>();
        ageList.add(18);
        ageList.add(19);
        paramMap.put("ageList", ageList);
        paramMap.put("name", "person");
        paramMap.put("gender", -1);

        // 2. 打印 SQL 中使用到的查询参数
        System.out.println("==== SQL中使用到的参数: ==== start ==");
        paramMap.entrySet().forEach(System.out::println);
        System.out.println("==== SQL中使用到的参数: ==== end ==" + System.lineSeparator());

        // 3. 构造XML语法的SQL (实际项目中可以通过注解等形式封装SQL字符串)
        String anotherSql = "<script>" +
                "select * from person " +
                "<where> " +
                "name like CONCAT('%', ${name} ,'%') " +
                "<if test='ageList != null'>" +
                "and age in " +
                "<foreach collection='ageList' open='(' close =')' item='ageList' separator=','>" +
                "${ageList}" +
                "</foreach>" +
                "</if>" +
                "<if test='gender != null'> " +
                "and gender > ${gender} " +
                "</if> " +
                "</where>"
                + "</script>";
        Configuration configuration = new Configuration();
        XMLLanguageDriver xmlLanguageDriver = new XMLLanguageDriver();
        SqlSource sqlSource = xmlLanguageDriver.createSqlSource(configuration, anotherSql, Map.class);
        BoundSql boundSql = sqlSource.getBoundSql(paramMap);
        String preparedSQL = boundSql.getSql();

        // 4. 输出 预编译SQL (?表示需要填充传入的参数的位置)
        System.out.println("==== 预编译SQL : ==== start ==");
        System.out.println(preparedSQL);
        System.out.println("==== 预编译SQL : ==== end ==" + System.lineSeparator());

        // 5. 输出 预编译SQL的 参数列表 (之后替代 ? 位置)
        System.out.println("==== 预编译SQL的参数列表 : ==== start ==");
        boundSql.getParameterMappings().forEach(System.out::println);
        System.out.println("==== 预编译SQL的参数列表 : ==== end ==" + System.lineSeparator());

        // 6. 替换预编译SQL的 ?, 传递参数 (或者XML的SQL中直接使用 ${} 则preparedSQL直接是最终的SQL, 下面这边再做替换其实也没啥意义)
//        Splitter splitter = Splitter.on("?");
//        Iterable<String> splitIterable = splitter.split(preparedSQL);
//        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//        StringBuilder stringBuilder = new StringBuilder();
//        int i = 0;
//        for (String str : splitIterable) {
//            if (StringUtils.isBlank(str)) {
//                continue;
//            }
//            stringBuilder.append(str);
//            ParameterMapping paramObj = parameterMappings.get(i);
//            String fieldName = paramObj.getProperty();
//            Object value;
//            // 这里 ITEM_PREFIX 见处理<foreach>的 org.apache.ibatis.scripting.xmltags.ForEachSqlNode.ITEM_PREFIX
//            if (fieldName.startsWith(ITEM_PREFIX)) {
//                fieldName = fieldName.substring(7);
//                int indexIndex = fieldName.lastIndexOf('_');
//                int valueIndex = NumberUtils.toInt(fieldName.substring(indexIndex + 1));
//                fieldName = fieldName.substring(0, indexIndex);
//                List listValue = (List) paramMap.get(fieldName);
//                value = listValue.get(valueIndex);
//            } else {
//                value = paramMap.get(fieldName);
//            }
//            stringBuilder.append(value);
//            i++;
//        }
//        String finalSqlWithParam = stringBuilder.toString();

        // 7. 输出最后传入参数后的 SQL (实际用 ${} 即可免去自己再处理一遍参数的情况)
        System.out.println("==== 最终完整的SQL : ==== start ==");
//        System.out.println(finalSqlWithParam);
        System.out.println(preparedSQL);
        System.out.println("==== 最终完整的SQL : ==== end ==");
    }
}


运行输出结果 (${}的版本)

运行输出结果
==== SQL中使用到的参数: ==== start ==
gender=-1
name=person
ageList=[18, 19]
==== SQL中使用到的参数: ==== end ==

==== 预编译SQL : ==== start ==
select * from person  WHERE name like CONCAT('%', person ,'%') and age in (18,19) and gender > -1
==== 预编译SQL : ==== end ==

==== 预编译SQL的参数列表 : ==== start ==
==== 预编译SQL的参数列表 : ==== end ==

==== 最终完整的SQL : ==== start ==
select * from person  WHERE name like CONCAT('%', person ,'%') and age in (18,19) and gender > -1
==== 最终完整的SQL : ==== end ==

标签:XML,System,println,SQL,MyBatis,import,null,out
From: https://www.cnblogs.com/Ashiamd/p/17575020.html

相关文章

  • MYSQL
    Smiling&Weeping----或许换个时间,我们真的很合适1.1初识数据库数据库是将大量数据保存起来,通过计算机加工而成的可以进行高效访问的数据集合。该数据集合称为数据库(Database,DB)。用来管理数据库的计算机系统称为数据库管理系统(DatabaseManagemen......
  • MyBatis 常用工具类
    SQL类MyBatis提供了一个SQL工具类,使用这个工具类,我们可以很方便在Java代码动态构建SQL语句StringnewSql=newSQL()({SELECT("P.ID,P.USERNAME,P.PASSWORD,P.FULLNAME");SELECT("P.LASTNAME,P.CREATEDON,P.UPDATEDON");FROM("PERSONP");FR......
  • java: 找不到符号 符号: 方法 findSql6(java.util.Map<java.lang.String,java.lan
    解决"java:找不到符号符号:方法findSql6(java.util.Map<java.lang.String,java.lan"错误作为经验丰富的开发者,当遇到编译错误时,我们需要仔细分析错误信息并采取相应的解决方法。在这个任务中,我们需要解决"java:找不到符号符号:方法findSql6(java.util.Map<java.lang.St......
  • SpringBoot项目集成Mybatis Generator代码生成器
    添加依赖在项目的pom.xml文件中添加以下依赖<!--mybatisgenerator自动生成代码插件--><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId>......
  • MyBatisPlus公共字段自动填充
    公共字段自动填充公共字段新增员工,修改、编辑员工信息时,都需要设置创建时间,修改时间等操作,这些操作过于重复、繁琐,为了有更快捷高效的,MyBatisPlus提供了公共字段自动填充功能,当我们执行insert和update操作时才执行MyBatisPLus公共字段自动填充就是在插入或者修改操作时,为指定字......
  • MyBatis-Plus文件上传方法
    网站的文件上传方法本地存储上传//本地存储方式MultipartFile接受文件@PostMapping("/save")publicResultsave(Stringusername,Integerage,MultipartFileimage)throwsIOException{log.info("文件:{},{},{}",username,age,image);......
  • MySQL之存储过程(循环)
    MySQL之存储过程变量@@是系统变量@是用户自定义的变量系统变量系统变量是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)。查看系统变量模板 SHOW[SESSION|GLOBAL]VARIABLES;--查看所有系统变量 SHOW[S......
  • go语言mysql驱动
    连接数据库是典型的CS编程,服务器端被动等待客户端建立TCP连接,并在此连接上进行特定的应用层协议。但一般用户并不需要了解这些细节,这些都被打包到了驱动库当中,只需要简单的调用打开就可以指定协议连接到指定的数据库。数据库的种类和产品太多,协议太多,Go官方很难提供针对不同数据......
  • SQL Server 的网络通信机制
    问题我试图了解SQLServer如何在网络上进行通信,因为我必须告诉我的网络团队在防火墙上打开哪些端口,以便边缘Web服务器与内部的SQLServer进行通信。我需要知道什么? 解决方案为了了解需要在哪里打开什么,我们首先简单谈谈当今常用的两个主要协议:TCP-传输控制协议UDP......
  • mysql auto_increment怎么删除
    MySQL中的auto_increment如何删除在MySQL中,auto_increment是一个非常有用的功能,它允许我们在插入数据时自动为表的主键字段生成唯一的递增值。然而,有时候我们可能需要删除表中的某些数据行,这就会导致auto_increment值出现断层。本文将介绍如何在MySQL中删除数据行时保持auto_incre......