【MyBatis详解】——从Xml配置解析到SQL执行过程,mybatis动态更新xml文件后热部署,不重启应用的方法
整体架构与使用Demo
Mybatis的架构整体可以分为3层:
- 接口层
也就是和数据库进行交互,核心接口为SqlSession,一个SqlSession对应着一次数据库会话,那么其生命周期不是永久的,理论上每次访问数据库时都需要创建它。
形式分为两种,一种是使用Mapper接口,一种是基于Mybatis提供的Api;- Mybatis提供的Api
需要我们提供StatementId和查询参数,传递给SqlSession对象,提供SqlSession对象实现与数据库的交互;但是这种创建sqlSession的形式不符合面向接口编程的习惯。 - 使用Mapper接口:
Mybatis将配置文件中的每一个Mapper节点都抽象为一个Mapper接口,根据SqlSession.getMapper(XXXMapper.class),Mybatis将通过动态代理,生成一个Mapper实例。但当我们调用Mapper接口中的方法时,Mybatis会根据方法名和参数,确定StatementId,底层还是通过SqlSession来实现对数据库的操作。
- Mybatis提供的Api
- 数据处理层
- 配置解析
- 参数、结果集映射:java数据类型与jdbc数据类型的转换,包括查询阶段和结果返回阶段
- Sql解析:动态sql生成
- Sql执行
- 框架支持层
- 事务管理
- 连接池管理
- 缓存机制
使用Demo:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行SQL语句 (通过StatementId)
List list = sqlSession.selectList("com.example.mapper.personMapper.selectPersonByMap");
// 执行SQL语句(通过Mapper接口)
PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);
personMapper.selectPersonByMap();
Mybatis的初始化有两种形式:
- 基于XML配置文件:通过XML配置文件,将配置信息解析为Configuration对象。
- 基于Java API:在Java代码中手动创建Configuration对象,然后将配置参数set 进入Configuration对象中。(不推荐)
Mybatis核心流程
1. 创建SqlSessionFactory
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

将xml配置信息解析为Configuration对象,然后构建一个DefaultSqlSessionFactory类型的工厂:
- 创建XMLConfigBuilder,解析xml全局文件
- 创建XMLMapperBuilder,解析每个xml
- 最后将返回Configuration对象,保存着全部的xml配置信息;
配置解析——SQL解析(存储MappedStatement到Configuration中)
我们重点分析XMLMapperBuilder.parse方法中,对sql相关信息的解析过程:
- 解析Mapper节点,获得XNode,再解析XNode中的 select|insert|update|delete 节点,保存为XNode类型的List;遍历CRUD的XNode节点:
- 新建XMLStatementBuilder,调用parseStatementNode,用于解析每一个CRUD的节点
- 创建LanguageDriver(默认为XMLLanguageDriver,用于处理xml中的sql部分)
- 调用LanguageDriver.createSqlSource,获取SqlSource
- 根据SqlSource创建MappedStatement,并且注册到Configuration中
createSqlSource获取SqlSource的方法:
- 新建XMLScriptBuilder,并调用parse解析各个节点的sql部分
- 递归解析,最终得到MixedSqlNode(本质为SqlNode的List集合),并且标记此Sql类型为动态还是静态
- 如果是动态类型,返回DynamicSqlSource
- 如果是静态类型,返回RawSqlSource
- 返回的SqlSource将会保存在MappedStatement中,然后等sql执行的时候,通过getBoundSql方法触发sql的解析
总结
- XMLConfigBuilder
解析xml整体文件 - XMLMapperBuilder
解析每个xml文件 - XMLStatementBuilder
解析xml文件中各个select,insert,update,delete节点 - XMLScriptBuilder
解析xml中各个节点sql部分的Builder,产生MapperStatement,保存到Configuration中
SqlNode
节点解析时的工具类,简单理解就是xml中的每个标签,如update,trim,if标签
其实现类包括
实现动态Sql的关键就是 各个SqlNode的 apply方法
以IfSqlNode为例,如果满足条件,则apply,并返回true
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
而StaticTextSqlNode类型静态sql,则直接append
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
MappedStatement
是Configuration中的属性,表示一个CRUD节点的信息
SqlSource
是MappedStatement的属性,实现类包括:
- StaticSqlSource:最终静态SQL语句的封装,其他类型的SqlSource最终都委托给StaticSqlSource。
- RawSqlSource:原始静态SQL语句的封装,在加载时就已经确定了SQL语句,比动态SQL语句要快,因为不需要运行时解析SQL节点。
- 如#占位符
- DynamicSqlSource:动态SQL语句的封装,在运行时需要根据参数处理if等标签或者${} SQL拼接之后才能生成最后要执行的静态SQL语句。
- 如$占位符或者if等标签
- ProviderSqlSource:当SQL语句通过指定的类和方法获取时(使用@XXXProvider注解),需要使用本类,它会通过反射调用相应的方法得到SQL语句。
其getBoundSql方法提供BoundSql对象。
BoundSql
是从SqlSource中获取得到的信息,封装mybatis最终产生sql的类,包括sql语句,参数,参数源数据等参数;后续在执行Sql的时候将会使用到BoundSql。
2. 创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();

