在Java代码中借助MyBatis解析在我们常常在Mapper.xml里面写的Sql,包括支持<if,where,foreach...>语法。
ps:做这个的主要目的是我们有个纯后端BI小工具,这个工具存储了从数据源查询的Sql,然后为了提高这个sql的灵活性和复用程度就想到用MyBatis的动态Sql语法直接实现
主要借助MyBatis的两个类实现:XMLScriptBuilder 和DefaultParameterHandler来实现,前者负责解析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执行中都是调用这个类来进行参数设置的。
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的入口
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的核心代码,所以可移植性也不错,可做简要参考。