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