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

LikeSql——让你更优雅地写SQL

阅读更多

 

LikeSql是什么

LikeSql是我写一个开源的小框架(http://code.google.com/p/likesql/),目标是提供一个更优雅地处理SQL的一个方案,希望能够替代PreparedStatement和直接拼接SQL。虽然现在还有没有正式的release版本,我还在为此努力。

 

设计宗旨:

  • 专注于优雅地编写SQL和易读性
  • 保持细小和灵活

 

一些代码示例:

 

public void testAtStyleExpression(){
		LikeSql sql=DML.select("id,name")
		               .from("user")
		               .where(AtExp.at("(name =@name or id=@id) and gender=@gender")
		            		                       .set("id", 1)
		            		                       .set("name", "John")
		            		                       .set("gender", "M"));
		
		String eSql="SELECT id,name FROM user WHERE (name ='John' or id=1) and gender='M'";
		String eTpl="SELECT id,name FROM user WHERE (name =? or id=?) and gender=?";
		
		assertEquals(eSql,sql.toString());
		assertEquals(eTpl,sql.toTemplate());
	}
public void testInExpression(){
		LikeSql sSql=DML.select("id,name")
		    .from("user")
		    .where(QuestExp.qt("id in (?)").set(1,ArrayExp.array(new int[]{1,3,6})));
		
		String eSql="SELECT id,name FROM user WHERE id in (1,3,6)";
		String eTpl="SELECT id,name FROM user WHERE id in (?,?,?)";
		
		assertEquals(eSql,sSql.toString());
		assertEquals(eTpl,sSql.toTemplate());
	}
public void testSimpleSql(){
		String eSql="INSERT INTO users (no,id,name,gender,height) VALUES(1,15666662656565,'johny',null,195.36)";
		String eTpl="INSERT INTO users (no,id,name,gender,height) VALUES(?,?,?,?,?)";
		
		LikeSql iSql=DML.insertInto("users")
		    .value("no", 1)
		    .value("id", new BigInteger("15666662656565"))
		    .value("name","johny")
		    .value("gender",null)
		    .value("height", 195.36);
		
		assertEquals(eSql,iSql.toString());
		assertEquals(eTpl,iSql.toTemplate());
	}

 

LikeSql sql=DML.selectAll()
				               .from("user")
				               .where(QuestExp.qt("id=? AND gender=?")
						                      .set(1, 1)
						                      .set(2, 'M'));
				PreparedStatement stmt=sql.toPreStatement(con);
				System.out.println("Template:"+sql.toTemplate());
				System.out.println("SQL:"+sql.toString());
				ResultSet rs=stmt.executeQuery();
				while(rs.next()){
					System.out.println(rs.getString("name"));
				}

 

LikeSql不是什么

  • LikeSql不是ORM框架
  • LikeSql不是重复发明iBatis,Hibernate

 

为什么会有LikeSql

其实最初的想法来源于我工作经手的几个项目,项目都是采用简单的JDBC+直接拼接SQL来访问数据库。直接拼接SQL存在诸多缺点,这里就不在多论述了。主要是我在维护和改进的过程中,发现并没有一个很优雅的方式来处理SQL。我写过两篇文章来讨论过这个问题

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

你还在用PreparedStatement吗

我认为PreparedStatement对比直接拼接SQL并没有绝对的优势,而且它没法解决一些实际的需求,写起来也不能算优雅。于是我开始搜索相关的解决方案,并没有发现一些现存的工具,在读了很多相关的文章,我认为DSL会是一个很好的方式来处理SQL。我在工作的项目当中引入了DSL的思想,写了一个雏形的框架,因为一开始写的时候,设计不是特别完善,于是决定做一个开源的项目,把这个框架重新设计,这就成了LikeSql。


LikeSql的计划

  1. 尽快完善文档,释出release版本(希望年内达成吧)
  2. 编写教程和例子
  3. 建立反馈渠道和讨论圈子
  4. 和其他框架的集成

 

关于LikeSql的名字

 

Like有两层意思,一个相似,像的意思,就是希望这个框架写起来与SQL文本很相近,易读性强;另一个意思是喜欢,希望使用它的用户能够喜欢写SQL。

 

 

欢迎加入这个项目,欢迎指教和讨论。

23
1
分享到:
评论
33 楼 yawei 2010-12-09  
I think it's a good idea. it's very useful when using sql query directly, like for reporting.

32 楼 osprey 2010-12-07  
LZ,我昨晚有幸花了点时间查看了这个项目在goolecode上源代码,你还是靠PreparedStatement来解决sql注入问题的,这样而言,你只是在PreparedStatement上薄薄的涂了一层,就只是为了优雅解决in (?,?)问题,其他也没有解决任何问题,你自己认为的DSL,我只能说:还是造轮子,而且不是基于语义或EL的,是基于枚举的,和mybatis,springtjdbcemple提供的功能而言,还要弱。你可以看看mybaits3.0里的SqlBuilder的用法,和你的用法查不多,但更优雅些,因为你的select.from.xx方法,到后面还是string+string+string,而不是stringbuffer.append,你明白的哦.坦白讲,我一直不认为sql放在java代码里是个好的主要。我也写过很多程序,如果sql语句进行调试时,非要在debug时才能看到它的sql到底是什么,这本身就是一件令人发指的事情,还是停留在一个人,页面上点个按钮,然后看看ok了,sql也不错了,就扔在一边的状态。丝毫不考虑sql 优化,多线程等事情,如果用了你的方式,我调试sql语句时,就非得把项目拉起来,debug到你的生成sql语句的地方,然后再copy出sql,放到我的pl developer上进行分析sql的执行计划,小项目这个还比较快,但你试过拉个大的项目要30s到3分钟的吗?我一天要浪费多少时间在拉项目上。当然,你说可以用juit来拉,问题是:现在普遍使用spring的情况下,我还是要把所有的spring里的实例初始化一边,才能测试这个dao。否则就要把spring的配置文件切成一片一片,为了使用你这个工具,要耗费多少周边的东西,收益与支出不成正比。
hibernate还有插件可以直接调试hql,ibatis可以直接xml里 copy出sql调试。你这样把sql hardcode 在代码里,你有什么工具支持调试?

btw,请注意你的StringUtils的filterSql方法,它是线程不安全的,具体你看看http://fhd001.iteye.com/blog/411013,你的SYM_PATTERN.matcher(str)实例了一个Matcher ,而Matcher 是线程不安全的,你的SYM_PATTERN还是个staitc变量!
31 楼 huansinho 2010-12-06  
如果把方法命简化一下就更好。如:value => val
30 楼 osprey 2010-12-03  
Heis 写道
@osprey:首先很感谢你能关注LikeSql,你提出的安全和性能当然非常重要,我在写这个框架的时候也是充分考虑到的,但是感觉片言两语很难回答清楚,我会再写一篇文章来解释。

现在能简略回答你的是
1.“对于转义sql注入,关键字过滤是苍白的”
我同意这个观点,我也没说过LikeSql是通过过滤Update这些关键字来确保安全性。

2.“当然,LZ现在做的应用是国内的,一般很少会用英文写作”
我没说过只针对国内的应用,而且,让你失望的是,我打算API文档都用英文来写。

3.“PreparedStatement 是避免sql最好最终手段,因为PreparedStatement 里的?告诉了数据库本身,这个?是参数,而不是sql语句的执行部分”
PreparedStatement本身只是一个接口(interface)而已,而具体的实现是在数据库的驱动(Driver)里实现的。如何将?替换成安全的参数,依赖于驱动自己的实现。


LZ,
1。关于过滤,我相信你有你的算法,但是,至少在现阶段,我看到的解决方案(包含了源码的)都不是很好的解决过滤问题,包括有些所谓的sql注入防火墙,这是因为 防守需要滴水不漏,而攻击只需突破一点,而过滤是属于防守性的,必须充分论证没有漏洞才可以,就像md5算法,公开了10年多才被认可是安全的,但现在又被认为是可以冲撞的,不安全的。当然,希望看到你的文章阐述你的算法,让大家看看是否有漏洞。
2。“当然,LZ现在做的应用是国内的,一般很少会用英文写作”这句话是有上下文的,它并不是说LZ不能用英文写api,而是表明,如果是用过滤update关键字的方法的话,如果字段内容里有英文就会产生无法输入的问题。如果纯粹输入中文就没有这种问题发生。
3。PreparedStatement本身只是一个接口(interface)而已,而具体的实现是在数据库的驱动(Driver)里实现的。这是没有错的,但“如何将?替换成安全的参数,依赖于驱动自己的实现”这句话是有局限性的,在主流商用数据库中,如何将?替换成安全的参数是依赖数据库实现的,而不是驱动,我举个例子,在oracle中,你通过jdbc执行 PreparedStatement("select  * from x where a=?");ps.setString(1,"abc"); 时,oracle的jdbc驱动还真是通过socket报文直接先发送"select  * from x where a=?"这句话给oracle的server,然后发送一个参数数组把 "abc" 给oracle的server,由oracle的server把sql语句进行sql语法解析等步骤后,再和"abc"配合进行查询的,如果你有兴趣的话,可以通过抓包工具监控jdbc与oracleserver之间的socket报文就清晰了。

所以,综上所述,你的过滤如果不能像数据库本身进行sql解析,判断数据类型是否合法,数据类型和数据库表对应的列数据类型匹配,那在原理上进行sql注入的防范难度是非常高的。当然LZ可以说通过jdbc接口获得每张表的字段类型,那就对有关联的select,如“select a.x,b.y from xx a ,yy  b where a.z=? "这种语句 必须自己进行sql解析,执行转义函数,判断,自己建立sandbox进行预执行方才知道该次的参数是否是sql注入的。否则,你就会错误的挡掉下面的sql语句"update a set x='update  y set b=1'",这句话的本义是将x列更新为字符串"'update  y set b=1y".在LZ的算法中变成如何就不知道是否是认为注入而拒绝了。
29 楼 Heis 2010-12-03  
@osprey:首先很感谢你能关注LikeSql,你提出的安全和性能当然非常重要,我在写这个框架的时候也是充分考虑到的,但是感觉片言两语很难回答清楚,我会再写一篇文章来解释。

现在能简略回答你的是
1.“对于转义sql注入,关键字过滤是苍白的”
我同意这个观点,我也没说过LikeSql是通过过滤Update这些关键字来确保安全性。

2.“当然,LZ现在做的应用是国内的,一般很少会用英文写作”
我没说过只针对国内的应用,而且,让你失望的是,我打算API文档都用英文来写。

3.“PreparedStatement 是避免sql最好最终手段,因为PreparedStatement 里的?告诉了数据库本身,这个?是参数,而不是sql语句的执行部分”
PreparedStatement本身只是一个接口(interface)而已,而具体的实现是在数据库的驱动(Driver)里实现的。如何将?替换成安全的参数,依赖于驱动自己的实现。
28 楼 Heis 2010-12-03  
bugnuke 写道
赞同你的钻研精神 但是真的很优雅么?

优雅是相对而言的,当然也只能在java语法的限制内实现的优雅。相对PreparedStatement来说,我觉得是有优势的。
27 楼 osprey 2010-12-03  
osprey 写道
为了灵活而灵活却放弃了PreparedStatement最重要的两点
1.安全:你的工具无法避免sql注入
2.性能:你的工具也无法让数据库重复利用执行计划(oracle10g以后也许区别不大)

同时,请参看mybatis,也就是ibatis3.0的实现,已经实现了你的功能,包括元注释和代码内嵌入SQL,但是还是用的PreparedStatement ,因为上述两点.
开源框架存在总是有理由的,jdbc的PreparedStatement 出现也是因为实际使用中的要求,这种要求从java出现之前的c时代就有了.


特别说明,避免sql注入的最终手段,不是靠LZ先前文章中所述的过滤类似 update等 关键字就可以解决的,因为这样会有下面的问题:
1.如果本身字段是个varchar的备注域,备注内填写的内容是"update the record",那就会导致要么插入记录异常,要不就变成"**** the record",这不是应用要求的真实表达,当然,LZ现在做的应用是国内的,一般很少会用英文写作.
2.sql注入还有所谓的转义sql注入,请LZ参看相关文档,对于转义sql注入,关键字过滤是苍白的.
市面上所谓的前端sql注入放火墙就存在上面的问题,只是用来忽悠一些人而已,因为这是置标,不是置本的方法.
而PreparedStatement 是避免sql最好最终手段,因为PreparedStatement 里的?告诉了数据库本身,这个?是参数,而不是sql语句的执行部分,就算这个?相对应的参数是"update table set xxx=xx",它也认为这个是字符串而已,不会当作sql语句执行.

所以LZ一直要避免使用PreparedStatement ,就因为它不让你爽,但是如果想通过自己的工具自己组SQL直接执行,就必须面对我前面所说的两个问题,黑客和性能不让你爽,这也是大部分使用广泛的SQL封装工具选用PreparedStatement 的原因.
综上所述,赞同你的钻研精神,但是优雅是要有代价的,而你的工具面临安全和性能的双重约束,如果用在没有安全要求和性能要求的地方也行是可以的,但只要有安全要求,基本就会crack掉.在这种意义上,这种工具反而是有害的. .
26 楼 boygirl 2010-12-03  
代码可以参考下?
25 楼 osprey 2010-12-03  
为了灵活而灵活却放弃了PreparedStatement最重要的两点
1.安全:你的工具无法避免sql注入
2.性能:你的工具也无法让数据库重复利用执行计划(oracle10g以后也许区别不大)

同时,请参看mybatis,也就是ibatis3.0的实现,已经实现了你的功能,包括元注释和代码内嵌入SQL,但是还是用的PreparedStatement ,因为上述两点.
开源框架存在总是有理由的,jdbc的PreparedStatement 出现也是因为实际使用中的要求,这种要求从java出现之前的c时代就有了.
24 楼 fxyc 2010-12-03  
用zend框架的时候,貌似写法很类似。
23 楼 bugnuke 2010-12-01  
赞同你的钻研精神 但是真的很优雅么?
22 楼 sslaowan 2010-12-01  
问题是你的底层是用的PS吗?如果不是的话,预编译SQL和非预编译SQL在运行多次时性能差距巨大
21 楼 shxiao 2010-12-01  
和楼主的想法差不多,不过楼主可以看一下 clojureql,
就是写一个sql对等的DSL,
20 楼 lyw985 2010-12-01  
Heis 写道
lyw985 写道
MS不支持多数据库兼容

绝大多数的数据库都支持标准sql,当然需要你写的标准,而LikeSql只是帮助你写sql而已,与数据库兼容性无关。

简单的分页就不支持么,mysql和oracle是不一样的
19 楼 Heis 2010-11-30  
liguiking 写道
有空写这种玩无聊的玩意,还不如找点私活干,赚两小酒钱,这东西百无一用

哈哈,兄弟活的够潇洒的。我只求这东西万有一用便可。
18 楼 Heis 2010-11-30  
lyw985 写道
MS不支持多数据库兼容

绝大多数的数据库都支持标准sql,当然需要你写的标准,而LikeSql只是帮助你写sql而已,与数据库兼容性无关。
17 楼 liguiking 2010-11-30  
有空写这种玩无聊的玩意,还不如找点私活干,赚两小酒钱,这东西百无一用
16 楼 lyw985 2010-11-30  
MS不支持多数据库兼容
15 楼 Heis 2010-11-29  
hsw168 写道
其实,在对预编译的方式稍微封装一下就很不错了:
db.preparedUpdate("DELETE FROM LD_XZYMX WHERE FCBH=? AND YDBH=? AND NVL(JHLX,0)!=0 AND CKQR=0",
new Object[]{fcbh, ydbh}, true);
这样的代码也是很灵活的,,,

jy1245626 写道
与Spring的JdbcTemplete有什么区别??
貌似就是它的翻版


JdbcTemplate也是对预编译方式的封装,就是对PreparedStatement的封装,但是我之前的文章有说过PreparedStatement可读性和灵活性不够。可以看我之前的文章http://www.iteye.com/topic/756176

LikeSql也是可以说是针对PreparedStatement的封装,不同的是更好的可读性和灵活性。
14 楼 Heis 2010-11-29  
flootball 写道
写个存储过程不就得了。

deepfuture 写道
不错,再智能一点就好。。。。,让它成为真正的描述性的语言,还能智能形成存储过程

LikeSql在设计的时候就没有针对存储过程,存储过程和SQL在兼容性和可读性上还是有很大差别的。暂时没有转化为存储过程的计划。

相关推荐

Global site tag (gtag.js) - Google Analytics