我正在寻找一种在外部文件中存储SQL语句的Java库/框架/技术。支持团队(包括DBA)应该能够(略微)更改语句,以便在数据库架构更改或调整目的时保持同步。
以下是要求:
注意:
到目前为止,我设法找到以下Java库,它们使用外部文件存储SQL语句。但是,我主要对存储而不是隐藏所有JDBC“复杂性”的库感兴趣。
示例文件内容:
<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>
示例文件内容:
<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>
-- 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}
}
任何人都可以推荐经过试用和测试的解决方案吗?
答案 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')
此处,selectSomething
和insertSomething
是查询名称。所以它非常方便,并且包含很少的特殊字符。查询由空行分隔,每个查询文本必须缩进。请注意,查询绝对可以包含自己的缩进,以便以下内容完全有效:
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时经常遇到的常见基础。
有关blog或user 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架构未构建为使用预准备语句或存储过程,则可能需要重新考虑架构。也许它需要重构。