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

Adr*_*ian 46 java sql jdbc

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

以下是要求:

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

笔记:

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

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

  • Axamol SQL库

    示例文件内容:

    <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>
    
    Run Code Online (Sandbox Code Playgroud)
  • 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>
    
    Run Code Online (Sandbox Code Playgroud)
  • 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}
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

Bor*_*vić 58

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

users.select.all = select * from user
Run Code Online (Sandbox Code Playgroud)

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

更新:如果要支持多行的SQL语句,请使用以下表示法:

users.select.all.0 = select *
users.select.all.1 = from   user
Run Code Online (Sandbox Code Playgroud)

  • 您应该能够在行的末尾使用\来允许内容遍历多行. (29认同)
  • 我已经使用存储在XML文件中的属性来实现此目的.它具有简单的语法(`properties`元素作为root,`entry`元素,`key`属性作为子元素),可以像普通的Properties一样加载(通过使用loadFromXML方法而不是load方法),并且它完美地工作.只有一个小问题是<,>字符的编码,必须用XML格式写成实体(&lt;,&gt;) (7认同)
  • @PeterŠtibraný您可以将整个条目放在CDATA元素中,这样您就不必费心使用XML实体了 (6认同)
  • +1表示简单但有效,并且可以很好地处理准备好的语句(http://java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html) (2认同)
  • 我同意,属性文件是一个不错的选择.支持团队易于实施且易于编辑.但是,有几点我不完全满意,"每行一个语句"可能意味着长语句会使文件难以查看/编辑 (2认同)

Joh*_*fer 8

如果必须这样做,您应该查看MyBatis项目.我没有用它,但已经多次听过它.

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

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

  • 你对Java和SQL紧密耦合的说法感到困惑.唯一的耦合应该是查询中列的名称. (4认同)
  • Adrian - 您链接到的文章将 SQL 代码与元数据等同起来,我认为这是一种错误的表征。我的首选方法是将所有 SQL 隔离到一组 Java 数据访问对象 (DAO) 类中,这些类负责在数据库和 Java 对象模型之间进行转换,并且不允许包含任何额外的逻辑。现在,您*仍然* 在一个明确定义的位置(一组 java 文件)中拥有所有 SQL 代码,并且具有使用查询的所有适当上下文的额外好处。我可以继续,但我没有字符...... (2认同)

Ric*_*oll 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}
Run Code Online (Sandbox Code Playgroud)

实用方法:

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;
}
Run Code Online (Sandbox Code Playgroud)

然后像这样使用它:

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

  • 我假设你想在"mySql.properties"文件中存储多个语句.你如何识别它?另外,如果要对多个语句进行分组,您会建议采用哪种方法? (2认同)

sip*_*uel 7

粘贴在这里我的答案,以清洁的方式来外部长(+20行SQL)使用了Spring JDBC是什么时候?:

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

selectSomething: >
  SELECT column1, column2 FROM SOMETHING

insertSomething: >
  INSERT INTO SOMETHING(column1, column2)
  VALUES(1, '1')
Run Code Online (Sandbox Code Playgroud)

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

anotherSelect: <
  SELECT column1 FROM SOMETHING
  WHERE column2 IN (
    SELECT * FROM SOMETHING_ELSE
  )
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用以下代码在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!!!");
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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


Ken*_*Liu 5

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


Jod*_*hen 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);
Run Code Online (Sandbox Code Playgroud)

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

有关博客用户指南的更多信息.