`
Heis
  • 浏览: 112615 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

我使用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(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);
 
 }
 
  • src.zip (8.8 KB)
  • 下载次数: 159
分享到:
评论
7 楼 elan1986 2010-11-30  
以前大部分项目都是SQL拼接的

方法很不错,但是我又一个问题和疑惑

如果我插入的数据很多

那么我的value()不是很多吗?一层一层的!这个也是一个问题!!!

例如下面:

InsertSQL sql=new InsertSQL();  
        sql.insertInto("students")  
          value("id", new Integer(id))  
           .value("name", name)  
           .value("gender",gender)
           ....... ;//<-----很多次的value
          
        System.out.println(sql.toPreparedString());  
        System.out.println(sql.toString());  
6 楼 nighthawk 2010-11-28  
看起来挺不错的
5 楼 Heis 2010-11-28  
哈哈,很感谢隔了一段时间还有人回复。我循着这个思路做了个小工具,现在还没有正式的release,正在努力写文档和example。


项目主页:http://code.google.com/p/likesql/
我的另一篇文章,讨论PreparedStatement的缺点:http://www.iteye.com/topic/620906
4 楼 beihan007 2010-11-27  
这个思路很好。对我很有帮助
3 楼 抛出异常的爱 2010-11-26  
我刚刚看过了
mybatis
2 楼 hailiang0901 2010-11-26  
这给我很大的启示,原来一直是拼sql。很牛,楼主加油啊....
1 楼 kqy929 2010-11-25  
哇,挺好的。
我正想去写一个,简单的拼写sql太纠结了。
thx.

相关推荐

    免费开源-jOOQ 是用 Java 编写 SQL 的最佳方式

    jOOQ 是一个内部 DSL 和源代码生成器,将 SQL 语言建模为类型安全的 Java API,以帮助您编写更好的 SQL。 其主要特点包括: 源代码生成器 用于类型安全查询构造和动态 SQL的 DSL API 次要特征包括: DAO 数据导出...

    java开源包4

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    JAVA上百实例源码以及开源项目

     用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作方法及源代码 1个目标文件 摘要:Java源码,初学实例,波浪文字  Java波浪文字,一个利用...

    java-sqlService:Kotlin的一个易于使用的Sql查询运行器

    现在,直接使用此库直接编写Sqls并将其在您的应用中使用(受yesql库的启发,对Clojure的启发很大)。该库提供了哪些优势? 分别编写带有.sql扩展名SQL文件,并通过将它们连接到数据库控制台来分别测试它们。 Sql ...

    JAVA上百实例源码以及开源项目源代码

     用JAVA编写了一个小工具,用于检测当前显示器也就是显卡的显示模式,比如分辨率,色彩以及刷新频率等。 Java波浪文字制作方法及源代码 1个目标文件 摘要:Java源码,初学实例,波浪文字  Java波浪文字,一个利用...

    java开源包3

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    java开源包8

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    java开源包11

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    java开源包6

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    java开源包9

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    java开源包101

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    java开源包5

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    java开源包10

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    java开源包1

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    java开源包2

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    java开源包7

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    Java资源包01

    jActionScript 是一个使用了 JavaSWF2 的 Flash 解析器和生成器。提供了一个基于对象模型的 ActionScript 字节码,并提供了 ActionScript 字节码统计工具。 Java类重加载工具 JReloader JReloader 是一个用来重新...

    pdb:PulseDB是用Java编写的数据库映射软件库,它提供了对多种数据库实现的透明访问和操作。 PDB提供的DSL涵盖了大多数SQL功能,并允许轻松地将持久性集成到您的项目和模块中

    PulseDB是用Java编写的数据库映射软件库,它提供了对多种数据库实现的透明访问和操作。 PDB提供的DSL涵盖了大多数SQL功能,并可以轻松地将持久性集成到您的项目和模块中。 使用PDB 将以下依赖项添加到您的Maven ...

    10个精妙的Java编码佳实践

     我在编写和维护jOOQ(Java中内部DSL建模的SQL)时遇到过这些。作为一个内部DSL,jOOQ大限度的挑战了Java的编译器和泛型,把泛型,可变参数和重载结合在一起,Josh Bloch可能不会推荐的这种太宽泛的API。  让我与...

Global site tag (gtag.js) - Google Analytics