1.导读
- 什么是DSL?领域特定语言(Domain Specific language)通常被定义为一种特别针对某类特殊问题的计算机语言,它不打算解决其领域外的问题。
2.你使用JDBC来 存取 数据时,怎么处理你的SQL
2.1 对于一个固定条件的查询,我们会使用PreparedStatement来实现。就像下面这个例子,只需要DateOfBirth一个固定条件来查询。
1. PreparedStatement statement = null;
2.
3. try
4.
5. Connection connection = getConnection();
6.
7. statement = connection.prepareStatement(
8.
9. "SELECT Name"
10.
11. " FROM Students"
12.
13. " WHERE DateOfBirth < ?");
14.
15. 1, new java.sql.Date(new
16.
17. ResultSet rs = statement.executeQuery();
18.
19. while
20.
21. 1));
22.
23. }
24.
25.
26. catch
27.
28.
29. e.printStackTrace();
30.
31.
32. }
2.2 你遇到过这样的问题么?
• 你使用JDBC来实现数据存取,如果你要实现一个复杂条件的查询,而且条件数目还不一定,这时候就很难使用PreparedStatement来解决了,因为你的SQL模板不是固定的。就像上面的这个例子,如果用户可能要使用DateOfBirth或者Name作为条件查询,或者还有更多的条件。
2.3 这个问题可以怎么解决呢?
• 你当然可以使用简单的字符串拼接,根据不同的条件拼接成不同的SQL。就像以下代码
1. int id = 0;
2. "Heis";
3. "male";
4. String sql = "select Name from Students where id="
5. if (name != null) {
6. " and name='" + name + "' ";
7. }
8. if (gender != null) {
9. " and gender='" + gender + "' ";
10. }
11. System.out.println(sql);
输出:
1. 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:
1. int id = 0;
2. "Heis";
3. "male";
4. new
5.
6. "name")
7. "Students")
8. "id=?", new
9.
10. if (name != null) {
11. "name='?'",name);
12. }
13.
14. if (gender != null) {
15. "gender='?'",gender);
16. }
17.
18. System.out.println(sql.toPreparedString());
19. System.out.println(sql.toString());
输出:
1. select name from Students where id=? and name='?' and gender='?'
2. select name from Students where id=0 and name='Heis' and gender='male'
4. QuerySQL是怎么实现的
其实实现的原理也很简单,就是在QuerySQL的内部准备两个StringBuffer,一个用来拼接SQL模板,另一个是拼接SQL;而对于API的设计,只要在完成拼接后,返回实例本身即可。
QuerySQL实现的片段:
1. public class QuerySQL extends
2. public
3. new StringBuffer(100);
4. new StringBuffer(90);
5. }
6.
7. public
8. buffer.append(SELECT);
9. preBuffer.append(SELECT);
10. append(value);
11. return this;
12. }
13.
14. public
15. String str = format(pattern, value);
16. buffer.append(WS).append(AND).append(WS).append(str);
17. preBuffer.append(WS).append(AND).append(WS).append(pattern);
18. return this;
19. }
20. //format 会过滤掉value的敏感字符
21. protected
22. if (value instanceof
23. String val = (String) value;
24. val = SymbolUtils.filterSensitiveSQLSymbol(val);
25. return
26. else if (value instanceof
27. Date date = DateUtils.convertToDate((java.sql.Date) value);
28. return
29. DateUtils.formatDate(date));
30. else if(value instanceof
31. return
32. DateUtils.formatDate(value));
33. else
34. return
35. .toString());
36. }
37. }
38.
39. ...
40. }
5. 关于Insert语句
对于Insert语句,如果插入的数据非常多,涉及很多个column,insert语句就显得不是那么直观了。你甚至要数着第几个column是什么类型,要插入相应的数据类型。
1. statement = connection.prepareStatement("insert into students(id,name,gender) values(?,?,?,...?)");
2. 1, id);
3. 2, value2);
4. 3, value3);
5. ...
6. statement.setString(n, valueN);
经过我封装的InsertSQL类
1. InsertSQL sql=new
2. "students")
3. "id", new
4. "name", name)
5. "gender",gender);
6.
7. System.out.println(sql.toPreparedString());
8. System.out.println(sql.toString());
输出:
1. insert into students (id,name,gender) values(?,?,?)
2. 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
官方示例:
1. public void
2. final
3. select(ARTICLE.OID)
4. .from(ARTICLE, ARTICLE_COLOR)
5. .where(ARTICLE.OID.eq(ARTICLE_COLOR.ARTICLE_OID)
6. .and(ARTICLE.ARTICLE_NO.is_not(NULL)));
7.
8. "select ARTICLE.OID"
9. " from ARTICLE, ARTICLE_COLOR"
10. " where ARTICLE.OID = ARTICLE_COLOR.ARTICLE_OID"
11. " and ARTICLE.ARTICLE_NO is not NULL", sql.toString());
12. }
7.2 Quaere
描述:一个类似LINQ的java实现
官方主页:http://quaere.codehaus.org/
官方示例:
1. Integer[] numbers={5, 4, 1, 3, 9, 8, 7, 2, 0};
2. Iterable<Integer> lowNumbers=
3. "n").in(numbers).
4. "n",5).
5. "n");
6.
7. System.out.println("All numbers that are less than five:")
8. for
9. System.out.println(n);
10. }
7.3 EoD SQL
描述:利用Annotation来声明SQL
官方主页:https://eodsql.dev.java.net/
官方示例:
1. public interface UserQuery extends
2. @Select("SELECT * FROM users WHERE id = ?1")
3. public User getUserById(long
4.
5. @Select("SELECT * FROM users")
6. public
7.
8. @Update("UPDATE users SET user_name = ?{1.userName}, email_address = ?{1.emailAddress} "
9. "dob = ?{1.dob} WHERE id = ?{1.id}")
10. public void
11.
12. @Update(sql = "INSERT INTO users (user_name, email_address, dob) VALUES "
13. "(?{1.userName}, ?{1.emailAddress}, ?{1.dob})",
14. keys = GeneratedKeys.RETURNED_KEYS_FIRST_COLUMN)
15. public
16.
17. }
•
src.zip (8.8 KB)
• 下载次数: 119