1.导读
- 什么是DSL?领域特定语言(Domain Specific language)通常被定义为一种特别针对某类特殊问题的计算机语言,它不打算解决其领域外的问题。了解更多
2.你使用JDBC来
存取
数据时,怎么处理你的SQL
2.1 对于一个固定条件的查询,我们会使用PreparedStatement来实现。就像下面这个例子,只需要DateOfBirth一个固定条件来查询。
PreparedStatement statement = null;
try {
Connection connection = getConnection();
statement = connection.prepareStatement(
"SELECT Name" +
" FROM Students" +
" WHERE DateOfBirth < ?");
statement.setDate(1, new java.sql.Date(new java.util.Date().getTime()));
ResultSet rs = statement.executeQuery();
while (rs.next()) {
System.out.print(rs.getString(1));
}
} catch (SQLException e) {
e.printStackTrace();
}
2.2 你遇到过这样的问题么?
- 你使用JDBC来实现数据存取,如果你要实现一个复杂条件的查询,而且条件数目还不一定,这时候就很难使用PreparedStatement来解决了,因为你的SQL模板不是固定的。就像上面的这个例子,如果用户可能要使用DateOfBirth或者Name作为条件查询,或者还有更多的条件。
2.3 这个问题可以怎么解决呢?
- 你当然可以使用简单的字符串拼接,根据不同的条件拼接成不同的SQL。就像以下代码
int id = 0;
String name = "Heis";
String gender = "male";
String sql = "select Name from Students where id=" + id;
if (name != null) {
sql += " and name='" + name + "' ";
}
if (gender != null) {
sql += " and gender='" + gender + "' ";
}
System.out.println(sql);
输出:
select Name from Students where id=0 and name='Heis' and gender='male'
- 这样处理的缺点是很明显的。首先,敏感字符没有过滤,容易被注入攻击
;其次,代码不容易读;第三,出于debug的需要,我希望可以保留SQL模板作日志记录,而不是完整的SQL,就是希望用问号?代替真实的数据。
3. 我的解决方案
我同样在项目中遇到这样的问题,所以借助DSL的思想对SQL做了一些封装。把SQL实现为java版的DSL,这样不但不会失去SQL的简单易懂的特性,而且本来SQL就是一门DSL,实现起来不会太困难。
我实现的QuerySQL:
int id = 0;
String name = "Heis";
String gender = "male";
QuerySQL sql = new QuerySQL();
sql.select("name")
.from("Students")
.where("id=?", new Integer(id));
if (name != null) {
sql.and("name='?'",name);
}
if (gender != null) {
sql.and("gender='?'",gender);
}
System.out.println(sql.toPreparedString());
System.out.println(sql.toString());
输出:
select name from Students where id=? and name='?' and gender='?'
select name from Students where id=0 and name='Heis' and gender='male'
4. QuerySQL是怎么实现的
其实实现的原理也很简单,就是在QuerySQL的内部准备两个StringBuffer,一个用来拼接SQL模板,另一个是拼接SQL;而对于API的设计,只要在完成拼接后,返回实例本身即可。
QuerySQL实现的片段:
public class QuerySQL extends SQL {
public QuerySQL() {
buffer = new StringBuffer(100);
preBuffer = new StringBuffer(90);
}
public QuerySQL select(String value) {
buffer.append(SELECT);
preBuffer.append(SELECT);
append(value);
return this;
}
public QuerySQL and(String pattern, Object value) {
String str = format(pattern, value);
buffer.append(WS).append(AND).append(WS).append(str);
preBuffer.append(WS).append(AND).append(WS).append(pattern);
return this;
}
//format 会过滤掉value的敏感字符
protected String format(String pattern, Object value) {
if (value instanceof String) {
String val = (String) value;
val = SymbolUtils.filterSensitiveSQLSymbol(val);
return StringUtils.replaceFirst(pattern, CHAR_FOR_REPLACE, val);
} else if (value instanceof java.sql.Date) {
Date date = DateUtils.convertToDate((java.sql.Date) value);
return StringUtils.replaceFirst(pattern, CHAR_FOR_REPLACE,
DateUtils.formatDate(date));
} else if(value instanceof Date){
return StringUtils.replaceFirst(pattern, CHAR_FOR_REPLACE,
DateUtils.formatDate(value));
}else {
return StringUtils.replaceFirst(pattern, CHAR_FOR_REPLACE, value
.toString());
}
}
...
}
5. 关于Insert语句
对于Insert语句,如果插入的数据非常多,涉及很多个column,insert语句就显得不是那么直观了。你甚至要数着第几个column是什么类型,要插入相应的数据类型。
statement = connection.prepareStatement("insert into students(id,name,gender) values(?,?,?,...?)");
statement.setInt(1, id);
statement.setString(2, value2);
statement.setString(3, value3);
...
statement.setString(n, valueN);
经过我封装的InsertSQL类
InsertSQL sql=new InsertSQL();
sql.insertInto("students")
.value("id", new Integer(id))
.value("name", name)
.value("gender",gender);
System.out.println(sql.toPreparedString());
System.out.println(sql.toString());
输出:
insert into students (id,name,gender) values(?,?,?)
insert into students (id,name,gender) values('0','Heis','male')
6. 后记
如果你对于这个实现感兴趣,可以下载源代码来看。但是我不推荐你在项目中使用,因为这个实现并不完整,很多地方还欠考虑,而且我还在不断地修改。写这篇文章的目的是希望作为一个导读,让更多人可以来探讨DSL,多交流java实现的DSL。
点击下载源代码
7. 延伸阅读
7.1 JEQUEL(Java Embedded QUEry Language)
描述:比较完整的一个开源的SQL/DSL实现
官方主页:http://www.jequel.de/index.php
官方示例:
public void testSimpleSql() {
final SqlString sql =
select(ARTICLE.OID)
.from(ARTICLE, ARTICLE_COLOR)
.where(ARTICLE.OID.eq(ARTICLE_COLOR.ARTICLE_OID)
.and(ARTICLE.ARTICLE_NO.is_not(NULL)));
assertEquals("select ARTICLE.OID" +
" from ARTICLE, ARTICLE_COLOR" +
" where ARTICLE.OID = ARTICLE_COLOR.ARTICLE_OID" +
" and ARTICLE.ARTICLE_NO is not NULL", sql.toString());
}
7.2 Quaere
描述:一个类似LINQ的java实现
官方主页:http://quaere.codehaus.org/
官方示例:
Integer[] numbers={5, 4, 1, 3, 9, 8, 7, 2, 0};
Iterable<Integer> lowNumbers=
from("n").in(numbers).
where(lt("n",5).
select("n");
System.out.println("All numbers that are less than five:")
for (Integer n: lowNumbers) {
System.out.println(n);
}
7.3 EoD SQL
描述:利用Annotation来声明SQL
官方主页:https://eodsql.dev.java.net/
官方示例:
public interface UserQuery extends BaseQuery {
@Select("SELECT * FROM users WHERE id = ?1")
public User getUserById(long id);
@Select("SELECT * FROM users")
public DataSet<User> getAllUsers();
@Update("UPDATE users SET user_name = ?{1.userName}, email_address = ?{1.emailAddress} " +
"dob = ?{1.dob} WHERE id = ?{1.id}")
public void updateUser(User user);
@Update(sql = "INSERT INTO users (user_name, email_address, dob) VALUES " +
"(?{1.userName}, ?{1.emailAddress}, ?{1.dob})",
keys = GeneratedKeys.RETURNED_KEYS_FIRST_COLUMN)
public User insertUser(User user);
}
分享到:
相关推荐
2. 示例或测试代码:展示如何在实际项目中使用这个工具包,包括如何配置、如何编写SQL查询、如何在Mybatis或Spring环境中集成等。 3. 文档:可能包括README文件,介绍了如何安装、配置和使用这个工具包,以及任何...
在SQL领域,DSL-SQL允许开发者以一种更高级、更抽象的方式编写SQL语句,提高了代码的可读性和可维护性。这篇博客文章"DSL-SQL源码分析"可能探讨了如何通过源码理解DSL-SQL的工作原理以及其在实际应用中的实现。 DSL...
jOOQ 是一个内部 DSL 和源代码生成器,将 SQL 语言建模为类型安全的 Java API,以帮助您编写更好的 SQL。 其主要特点包括: 源代码生成器 用于类型安全查询构造和动态 SQL的 DSL API 次要特征包括: DAO 数据导出...
在这个项目中,我们聚焦于一个使用Java实现的DSL解释器的设计与实现,这是一份面向北京邮电大学(BUPU)学生的期末课程设计。 首先,我们要理解DSL的概念。DSL可以分为内部DSL和外部DSL。内部DSL通常嵌入到一个更大...
在Kotlin中,Exposed是一个流行的SQL DSL库,它允许开发者使用Kotlin的函数式风格编写SQL查询。Exposed提供了如下功能: 1. 表定义:开发者可以创建表示数据库表的Kotlin数据类,并通过注解指定表名和字段。 2. ...
在Java项目中,`sql-dsl-java-main`可能是一个包含核心功能的模块,提供了SQL DSL的实现和与JPA的集成。该模块可能包含了以下关键组件: 1. **DSL构建器**:提供构建SQL查询的API,如`select`, `from`, `where`, `...
另一个选择是Kotliquery,它是一个Kotlin编写的SQL查询库,允许开发者以Kotlin代码的形式编写SQL语句。它支持多种数据库引擎,提供了流式API,使得编写复杂的查询变得简单。 六、Android中的数据库实现——Room 在...
jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...
MyBatis是一个轻量级的Java持久层框架,它允许开发者通过XML或注解来编写SQL语句,实现数据库操作。而Elasticsearch则是一种分布式、RESTful风格的搜索和分析引擎,常用于大数据分析和全文检索。本文将探讨如何将...
现在,直接使用此库直接编写Sqls并将其在您的应用中使用(受yesql库的启发,对Clojure的启发很大)。该库提供了哪些优势? 分别编写带有.sql扩展名SQL文件,并通过将它们连接到数据库控制台来分别测试它们。 Sql ...
Elasticsearch-SQL是针对Elasticsearch开发的一个插件,它允许用户通过SQL语句来查询Elasticsearch中的数据,极大地降低了使用Elasticsearch的门槛,特别是对于熟悉SQL语法的开发者来说,这是一个非常友好的特性。...
jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...
因此,"UT_DSL_JAVA_UTL-1.1.5.zip" 包含的 "inputvalidator" 项目是一个使用Scala编写的输入验证工具,具备易读的DSL特性,可以用于各种应用的输入验证需求。如果你打算使用或研究这个项目,你可以从源码中学习如何...
jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...
jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...
jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...
jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...
jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...
jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...
jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...