Java - 将SQL语句存储在外部文件中

时间:2009-10-09 14:57:05

标签: java jdbc sql

我正在寻找一种在外部文件中存储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}
    }
    

任何人都可以推荐经过试用和测试的解决方案吗?

12 个答案:

答案 0 :(得分:58)

只需创建一个简单的Java Properties文件,其中包含键值对,如下所示:

users.select.all = select * from user

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

UPDATE :如果您想支持多行的SQL语句,请使用以下表示法:

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

答案 1 :(得分:8)

如果必须执行此操作,则应查看MyBatis项目。我没有用它,但已多次听过它。

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

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

答案 2 :(得分:7)

我们在面对这个时实现的一个简单解决方案是将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"});

答案 3 :(得分:6)

将我的answer粘贴到Clean 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文件映射到包含查询名称/字符串的地图。

答案 4 :(得分:5)

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

答案 5 :(得分:5)

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 vs =。可选DSL标记的目标是提供在尝试以数据库中立方式构建动态SQL时经常遇到的常见基础。

有关bloguser guide的更多信息。

答案 6 :(得分:3)

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

答案 7 :(得分:2)

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

答案 8 :(得分:2)

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

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

答案 9 :(得分:1)

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

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

答案 10 :(得分:1)

dynamic-query对于那些想要JDBC和ORM之间的东西的人来说是一个很好的开源框架。

1个简单的SQL。 - 它将普通sql保存到外部文件。没有冗余标签,支持评论。

/* 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();
}

答案 11 :(得分:0)

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

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

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

如果您的SQL架构未构建为使用预准备语句或存储过程,则可能需要重新考虑架构。也许它需要重构。