功能:
把SQL从代码中解脱出来,修改XML文件中的SQL即时生效不需要重启服务,支持查询参数传递自动判空,参数支持SPEL表达式,功能简单,适用于简单报表场景。
特点:
1、每个查询条件用一对花括号{}括起来,语句内的参数使用SPEL表达式
2、每次查询要解析一次xml的SQL,修改SQL后不需要重启服务
3、配置一份XML报表文件即可同时支持浏览器端数据EXCEL导出,所见即所得
4、gitee完整代码地址:https://gitee.com/hhf002/rpt.git
SQL解析器部分可执行代码
package org.hhf.rpt;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.CompositeStringExpression;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author haohaifeng
* @date 2021/1/13 17:40
*/
@Slf4j
public class SQLUtils {
private static String SQL_FILE = "/rpt/RPT_I18N.xml";
/**
*
* @param obj 参数接收对象
* @return 返回解析后的sql
*/
public static String getSQL(Object obj) {
SAXReader saxReader = new SAXReader();
Document doc = null;
try {
doc = saxReader.read(SQLUtils.class.getResourceAsStream(SQL_FILE));
Element root = doc.getRootElement();
String sql = root.elementText("sql");
return parseSql(sql, obj);
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage(), e);
}
return null;
}
private static String parseSql(String sqlTemplate, Object obj) throws IllegalAccessException {
//创建表达式解析器
ExpressionParser paser = new SpelExpressionParser();
//通过evaluationContext.setVariable可以在上下文中设定变量。
EvaluationContext context = new StandardEvaluationContext();
if (obj instanceof Map) {
Map<String, Object> map = (Map) obj;
String[] sqlRow = sqlTemplate.split("\n");
List<String> sqlRowList = Arrays.stream(sqlRow).filter(n -> n.trim().startsWith("{") && n.trim().endsWith("}")).collect(Collectors.toList());
for (String row : sqlRowList) {
row = row.trim();
Expression expression = paser.parseExpression(row, new TemplateParserContext());
SpelExpression spelExpression = (SpelExpression) ((CompositeStringExpression) expression).getExpressions()[1];
String key = spelExpression.getExpressionString().replace("#", "");
Object value = map.get(key);
if (value != null && StringUtils.isNotBlank(value.toString())) {
sqlTemplate = sqlTemplate.replace(row, row.substring(1, row.length() - 1));
context.setVariable(key, "'" + value.toString() + "'");
} else {
sqlTemplate = sqlTemplate.replace(row, "");
}
}
} else {
log.error("参数类型传递异常");
}
//解析表达式,如果表达式是一个模板表达式,需要为解析传入模板解析器上下文。
Expression expression = paser.parseExpression(sqlTemplate, new TemplateParserContext());
//使用Expression.getValue()获取表达式的值,这里传入了Evalution上下文,第二个参数是类型参数,表示返回值的类型。
String result = expression.getValue(context, String.class);
return result;
}
public static void main(String[] args) {
Map params = new HashMap(1);
params.put("msg", "异常");
String sql = getSQL(params);
log.info(sql);
}
}
xml模板
<?xml version="1.0" encoding="UTF-8"?>
<RPT_I18N show="true">
<rptName>自定义问卷列表</rptName>
<colNames>ID,资源包,语言,键,值,排序,是否有效</colNames>
<sql>
<![CDATA[
SELECT
id,
bundle,
locale,
`code`,
msg,
sort,
state
FROM t_i18n
where 1=1
{ and msg like concat('%',#{#msg},'%')}
]]>
</sql>
</RPT_I18N>
依赖参考
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>