使用spring jdbc时将长(+20行sql)外部化的干净方法? [关闭]

Posted

技术标签:

【中文标题】使用spring jdbc时将长(+20行sql)外部化的干净方法? [关闭]【英文标题】:Clean way to externalize long (+20 lines sql) when using spring jdbc? [closed] 【发布时间】:2013-03-04 04:54:31 【问题描述】:

我想将我的应用程序中的一些大型查询外部化到 properties\sql\xml 文件。但是我想知道是否有人对如何以干净的方式实现这一点有一些建议。大多数结果建议使用 ORM 框架,但由于某些数据限制,这并不适用。

我查看了:Java - Storing SQL statements in an external file,但对一些查询执行此属性名 .1、.2 等,每个查询都超过 20 行,看起来并不那么干净。

【问题讨论】:

通过外部化查询可以获得什么?现在,您不必在阅读或调试代码时查看正在执行的查询,而是必须找到定义查询的文件。 另外,当查询是动态构建时,外部化查询很难使用。例如,如果您根据某些条件动态构建 where 子句,或者如果您根据用户希望它们如何排序来选择元素的顺序,或者您根据不同的配置或需要显示数据来限制列数。因此,根据我的经验,外部化查询从来都不是一个好主意。 JB Nizet,我将一无所获。但这些是我必须克服的限制。 如何在 Spring JDBC 模板上构建一个包装器,并在执行查询时将它们保存在一个文件中。最终该文件将包含所有查询,您可以使用文件内容将它们外部化。 我一直在寻找相同的解决方案,但我可以说有收获。在 SQL 编辑器中编写和测试查询,然后将其带到 Java 并必须将其包装为字符串,只是为了以后必须解包和重新缩进以进行调试,这很痛苦。我知道随着代码的成熟,或者如果它是动态构建的查询,外部化的用处会降低,但我确实认为在 Java 代码之外进行查询会使其更简洁。 【参考方案1】:

你可以把你的查询放在一个xml文件中

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

<properties>
<entry key="getPersonById">
    <![CDATA[
        Select Name From Person 
        Where Id =?     
    ]]>

</entry>    
<entry key="getPersonBySSN">
    <![CDATA[

    ]]>
</entry>

</properties>

在Spring应用上下文中,加载这个xml文件

<bean id="queryProps" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="locations" value="classpath:/queries.xml" />
</bean>

在你的 DAO 类中注入这个 bean

<bean id="myDAO" class="com.xyz.dao.MyDAOImpl">
  <property name="queryProps" ref="queryProps" />
</bean>

在你的 DAO 类中定义 queryProps,不要忘记为此设置 setter 方法

 private Properties queryProps;

现在您可以像这样在您的 DAO 中访问查询 -

 String query = queryProps.getProperty("getPersonById");

希望这会有所帮助。

【讨论】:

此解决方案在 DBA 应用他/她的更新然后将其保存回文件的情况下非常有用。当然,您可以为新的更新使用某种更改侦听器,并将 XML 属性文件部署在 WEB-INF 之外,以及另一个对您的应用程序可见的属性文件,该文件读取 XML 属性文件所在的位置。 它在没有 CDATA 的情况下对我有用。 这很酷并且可以解决问题,但我宁愿每个查询都有一个 .sql 文件,并将它们与运行它们的 Java 代码一起存储。而且我认为从 Java 代码加载查询文件会更好,避免 XML 文件中的 Spring 配置。这可能吗? 您好,您知道如何使用 java Config(带有 springboot 注释)做同样的事情吗?试过了但是对Spring不太了解。。直接用springboot开始学习了。。 这个例子很完美。我只有一件事。如何使用表名解析器解析表名(自定义 java Util 类,当表的短名称作为输入传递时,返回带有模式的完整表名。)?【参考方案2】:

我前段时间遇到了同样的问题,并提出了 YAML。它支持多行字符串属性值,因此您可以在查询文件中编写如下内容:

selectSomething: >
  SELECT column1, column2 FROM SOMETHING

insertSomething: >
  INSERT INTO SOMETHING(column1, column2)
  VALUES(1, '1')

这里,selectSomethinginsertSomething 是查询名称。所以它真的很方便,并且包含很少的特殊字符。查询以空行分隔,每个查询文本必须缩进。请注意,查询绝对可以包含自己的缩进,因此以下内容完全有效:

anotherSelect: <
  SELECT column1 FROM SOMETHING
  WHERE column2 IN (
    SELECT * FROM SOMETHING_ELSE
  )

然后,您可以在 SnakeYAML 库的帮助下,使用以下代码将文件的内容读入哈希映射:

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.FileUtils;
import java.io.FileReader;

import org.yaml.snakeyaml.Yaml;
import java.io.File;
import java.io.FileNotFoundException;

public class SQLReader 
  private Map<String, Map> sqlQueries = new HashMap<String, Map>();

  private SQLReader() 
    try 
      final File sqlYmlDir = new File("dir_with_yml_files");
      Collection<File> ymlFiles = FileUtils.listFiles(sqlYmlDir, new String[]"yml", false);
      for (File f : ymlFiles) 
        final String fileName = FilenameUtils.getBaseName(f.getName());
        Map ymlQueries = (Map)new Yaml().load(new FileReader(f));
        sqlQueries.put(fileName, ymlQueries);
      
    
    catch (FileNotFoundException ex) 
      System.out.println("File not found!!!");
    
  

在上面的示例中,创建了一个映射映射,将每个 YAML 文件映射到一个包含查询名称/字符串的映射。

【讨论】:

一个干净的解决方案。您可以将它与 Spring NamedParameterJdbcTemplate 一起使用,以使代码更具可读性。它允许您使用命名参数,例如 Hibernate(例如,其中 dt >= :startDt) 如果我的 yml 文件位于“/resources/queries”中,“dir_with_yml_files”的正确字符串是什么?【参考方案3】:

这是对Pankaj has answered 的补充。这个在属性 XML 中没有 CDATA 并使用自动装配。我必须将此添加为答案,因为如果我必须在评论部分执行此操作,我将无法格式化代码。

确保您在 spring 应用程序上下文 xml 文件中有以下命名空间。

xmlns:util="http://www.springframework.org/schema/util

将以下bean添加到spring应用程序上下文xml

<util:properties id="sqls" location="classpath:oracle/sqls.xml" />

文件sqls.xml的内容是

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>Employee Queries</comment>
    <entry key="employee.insert">
        INSERT
        INTO EMPLOYEE
          (
            ID,
            NAME,
            AGE,
            DEPARTMENT
          )
        VALUES
          (
            EMPLOYEE_SEQ.NEXTVAL,
            ?,
            ?,
            ?
          )
    </entry>
</properties>

自动装配如下属性

@Autowired
@Qualifier("sqls")
private Properties sqls;

从属性获取 sql 查询的代码

String sql = sqls.getProperty("employee.insert");

【讨论】:

我认为您将需要&lt;![CDATA[ 标签 IF 您的 SQL 包含比较关键字,例如 &lt;&gt;,它们是 xml 中的保留字符。如果不包含在CDATA 中,Spring 在加载应用程序上下文时会抛出异常。 你可以使用 >对于 > 和 <对于 <.> 是的,这违背了恕我直言的目的。以这种方式外部化的一个好处是我们可以清楚地看到查询,也可以直接复制 sql 并粘贴到 sql 工具中并运行。我的 2 美分。 只是为了再补充一点,在 schemaLocation 中也有必要提及(@ArunBC 也许你可以编辑你的答案)xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.1.xsd"&gt;【参考方案4】:

您可以通过在行尾添加 \ 来在属性文件中执行多行查询。例如

queries.myquery = select \
foo, bar \
from mytable \
where baz > 10

【讨论】:

【参考方案5】:

在JdbcTestUtils 抢夺战利品 以及“executeSqlScript”和“readScript”方法。

【讨论】:

FAI(供任何人参考);从 Spring 4.0.3 开始,所有这些方法(executeSqlScriptreadScriptcontainsSqlScriptDelimiterssplitSqlScript)都被标记为已弃用;从 Spring 5.0 开始,它们都已移至 ScriptUtils 或替换为 ResourceDatabasePopulator 提供的功能。

以上是关于使用spring jdbc时将长(+20行sql)外部化的干净方法? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 在启动时将示例数据插入数据库

Spring JDBC - 针对多个表的批量插入

Spring 学习其三:数据库编程

如果 SQL :parameter 在 DB2 JDBC 中为“ALL”,如何匹配所有行?

使用spring + mybatis时,java.lang.ClassNotFoundException:com.mysql.cj.jdbc.Drive发生

Spring Boot JDBC 参数别名 SQL(NamedParameterJdbcTemplate)