- 创建Transaction事务:
- 根据Configuration配置信息获取Environment环境对象
- 根据environment信息获取TransactionFactory,这里获取到的事务工厂,即对应着xml文件中的transactionManager节点
- newTransaction创建事务tx
- 创建Executor执行器:
- 调用Configuration对象的newExecutor方法,将事务对象tx传参进去;
- newExecutor方法,默认创建的是Simple类型的SimpleExecutor
(类型一共三种:SIMPLE, REUSE, BATCH)
如果开启了cacheEnabled(默认开启),那么包装为CachingExecutor:在查询数据之前先查找缓存,没有找到再从数据库查询并加入缓存。 - executor = (Executor) interceptorChain.pluginAll(executor);
使用了执行器链模式,使得Executor对象可以被插件拦截;
- 创建DefaultSqlSession:
- new DefaultSqlSession(configuration, executor, autoCommit)
SqlSession
Mybatis与数据库交互的核心接口,一个SqlSession对应着一次数据库会话。
Executor
执行器,真正的sql执行并不是SqlSession直接执行的,而是通过Executor去执行。
3. 获取Mapper
PersonMapper personMapper = sqlSession.getMapper(PersonMapper.class);

- 通过MapperRegister(Map类型的Mapper注册器Map<Class<?>, MapperProxyFactory<?>>)根据接口Class信息,获取到目标MapperProxyFactory
- 通过MapperProxyFactory创建MapperProxy(也就是Mapper接口的代理)
- Proxy.newProxyInstance动态代理创建
- 至此,我们通过SqlSession.getMapper获取到的personMapper对象,实际上是一个代理类
4. 通过Mapper接口调用CRUD方法
personMapper.selectPersonByMap();

被代理对象的方法的访问,都会落实到代理者的invoke上来,所以调用的mapper.select等方法,将会经过MapperProxy中的invoke:
- 创建MapperMethod对象:
根据CRUD的类型将调用分发到SqlSession的不同方法上 - 处理参数:
convertArgsToSqlCommandParam:当args数量>1时,将Object[]的参数包装为Map<String, Object> - SqlSession方法的执行:
- 根据MapperMethod内部类SqlCommand中存储的statementId,从Configuration中找对应的MappedStatement
- 将MappedStatement作为参数,调用Executor中对应的方法
MapperMethod
它是一个分发者,根据CRUD的类型将调用分发到SqlSession的不同方法上。
有两个内部类同时被创建:
- SqlCommand
interfaceName + methodName作为statementId,去Configuration中去找对应的MappedStatement
存储statementId、SqlCommandType(CRUD类型)信息
- MethodSignature
记录方法的参数、返回值等信息
5. Executor执行SQL

