我使用DSL编写SQL的一个Java实现

1.导读

  • 什么是DSL?领域特定语言(Domain Specific language)通常被定义为一种特别针对某类特殊问题的计算机语言,它不打算解决其领域外的问题。了解更多

2.你使用JDBC来 存取 数据时,怎么处理你的SQL

2.1 对于一个固定条件的查询,我们会使用PreparedStatement来实现。就像下面这个例子,只需要DateOfBirth一个固定条件来查询。

Java代码   收藏代码
  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.         statement.setDate(1new java.sql.Date(new java.util.Date().getTime()));  
  16.   
  17.          ResultSet rs = statement.executeQuery();  
  18.   
  19.         while (rs.next()) {  
  20.   
  21.             System.out.print(rs.getString(1));  
  22.   
  23.         }  
  24.   
  25.   
  26.     } catch (SQLException e) {  
  27.   
  28.   
  29.         e.printStackTrace();  
  30.   
  31.   
  32.     }  
 

2.2 你遇到过这样的问题么?

  • 你使用JDBC来实现数据存取,如果你要实现一个复杂条件的查询,而且条件数目还不一定,这时候就很难使用PreparedStatement来解决了,因为你的SQL模板不是固定的。就像上面的这个例子,如果用户可能要使用DateOfBirth或者Name作为条件查询,或者还有更多的条件。

2.3 这个问题可以怎么解决呢?

  • 你当然可以使用简单的字符串拼接,根据不同的条件拼接成不同的SQL。就像以下代码

 

Java代码   收藏代码
  1. int id = 0;  
  2.         String name = "Heis";  
  3.         String gender = "male";  
  4. String sql = "select Name from Students where id=" + id;  
  5.         if (name != null) {  
  6.             sql += " and name='" + name + "' ";  
  7.         }  
  8.         if (gender != null) {  
  9.             sql += " and gender='" + gender + "' ";  
  10.         }  
  11.         System.out.println(sql);  

  输出:

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:

Java代码   收藏代码
  1. int id = 0;  
  2.         String name = "Heis";  
  3.         String gender = "male";  
  4.         QuerySQL sql = new QuerySQL();  
  5.           
  6.         sql.select("name")  
  7.            .from("Students")  
  8.            .where("id=?"new Integer(id));  
  9.           
  10.         if (name != null) {  
  11.             sql.and("name='?'",name);  
  12.         }  
  13.           
  14.         if (gender != null) {  
  15.             sql.and("gender='?'",gender);  
  16.         }  
  17.           
  18.         System.out.println(sql.toPreparedString());  
  19.         System.out.println(sql.toString());  
 

输出:

Sql代码   收藏代码
  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实现的片段:

Java代码   收藏代码
  1. public class QuerySQL extends SQL {  
  2. public QuerySQL() {  
  3.         buffer = new StringBuffer(100);  
  4.         preBuffer = new StringBuffer(90);  
  5.     }  
  6.   
  7. public QuerySQL select(String value) {  
  8.         buffer.append(SELECT);  
  9.         preBuffer.append(SELECT);  
  10.         append(value);  
  11.         return this;  
  12.     }  
  13.   
  14. public QuerySQL and(String pattern, Object value) {  
  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 String format(String pattern, Object value) {  
  22.         if (value instanceof String) {  
  23.             String val = (String) value;  
  24.             val = SymbolUtils.filterSensitiveSQLSymbol(val);  
  25.             return StringUtils.replaceFirst(pattern, CHAR_FOR_REPLACE, val);  
  26.         } else if (value instanceof java.sql.Date) {  
  27.             Date date = DateUtils.convertToDate((java.sql.Date) value);  
  28.             return StringUtils.replaceFirst(pattern, CHAR_FOR_REPLACE,  
  29.                     DateUtils.formatDate(date));  
  30.         } else if(value instanceof Date){  
  31.             return StringUtils.replaceFirst(pattern, CHAR_FOR_REPLACE,  
  32.                     DateUtils.formatDate(value));  
  33.         }else {  
  34.             return StringUtils.replaceFirst(pattern, CHAR_FOR_REPLACE, value  
  35.                     .toString());  
  36.         }  
  37.     }  
  38.   
  39. ...  
  40. }  

 

5. 关于Insert语句

对于Insert语句,如果插入的数据非常多,涉及很多个column,insert语句就显得不是那么直观了。你甚至要数着第几个column是什么类型,要插入相应的数据类型。

Java代码   收藏代码
  1. statement = connection.prepareStatement("insert into students(id,name,gender) values(?,?,?,...?)");  
  2.             statement.setInt(1, id);  
  3.             statement.setString(2, value2);  
  4.             statement.setString(3, value3);  
  5.                         ...  
  6. statement.setString(n, valueN);  

 

经过我封装的InsertSQL类

Java代码   收藏代码
  1. InsertSQL sql=new InsertSQL();  
  2.         sql.insertInto("students")  
  3.            .value("id"new Integer(id))  
  4.            .value("name", name)  
  5.            .value("gender",gender);  
  6.           
  7.         System.out.println(sql.toPreparedString());  
  8.         System.out.println(sql.toString());  

 

输出:

Sql代码   收藏代码
  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

官方示例:

Java代码   收藏代码
  1. public void testSimpleSql() {  
  2.         final SqlString sql =  
  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.         assertEquals("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/

官方示例:

Java代码   收藏代码
  1. Integer[] numbers={541398720};  
  2. Iterable<Integer> lowNumbers=  
  3.         from("n").in(numbers).  
  4.         where(lt("n",5).  
  5.         select("n");  
  6.   
  7. System.out.println("All numbers that are less than five:")  
  8. for (Integer n: lowNumbers) {  
  9.     System.out.println(n);  
  10. }  

 

7.3 EoD SQL

描述:利用Annotation来声明SQL

官方主页:https://eodsql.dev.java.net/

官方示例:

Java代码   收藏代码
  1. public interface UserQuery extends BaseQuery {  
  2.     @Select("SELECT * FROM users WHERE id = ?1")  
  3.     public User getUserById(long id);  
  4.   
  5.     @Select("SELECT * FROM users")  
  6.     public DataSet<User> getAllUsers();  
  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 updateUser(User user);  
  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 User insertUser(User user);  
  16.   
  17. }  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值