Java – 在外部文件中存储SQL语句
我正在寻找将SQL语句存储在外部文件中的Java库/框架/技术。 支持团队(包括DBA)应该能够更改(轻微)语句,以便在数据库模式更改或调整目的时保持同步。
这是要求:
- 该文件必须可以从Java应用程序读取,但也必须由支持团队进行编辑,而无需花哨的编辑
- 理想情况下,文件应该是纯文本格式,但XML也可以
- 允许DML以及DDL语句被存储/检索
- 新的语句可以在稍后的阶段添加(应用程序足够灵活,可以将其选中并执行)
- 语句可以分组(并由应用程序作为一个组来执行)
- 陈述应该允许参数
笔记:
- 一旦检索到,语句将使用Spring的JDBCTemplate执行
- Hibernate或Spring的IOC容器将不会被使用
到目前为止,我设法find以下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>
-
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} }
任何人都可以推荐一个经过testing的解决scheme吗?
只需使用如下所示的键值对创build一个简单的Java属性文件即可:
users.select.all = select * from user
在你的DAO类中声明一个属性types的私有字段,并使用Springconfiguration注入它,该configuration将读取文件中的值。
更新 :如果你想在多行中支持SQL语句,请使用下面的表示法:
users.select.all.0 = select * users.select.all.1 = from user
如果你必须这样做,你应该看看MyBatis项目。 我没有用过,但听说过它推荐了好几次。
分离SQL和Java并不是我最喜欢的方法,因为SQL实际上是代码,并与调用它的Java代码紧密耦合。 维护和debugging分离的代码可能具有挑战性。
绝对不要使用这个存储过程。 它们只能用于通过减less数据库和应用程序之间的stream量来提高性能。
我们在面对这个问题时已经实现了一个简单的解决scheme,即将SQL / DML外部化为一个文件(mySql.properties),然后使用MessageFormat.format(String [] args)将dynamic属性注入到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"});
我强烈build议您使用存储过程。 这种事情正是他们所为。
您也可以使用Apache Commons DbUtils中的QueryLoader类,它将从属性文件中读取sql。 但是,您将不得不使用DbUtils,它与JDBCTemplate具有相同的用途。
ElSql库提供了这个function。
ElSql由一个允许加载外部SQL文件(elsql)的小型jar文件(六个公共类)组成。 该文件使用简单的格式来select性地提供比简单加载文件更多的行为:
-- 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。 只有指定的variables存在时才会输出@AND
(帮助构builddynamicsearch)。 DSL还在search中处理LIKE
vs =
通配符。 可选的DSL标签的目标是提供在尝试以数据库中立的方式构builddynamicSQL时经常遇到的常见基础知识。
有关博客或用户指南的更多信息。
在这里粘贴我的答案 干净的方式来外化长(+20行SQL)使用弹簧jdbc时? :
前段时间我面临同样的问题,想出了YAML。 它支持多行string属性值,所以你可以在你的查询文件中写这样的东西:
selectSomething: > SELECT column1, column2 FROM SOMETHING insertSomething: > INSERT INTO SOMETHING(column1, column2) VALUES(1, '1')
在这里, insertSomething
和insertSomething
是查询名称。 所以它非常方便,包含很less的特殊字符。 查询由空行分隔,并且每个查询文本都必须缩进。 请注意,查询可以绝对包含自己的缩进,因此以下内容是完全有效的:
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!!!"); } } }
在上面的例子中,创build了一个映射图,将每个YAML文件映射到包含查询名称/string的映射。
你可以使用Spring,并将你的sql语句存储在你的bean文件中,当你从你的bean工厂获得这个类的时候注入这些语句。 该类还可以使用可以通过bean文件configuration的SimpleJDBCTemplate实例来帮助简化代码。
使用Spring的类很简单,可靠。 把你的SQL文件保存在类path的某个位置。 这可以在只包含SQL的JAR文件中进行。 然后使用Spring的ClassPathResource将文件加载到stream中,并使用Apache IOUtils将其转换为string。 然后,您可以使用SimpleJdbcTemplate或您select的DB代码执行SQL。
我build议你创build一个实用工具类,它接受一个简单的Java类,它具有公共的String字段,这些字段对应于你select的约定的SQL文件名。 然后使用reflection与ClassPathResource类一起查找符合您的命名约定的SQL文件,并将它们分配给String字段。 之后,只需在需要SQL时引用类字段即可。 这很简单,很好,并达到你想要的目标。 它也使用很好的类和技术。 没有什么花哨。 几年前我做了这件事。 很好用。 懒得去拿代码。 你没有时间搞清楚自己。
您可以使用本地化设施来执行此操作。 然后使用数据库的名称作为语言环境来获取“插入foo-in-bar”的“oraclish”版本,而不是英语或法语版本。
翻译通常存储在属性文件中,通过允许编辑这些属性文件,有很好的工具来定位应用程序。
对于那些想要JDBC和ORM的人来说, dynamic查询是一个很好的开源框架。
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示例代码。 从数据库获取值。
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(); }
您可以使用velocity来具有“脚本化”的sql模板,您可以使用这些模板以灵活的方式处理这些文件。 你有像条件和循环来构build你的SQL命令的原始语句。
但我强烈build议使用准备好的语句和/或存储过程。 按照您计划的方式构buildSQL会使您容易受到SQL注入的攻击,数据库服务器将无法cachingsql查询(这将导致性能不佳)。
顺便说一句:您也可以将准备好的语句的定义存储在文件中。 这不是最好的解决scheme,但非常接近它,并获得SQL注入保护和性能的好处。
当您的SQL架构不能与预处理语句或存储过程配合使用时,您可能需要重新考虑您的架构。 也许它需要重构。