【问题标题】:Java - Storing SQL statements in an external file [closed]Java - 将 SQL 语句存储在外部文件中 [关闭]
【发布时间】:2010-12-05 09:15:16
【问题描述】:

我正在寻找将 SQL 语句存储在外部文件中的 Java 库/框架/技术。支持团队(包括 DBA)应该能够(稍微)更改语句,以在数据库架构更改或出于调整目的时使它们保持同步。

以下是要求:

  • 该文件必须可以从 Java 中读取 应用程序,但也必须是可编辑的 由支持团队无需 高级编辑
  • 理想情况下,文件应该是普通的 文本格式,但 XML 也可以
  • 允许 DML 和 DDL 语句 存储/检索
  • 可以在稍后阶段添加新语句(应用程序足够灵活,可以选择并执行它们)
  • 语句可以分组(并由应用程序作为一个组执行)
  • 语句应该允许参数

注意事项:

  • 一旦检索到,语句将 使用 Spring 的 JDBCTemplate 执行
  • Hibernate 或 Spring 的 IOC 容器 不会被使用

到目前为止,我设法找到了以下 Java 库,它们使用外部文件来存储 SQL 语句。但是,我主要对存储感兴趣,而不是隐藏所有 JDBC “复杂性”的库。

  • Axamol SQL Library

    示例文件内容:

    <s:query name="get_emp">
      <s:param name="name" type="string"/>
      <s:sql databases="oracle">
        select    *
        from      scott.emp
                  join scott.dept on (emp.deptno = dept.deptno)
        where     emp.ename = <s:bind param="name"/>
      </s:sql>
    </s:query>
    
  • iBATIS

    示例文件内容:

    <sqlMap namespace="Contact"">
        <typeAlias alias="contact"
            type="com.sample.contact.Contact"/">
        <select id="getContact"
            parameterClass="int" resultClass="contact"">
                select CONTACTID as contactId,
                       FIRSTNAME as firstName,
                       LASTNAME as lastName from
                       ADMINISTRATOR.CONTACT where CONTACTID = #id#
        </select>
    </sqlMap>
    <insert id="insertContact" parameterClass="contact">
    INSERT INTO ADMINISTRATOR.CONTACT( CONTACTID,FIRSTNAME,LASTNAME)
            VALUES(#contactId#,#firstName#,#lastName#);
     </insert>
    <update id="updateContact" parameterClass="contact">
    update ADMINISTRATOR.CONTACT SET
    FIRSTNAME=#firstName# ,
    LASTNAME=#lastName#
    where contactid=#contactId#
    </update>
    <delete id="deleteContact" parameterClass="int">
    DELETE FROM ADMINISTRATOR.CONTACT WHERE CONTACTID=#contactId#
    </delete>
    
  • WEB4J

    -- This is a comment 
     ADD_MESSAGE   {
     INSERT INTO MyMessage -- another comment
      (LoginName, Body, CreationDate)
      -- another comment
      VALUES (?,?,?)
     }
    
    -- Example of referring to a constant defined above.
    FETCH_RECENT_MESSAGES {
     SELECT 
     LoginName, Body, CreationDate 
     FROM MyMessage 
     ORDER BY Id DESC LIMIT ${num_messages_to_view}
    }
    

谁能推荐一个经过试验和测试的解决方案?

【问题讨论】:

  • 您确定要将所有 SQL 集中到支持团队可以编辑的文件中吗?他们能把东西弄坏到什么程度?
  • 一旦部署,除非发现重大错误,否则应用程序不太可能更改。支持团队不具备修补复杂 Java 应用程序的专业知识,因为他们知道如何修补 SQL 语句。通常他们需要进行 DBA 类型更改而不是实际的应用程序更改
  • WEB4J 示例格式不正确,有编辑权限的人可以解决这个问题吗?
  • 认为 Java 代码不会改变是一种错觉。如果您更改查询,您可能必须更改使用结果的代码。
  • SQLind 提供了一种 xml 模板化的方式来从 java 代码中外部化 SQL。

标签: java jdbc sql


【解决方案1】:

在这里粘贴我的answerClean way to externalize long (+20 lines sql) when using spring jdbc?

我前段时间遇到了同样的问题,并提出了 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 文件映射到一个包含查询名称/字符串的映射。

【讨论】:

  • 如果你不打算使用一个完整的库,这个答案是正确的。非常干净优雅。
【解决方案2】:

ElSql 库提供此功能。

ElSql 由一个小 jar 文件(六个公共类)组成,允许加载外部 SQL 文件 (elsql)。该文件使用一种简单的格式来选择性地提供比简单地加载文件更多的行为:

-- an example comment
@NAME(SelectBlogs)
  @PAGING(:paging_offset,:paging_fetch)
    SELECT @INCLUDE(CommonFields)
    FROM blogs
    WHERE id = :id
      @AND(:date)
        date > :date
      @AND(:active)
        active = :active
    ORDER BY title, author
@NAME(CommonFields)
  title, author, content

// Java code:
bundle.getSql("SelectBlogs", searchArgs);

文件被分成@NAME 块,可以从代码中引用。每个块由重要的空白缩进定义。 @PAGING 将插入必要的分页代码,例如 FETCH/OFFSET。 @AND 仅在指定变量存在时才会输出(帮助构建动态搜索)。 DSL 还处理搜索中的通配符 LIKE=。可选 DSL 标记的目标是提供在尝试以与数据库无关的方式构建动态 SQL 时经常遇到的常见基础知识。

有关bloguser guide 的更多信息。

【讨论】:

    【解决方案3】:

    dynamic-query 是一个很好的开源框架,适合那些想要介于 JDBC 和 ORM 之间的人。

    1个普通的SQL。 - 它将纯 sql 保存到外部文件。没有多余的标签,支持 cmets。

    /* It also supports comment.
    This code is in an external file 'sample.sql', Not inisde java code.*/
    listUsers : select * from user_table
    where user_id= $$;  /* $$ will automatically catch a parameter userId */
    


    2 可扩展的 SQL。 -支持参数,包括其他文件和子查询。

    listUsers:
    select
        id, amount, created
        @checkEmail{ ,email } 
    from user_table
    where amount > $amt and balance < $amt
        @checkDate { and created = $$ }
        @checkEmail{ and email in (
            select email from vip_list ) } ;        
    /* Above query can be four queries like below.
    1. listUsers
    2. listUsers.checkDate 
    3. listUsers.checkEmail
    4. listUsers.checkDate.checkEmail 
    */
    
    
    
    -- It can include other files like below
    & ../hr/additional hr.sql ; 
    & ../fi/additional fi.sql ;
    


    使用上面的 Java 示例代码。将值设置为 db。

    QueryUtil qu = qm.createQueryUtil("selectAll");
    try {
        qu.setConnection(conn);
    
        // with native jdbc
        qu.setString("alpha");
        qu.setDouble(10.1);
        qu.executeQuery();
    
        // or with bean
        qu.executeQuery(new User("alpha", 10.1));
    
        // or with map
        Map<String, Object> map=new HashMap<String, Object>();
        map.put("userName", "alpha");
        map.put("amt", 10.1);
        qu.executeQuery(map);
    
        // or with array
        qu.executeQueryParameters("alpha", 10.1);
    

    使用上面的 Java 示例代码。从 db 获取值。

        while (qu.next()) // == qu.rs.next()
        {
            // native jdbc
            String usreName = qu.getString("user_name"); 
            double amt = qu.getDouble("amt");
    
            // or bean
            User user = new User();
            qu.updateBean(user);
    
            // or array
            Object[] values = qu.populateArray();
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        qu.closeJust();
    }
    

    【讨论】:

      【解决方案4】:

      如果您必须这样做,您应该查看MyBatis 项目。没用过,但听过很多次推荐了。

      分离 SQL 和 Java 不是我最喜欢的方法,因为 SQL 实际上是代码,并且与调用它的 Java 代码紧密耦合。维护和调试分离的代码可能具有挑战性。

      绝对不要为此使用存储过程。它们只能用于通过减少数据库和应用程序之间的流量来提高性能。

      【讨论】:

      • 我对您关于 Java 和 SQL 紧密耦合的说法感到困惑。唯一的耦合应该是查询中列的名称。
      • iBATIS 是我正在考虑的一种选择;它有点复杂,但它符合大多数情况
      • “分离 SQL 和 Java 不是我最喜欢的方法......”我假设你不同意这篇文章中的陈述“让 SQL 远离代码”javapractices.com/topic/TopicAction.do?Id=105
      • 我也会推荐 iBatis;新开发人员通常需要几分钟才能开始使用它。 (当然,假设您已经在使用它;将其引入新项目通常需要更多的工作,具体取决于项目本身。)
      • Adrian - 您链接到的文章将 SQL 代码等同于元数据,我认为这是一种错误的描述。我的首选方法是将所有 SQL 隔离到一组 Java 数据访问对象 (DAO) 类中,这些类负责在数据库和 Java 对象模型之间进行转换,并且不允许包含任何额外的逻辑。现在您仍然将所有 SQL 代码放在一个定义明确的位置(一组 java 文件),另外还有一个好处是拥有使用查询的所有适当上下文。我可以继续,但我的字符用完了......
      【解决方案5】:

      使用 Spring 中的类既简单又可靠。获取您的 SQL 文件并将它们保存在类路径中的某个位置。如果需要,它可以在只包含 SQL 的 JAR 文件中。然后使用 Spring 的 ClassPathResource 将文件加载到流中,并使用 Apache IOUtils 将其转换为 String。然后,您可以使用 SimpleJdbcTemplate 或您选择的 DB 代码执行 SQL。

      我建议您创建一个实用程序类,它采用一个简单的 Java 类,该类具有公共字符串字段,这些字段对应于您选择的约定的 SQL 文件名。然后将反射与 ClassPathResource 类结合使用,以查找符合您的命名约定的 SQL 文件并将它们分配给 String 字段。之后,当您需要 SQL 时,只需参考类字段。它很简单,效果很好,并且可以实现您想要的目标。它还使用陈旧的课程和技术。没有什么花哨。几年前我做到了。效果很好。懒得去拿代码了。您将没有时间自己弄清楚。

      【讨论】:

        【解决方案6】:

        您可以使用 Spring 并将 sql 语句存储在 bean 文件中,当您从 bean 工厂获取类时注入这些语句。该类还可以使用 SimpleJDBCTemplate 的实例,该实例可以通过 bean 文件进行配置,以帮助简化代码。

        【讨论】:

          【解决方案7】:

          您可以使用velocity 拥有“可编写脚本的” sql 模板,您可以使用这些模板以灵活的方式处理文件。您可以使用条件和循环等原始语句来构建您的 sql 命令。

          但我强烈建议使用准备好的语句和/或存储过程。 以您计划的方式构建 SQL 会使您容易受到 SQL 注入的攻击,数据库服务器将无法缓存 sql 查询(这将导致性能下降)。

          顺便说一句:您也可以将准备好的语句的定义存储在文件中。这不是最好的解决方案,但非常接近它,您可以获得 SQL 注入保护和性能的好处。

          当您的 SQL 架构不是为使用准备好的语句或存储过程而构建时,您可能需要重新考虑您的架构。也许它需要重构。

          【讨论】:

          • 应用程序将建立在旧数据库上,因此架构重构是不可能的。此外,使用存储过程不是一个公认的解决方案(可以说,它们可能是一个好主意)。缓存(幸运的是)不是主要问题,每条语句每天执行一次(它是一个批处理应用程序)
          • 好的,那么我会选择 Velocity。恕我直言,它足够灵活,可以稍微“编写”脚本,而不是那么灵活,它本身就是一种编程语言。
          【解决方案8】:

          您还可以使用Apache Commons DbUtils 中的QueryLoader 类,它将从属性文件中读取sql。但是,您将不得不使用 DbUtils,它的用途与 JDBCTemplate 相同。

          【讨论】:

            【解决方案9】:

            您可以使用本地化工具来执行此操作。然后,您使用数据库名称作为语言环境来获取“insert-foo-in-bar”的“oraclish”版本,而不是英语或法语版本。

            翻译通常存储在属性文件中,并且有很好的工具可以通过允许编辑这些属性文件来本地化应用程序。

            【讨论】:

            【解决方案10】:

            在遇到这种情况时,我们实施的一个简单解决方案是将 SQL/DML 外部化到一个文件 (mySql.properties) 中,然后使用 MessageFormat.format(String[] args) 将动态属性注入 SQL。

            例如: mySql.properties:

            select    *
                from      scott.emp
                          join scott.dept on (emp.deptno = dept.deptno)
                where     emp.ename = {0}
            

            实用方法:

            public static String format(String template, Object[] args) {
                String cleanedTemplate = replaceSingleQuotes(template);
                MessageFormat mf = new MessageFormat(cleanedTemplate);
                String output = mf.format(args);
                return output;
            }
            private static String replaceSingleQuotes(String template) {
                String cleaned = template.replace("'", "''");
                return cleaned;
            }
            

            然后像这样使用它:

            String sqlString = youStringReaderImpl("/path/to/file");
            String parsedSql = format(sqlString, new String[] {"bob"});
            

            【讨论】:

            • 我假设您想在“mySql.properties”文件中存储多个语句。你如何识别它?另外,如果你想将多个语句分组,你会建议什么方法?
            • 我们实际上为每个 sql/dml 语句存储了一个属性文件,并使用 spring 将文件的映射注入到负责加载文件的组件中。
            【解决方案11】:

            只需创建一个简单的 Java 属性文件,其中包含这样的键值对:

            users.select.all = select * from user
            

            在您的 DAO 类中声明一个 Properties 类型的私有字段,并使用 Spring 配置将其注入,该配置将从文件中读取值。

            UPDATE:如果您想在多行中支持 SQL 语句,请使用此表示法:

            users.select.all.0 = select *
            users.select.all.1 = from   user
            

            【讨论】:

            • +1 表示简单但有效,并且可以很好地与准备好的语句一起使用 (java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html)
            • 属性文件是一个不错的选择,我同意。支持团队易于实施和编辑。但是,有几点我并不完全满意,即“每行一个语句”可能意味着长语句会使文件难以查看/编辑
            • 您应该能够在行尾使用 \ 以允许内容跨越多行。
            • 我已经使用存储在 XML 文件中的属性来实现这个目的。它具有简单的语法(properties 元素作为根,entry 元素和key 属性作为子元素),可以像普通属性一样简单地加载(通过使用 loadFromXML 方法而不是 load 方法),并且运行良好。只有一个小问题是 字符的编码,它们必须写成 XML 中的实体(<、>)
            • @PeterŠtibraný 您可以将整个条目放在 CDATA 元素中,因此您不必为 XML 实体而烦恼
            【解决方案12】:

            我强烈建议您使用存储过程。这种事情正是他们的目的。

            【讨论】:

            • 我不能再反对了。存储过程使您的业务逻辑与 Java 代码分离。存储过程是用与数据库供应商紧密耦合的 plsql 代码编写的,没有适当的重构工具,更常见的是没有 OOP 支持等。仅将存储过程用于性能优化。
            • 只有在使用 Oracle 时才用 plsql 编写有用的存储过程。通常有用的存储过程是用 SQL 编写的,可以很容易地移植到另一个平台。
            • +1 - 将我们所有的 SQL 放入 (Oracle) 包中非常棒。它将所有查询保存在一个不错的位置,并允许 DB 团队进行操作,而不必担心重新编译应用程序。
            • 这个项目将再次运行 Ingres 数据库; Ingres 支持存储过程,所以这不是问题。但其中一项要求是不要使用存储过程。
            • 这绝对不是一个奇怪的限制。使用存储过程,所有业务逻辑都在数据库机器上运行(因为它是一台大机器,所以不容易扩展)。使用 SQL 查询,业务逻辑是用 Java 编码的(您可以从所有语言功能中受益)并在应用程序服务器上运行,与数据库服务器相比,它的扩展性更好且成本更低。对我来说,不使用存储过程实际上是一个很好的限制。
            猜你喜欢
            • 1970-01-01
            • 2012-01-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-03-10
            • 2010-10-19
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多