Spring 项目中把属性或 SQL 语句写在 .xml 文件中

Spring 项目中把大量的 SQL 分散在 Java 代码中,无 Here Doc 的情况下用加号来连接写着实在是不爽,于是之前思考这个 Spring 项目中把 SQL 语句写在 .sql 文件中 --  把它们写在 *.sql 文件中,但是这个 *.sql 需要特定的格式来标识属性 Key
--!select.user
select id, firstname, lastname, address --!update.user
update ........
而且还需要一个额外的类 SqlPropertySourceFactory 来解析上面的 *.sql 文件, 识别出 select.user 是 Key, 紧接着后面的块是相应的属性值,用注解引用它时还有点额外的 factory 属性来配置,如
@PropertySource(value = "classpath:sql/queries.sql", factory = SqlPropertySourceFactory.class)
所以一直在思考是否能够再简单些,是否能用一个自定义的注解,如
@SqlPropertySource("classpath:sql/queries.sql")
捉摸了很久,似乎有点难度,不过再不断发掘的过程中找到了这个类 org.springframework.core.io.support.PropertiesLoaderUtils, 有下面的代码片断
 1public static Properties loadProperties(EncodedResource resource) throws IOException {
 2    Properties props = new Properties();
 3    fillProperties(props, resource);
 4    return props;
 5}
 6......
 7static void fillProperties(Properties props, EncodedResource resource, PropertiesPersister persister)
 8   throws IOException {
 9
10    InputStream stream = null;
11    Reader reader = null;
12    try {
13        String filename = resource.getResource().getFilename();
14        if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
15            stream = resource.getInputStream();
16            persister.loadFromXml(props, stream);
17        }
18        else if (resource.requiresReader()) {
19......

也就是说其实我们是可以用 XML 文件来定义属性的。

继续深挖到 run.util.spi.XmlPropertiesProvider, 这居然是来自于 rt.jar, 原来用 XML 定义属性由来已久。Spring 所用的一个实现同样是 rt.jar 中的 sun.util.xml.PlatformXmlPropertiesProvider, 看到这个类的前几行我瞬间就豁然开朗了
1private static final String PROPS_DTD_URI = "http://java.sun.com/dtd/properties.dtd";
2private static final String PROPS_DTD = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><!-- DTD for properties --><!ELEMENT properties ( comment?, entry* ) ><!ATTLIST properties version CDATA #FIXED \"1.0\"><!ELEMENT comment (#PCDATA) ><!ELEMENT entry (#PCDATA) ><!ATTLIST entry key CDATA #REQUIRED>";
3private static final String EXTERNAL_XML_VERSION = "1.0";

定义属性的 XML 该如何写已经一目了然了,关键是 http://java.sun.com/dtd/properties.dtd 给我们的定义,也可以直接抓下它的定义来
➜ / curl -L http://java.sun.com/dtd/properties.dtd
<!--
Copyright 2006 Sun Microsystems, Inc. All rights reserved.
--> <!-- DTD for properties --> <!ELEMENT properties ( comment?, entry* ) > <!ATTLIST properties version CDATA #FIXED "1.0"> <!ELEMENT comment (#PCDATA) > <!ELEMENT entry (#PCDATA) > <!ATTLIST entry key CDATA #REQUIRED>
根节点是 properties, 其下是多个 entry 定义,entry 的 key 属性即属性的 Key, entry 中是 CDATA 文本内容就是属性值。下面是一个实际的属性 XML 文件
 1<?xml version="1.0" encoding="UTF-8"?>
 2<!DOCTYPE properties PUBLIC "xml property dtd" "http://java.sun.com/dtd/properties.dtd">
 3
 4<properties>
 5    <entry key="select.user">
 6select id, firstname, lastname, address
 7  from user
 8  where id = ?
 9    </entry>
10    <entry key="update.user">
11<![CDATA[
12update user set address='<where>'
13  where id = ?
14]]>
15    </entry>
16</properties>

如果 entry 中有 XML 的特殊字符就用 <![CDATA[...]> 框起来,无 XML Entities 就无所谓了。

现在要引用定义在 XML 中的属性值就简单了,没有什么特别的,不需要额外的文件解析类,只是文件扩展名的不同而已。比如在你的 Spring 代码中使用如下注解
1@PropertySource("sql/queries.sql.xml")   //由于默认是 classpath 所以无需写成 @PropertySource("classpath:sql/queries.sql.xml")
2public class JavaConfig {
3......
4}

如此,定义在 sql/queries.sql.xml 便进到属性环境中去了,在任何一个 Spring Bean 中可以用 @Value("${select.uer}") 引用到了
1@Named
2public class UserRepository {
3
4    @Value("${select.user}")
5    private String selectUserSql;
6
7    ........

从此再也不用那个 SqlPropertySourceFactory 了。

IDE 打开 queries.sql.xml 只会以 XML 文件格式来进行语法高亮,如果想要 IDE 对其中的 SQL 语法进行高亮显示,可以修改 IDE 的配置,让 SQL 文件类型也包含 *.sql.xml 这个模式的文件名,那么每次在 IDE 中打开后缀为 *.sql.xml 都会认成是 SQL 文件,更增添了可读性。


2019-06-12

Spring 对 xml 写属性文件的支持其实是由 JDK 的 Properties 类自 JDK 1.5 开始提供的,见 Properties 的 JavaDoc。Properties 中新增了 loadFromXML(Inputstream)storeToXML(OutputStream, String, String), 来见识一下用 Properties 如何加载前方那个 sql/queries.sql.xml 配置的属性文件
1Properties properties = new Properties();
2properties.loadFromXML(Thread.currentThread().getContextClassLoader().getResourceAsStream("sql/queries.sql.xml"));
3System.out.println(properties.getProperty(""));

输出
select id, firstname, lastname, address
from user
where id = ?
永久链接 https://yanbin.blog/spring-properties-sql-in-xml-file/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。