Heis的Blog

          保持簡單,保持愚蠢
          隨筆 - 29, 文章 - 1, 評論 - 122, 引用 - 0
          數據加載中……

          我使用DSL編寫SQL的一個Java實現

          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(
          1new 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={541398720};
          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);
           
           }









          程序員的一生其實可短暫了,這電腦一開一關,一天過去了,嚎;電腦一開不關,那就成服務器了,嚎……

          posted on 2010-03-21 23:41 Heis 閱讀(4950) 評論(2)  編輯  收藏 所屬分類: 雜七雜八

          評論

          # re: 我使用DSL編寫SQL的一個Java實現  回復  更多評論   

          創意不錯
          2010-03-22 17:48 | 隔葉黃鶯

          # re: 我使用DSL編寫SQL的一個Java實現  回復  更多評論   

          這個正是我想找的啦。呵呵
          2010-03-23 15:16 | fantasy
          主站蜘蛛池模板: 神农架林区| 周口市| 颍上县| 乐陵市| 潼关县| 交口县| 东兴市| 北碚区| 江北区| 西华县| 枣强县| 银川市| 宜宾县| 东明县| 青铜峡市| 双峰县| 皮山县| 建瓯市| 尖扎县| 平阳县| 雷波县| 鸡西市| 潜江市| 石阡县| 苏州市| 沅江市| 若羌县| 洪洞县| 芦溪县| 尤溪县| 成武县| 彭州市| 湄潭县| 龙陵县| 黔西县| 漳州市| 连山| 兴义市| 竹溪县| 土默特左旗| 大竹县|