- 我们默认为调用的是有缓存的Executor,那么先本地查询缓存,如果没找到再去调用(默认简单类型)SimpleExecutor的query方法
- 创建StatementHandler:
默认类型为ParparedStatementHandler,Executor将执行的任务交给它,它才是sql的具体执行者。也可以被插件所拦截(常见的物理分页插件)- prepare:预编译sql,得到Statement对象
- 调用ParameterHandler:处理参数
- 调用ResultSetHandler:处理执行结果
StatementHandler
由此可见,Executor也不是真正的执行者,而是由StatementHandler完成的。在StatementHandler被创建的过程中,有一个getBoundSql的步骤,触发了动态sql的处理,下节详细分析。
【MyBatis详解】——从Xml配置解析到SQL执行过程_查看xml里写的sql语句实际是怎么执行的-CSDN博客
mybatis动态更新xml文件后热部署,不重启应用的方法_idea xml文件 热更新-CSDN博客
mybatis应用程序,由于是半自动化的sql, 有大量的sql是在xml文件中配置的,而在开发程序的过程中,通常需要边写sql变调试应用。但在默认情况下,xml文件里配置的sql语句是被放入到缓存中去了,每次更改有sql语句的xml文件,需要重新启动应用,这样工作效率很低,于是很希望有一个动态加载xml文件的功能,自动加载新的sql语句,并重新写入到缓存中,在网上参考了很多资料,最终弄了一个简单的东西出来,直接写成了spring mvc的controller。代码如下:
-
package com.yihaomen.controller;
-
import java.io.IOException;
-
import java.lang.reflect.Field;
-
import java.util.HashMap;
-
import java.util.Map;
-
import java.util.Set;
-
import org.apache.commons.logging.Log;
-
import org.apache.commons.logging.LogFactory;
-
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
-
import org.apache.ibatis.session.Configuration;
-
import org.apache.ibatis.session.SqlSessionFactory;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.context.annotation.Scope;
-
import org.springframework.core.io.Resource;
-
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
-
import org.springframework.stereotype.Controller;
-
import org.springframework.web.bind.annotation.RequestMapping;
-
import org.springframework.web.bind.annotation.ResponseBody;
-
@Scope("prototype")
-
@Controller
-
@RequestMapping("/sql")
-
public class SQLSessionCacheController {
-
-
private Log log = LogFactory.getLog(SQLSessionCacheController.class);
-
-
@Autowired
-
private SqlSessionFactory sqlSessionFactory;
-
-
private Resource[] mapperLocations;
-
private String packageSearchPath = "classpath*:**/mappers/*.xml";
-
private HashMap<String, Long> fileMapping = new HashMap<String, Long>();// 记录文件是否变化
-
-
@RequestMapping("/refresh")
-
@ResponseBody
-
public String refreshMapper() {
-
try {
-
Configuration configuration = this.sqlSessionFactory.getConfiguration();
-
-
// step.1 扫描文件
-
try {
-
this.scanMapperXml();
-
} catch (IOException e) {
-
log.error("packageSearchPath扫描包路径配置错误");
-
return "packageSearchPath扫描包路径配置错误";
-
}
-
-
System.out.println("==============刷新前mapper中的内容===============");
-
for (String name : configuration.getMappedStatementNames()) {
-
System.out.println(name);
-
}
-
-
// step.2 判断是否有文件发生了变化
-
if (this.isChanged()) {
-
// step.2.1 清理
-
this.removeConfig(configuration);
-
// step.2.2 重新加载
-
for (Resource configLocation : mapperLocations) {
-
try {
-
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(), configuration, configLocation.toString(), configuration.getSqlFragments());
-
xmlMapperBuilder.parse();
-
log.info("mapper文件[" + configLocation.getFilename() + "]缓存加载成功");
-
} catch (IOException e) {
-
log.error("mapper文件[" + configLocation.getFilename() + "]不存在或内容格式不对");
-
continue;
-
}
-
}
-
}
-
-
System.out.println("==============刷新后mapper中的内容===============");
-
for (String name : configuration.getMappedStatementNames()) {
-
System.out.println(name);
-
}
-
return "刷新mybatis xml配置语句成功";
-
} catch (Exception e) {
-
e.printStackTrace();
-
return "刷新mybatis xml配置语句失败";
-
}
-
}
-
-
public void setPackageSearchPath(String packageSearchPath) {
-
this.packageSearchPath = packageSearchPath;
-
}
-
-
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
-
this.sqlSessionFactory = sqlSessionFactory;
-
}
-
/**
-
* 扫描xml文件所在的路径
-
* @throws IOException
-
*/
-
private void scanMapperXml() throws IOException {
-
this.mapperLocations = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
-
}
-
/**
-
* 清空Configuration中几个重要的缓存
-
* @param configuration
-
* @throws Exception
-
*/
-
private void removeConfig(Configuration configuration) throws Exception {
-
Class<?> classConfig = configuration.getClass();
-
clearMap(classConfig, configuration, "mappedStatements");
-
clearMap(classConfig, configuration, "caches");
-
clearMap(classConfig, configuration, "resultMaps");
-
clearMap(classConfig, configuration, "parameterMaps");
-
clearMap(classConfig, configuration, "keyGenerators");
-
clearMap(classConfig, configuration, "sqlFragments");
-
clearSet(classConfig, configuration, "loadedResources");
-
}
-
@SuppressWarnings("rawtypes")
-
private void clearMap(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
-
Field field = classConfig.getDeclaredField(fieldName);
-
field.setAccessible(true);
-
Map mapConfig = (Map) field.get(configuration);
-
mapConfig.clear();
-
}
-
@SuppressWarnings("rawtypes")
-
private void clearSet(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
-
Field field = classConfig.getDeclaredField(fieldName);
-
field.setAccessible(true);
-
Set setConfig = (Set) field.get(configuration);
-
setConfig.clear();
-
}
-
-
/**
-
* 判断文件是否发生了变化
-
* @param resource
-
* @return
-
* @throws IOException
-
*/
-
private boolean isChanged() throws IOException {
-
boolean flag = false;
-
for (Resource resource : mapperLocations) {
-
String resourceName = resource.getFilename();
-
-
boolean addFlag = !fileMapping.containsKey(resourceName);// 此为新增标识
-
-
// 修改文件:判断文件内容是否有变化
-
Long compareFrame = fileMapping.get(resourceName);
-
long lastFrame = resource.contentLength() + resource.lastModified();
-
boolean modifyFlag = null != compareFrame && compareFrame.longValue() != lastFrame;// 此为修改标识
-
-
// 新增或是修改时,存储文件
-
if(addFlag || modifyFlag) {
-
fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值
-
flag = true;
-
}
-
}
-
return flag;
-
}
-
}
注意事项:
在我的应用中,mybatis配置文件放在这里的:classpath*:**/mappers/*.xml, 因此我定义死了,需要修改成自己的路径.
测试方法:
可以通过controller提供的地址,直接在浏览器上输入url访问, 比如: http://localhost:8080/sql/refresh , 当然,你还可以通过js用ajax方式调用,都是可以的。
提取MyBatis中XML语法构造SQL的功能
提取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: ashiamd@foxmail.com
* @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: ashiamd@foxmail.com
* @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 ==

浙公网安备 33010602011771号