在 SpringMVC + Spring + Hibernate 架構下,將 SQL 與 Java 代碼解耦、實現動態參數綁定,是提升維護性的關鍵。本文展示一套基於 Apache Commons Digester 3 的輕量級解決方案。
commons-digester3-3.2.jar:XML → JavaBean 映射commons-beanutils-1.9.3.jar:屬性填充輔助<?xml version="1.0" encoding="utf-8"?>
<set>
<sql name="codeType">
<pattern><![CDATA[
select A.id, A.value, A.text
from sys.code A
left join sys.codeType B on A.codeTypeId = B.id
where (case when "{0}"="" then 1=1 else B.codeType = "{0}" end)
and (case when "{1}"="" then 1=1 else A.isEnable = "{1}" end)
and (case when "{2}"="" then 1=1 else A.isDefault = "{2}" end)
]]></pattern>
<param name="codeType" position="0" />
<param name="isEnable" position="1" />
<param name="isDefault" position="2" />
</sql>
</set>
{0}、{1} 為 MessageFormat 佔位符,position 對應參數順序。
// SqlSet.java - 根節點
public class SqlSet {
private Map<String, Sql> sqls = new HashMap<>();
public void addSql(Sql sql) { sqls.put(sql.getName(), sql); }
// getter/setter
}
// Sql.java - SQL 定義
public class Sql {
private String name, pattern;
private List<SqlParam> condition = new ArrayList<>();
public void addParam(SqlParam p) { condition.add(p); }
// getter/setter
}
// SqlParam.java - 參數元數據
public class SqlParam {
private String name;
private Integer position;
// getter/setter
}
public class SqlXmlParser {
private static final String WEB_ROOT = System.getProperty("webapp.root") + "sql/";
private static final Digester DIGESTER = buildDigester();
private final String sqlXml;
private static Digester buildDigester() {
Digester d = new Digester();
d.setValidating(false);
d.addObjectCreate("set", SqlSet.class);
d.addObjectCreate("set/sql", Sql.class);
d.addSetProperties("set/sql"); // 映射 name 屬性
d.addBeanPropertySetter("set/sql/pattern");
d.addSetNext("set/sql", "addSql"); // 關聯 SqlSet
d.addObjectCreate("set/sql/param", SqlParam.class);
d.addSetProperties("set/sql/param"); // 映射 name, position
d.addSetNext("set/sql/param", "addParam");
return d;
}
public String parse(String sqlName, Map<String, String> params) {
try (InputStream is = new FileInputStream(WEB_ROOT + sqlXml)) {
SqlSet set = (SqlSet) DIGESTER.parse(is);
Sql sql = set.getSqls().get(sqlName);
List<String> args = new ArrayList<>();
for (SqlParam p : sql.getCondition()) {
String v = params.get(p.getName());
args.add(p.getPosition(), StringUtils.defaultString(v));
}
return MessageFormat.format(sql.getPattern(), args.toArray());
} catch (Exception e) {
log.error("SQL XML 解析失敗", e);
return null;
}
}
}
@Service("codeBS")
public class CodeBSImpl implements CodeBS {
private static final String SQL_XML = "xxx/code.xml";
@Autowired private CodeDao codeDao;
@Override
public List<Map<String, Object>> getValueTextListByType(String codeType) {
Map<String, String> param = new HashMap<>();
param.put("codeType", codeType);
String sql = new SqlXmlParser(SQL_XML).parse("codeType", param);
return codeDao.findBySql(sql).stream()
.map(m -> Map.of("value", m.get("value"), "text", m.get("text")))
.collect(Collectors.toList());
}
}
case when 模式實現參數為空時自動忽略條件。typeHandler、jdbcType 等屬性支援複雜類型轉換。將 SQL 外部化,讓業務代碼回歸純粹,是架構演進的必經之路。