论坛首页 入门技术论坛

你还在用PreparedStatement吗?

浏览 6909 次
该帖已经被评为新手帖
作者 正文
   发表时间:2010-09-05   最后修改:2010-09-05

    我先列举PreparedStatement的几大罪状吧。

 

1.难以调试 。这几乎是PreparedStatement最为人诟病之处了。在Debug的时候,你是无法读到完整的SQL的。

2.过于死板,难以扩展。

罪证:

有一个SQL模板

 

select * from table where name in (?)
 

 

 

我希望把一个有可能变动的array植入这个模板,例如["Tim","Mary","Joe"], 期望可以得到

 

select * from table where name in ('Tim','Mary','Joe')

 

 

虽然PreparedStatement有setArray(java.sql.Array)这样的方法,先别说java.sql.Array这个 几乎没几个人听说过的类是什么来着,但是令人崩溃的很多驱动(Driver)都没有去实现这个方法,其中包括MySql的官方驱动。遇着这种情况只能去拼 接SQL了。

 

 

3.有时候阅读起来非常费劲

罪证:

 

 

insert String sql="into table (ffews,fggws,fu756,grgr,jg,f498s,luy,qff,gre,T4y6,n_,ngn,erw) values(?,?,?,?,?,?,?,?,?,?,?,?,?)";

PreparedStatement ps=...;
ps.setString(1,"abc");
ps.setString(2,"hr");
ps.setString(3,"gege");
ps.setString(4,"nfgdn");
ps.setString(5,"nt");
ps.setString(6,"o78");
ps.setString(7,"kykt");
ps.setString(8,"h4f");
ps.setString(9,"kuyk");
ps.setString(10,"nhnd");
ps.setString(11,"ng");
ps.setString(12,"32r32");
ps.setString(13,"bfg");
 

 

 你知道值”h4f “是对应哪个column吗?你不会是在数吧?

 

 

 

     读到这里,很多人都庆幸自己早已经摒弃PreparedStatement,用上ORM的框架了。但是本文并不是探讨用ORM的替代方案。我相信现在还是 有很多的遗留系统在使用PreparedStatement或者直接拼接SQL,我就经手过不少这样的项目。因此我在这里想探讨的是保留SQL的前提下, 寻找比PrepareStatement更好的解决方案,而不用对系统造成太大的改动。

 

     我之前有写过使用DSL的方式来改善SQL的编写,请看这里

 

     现在它变成了一个开源的小工具,名字叫LikeSql (http://code.google.com/p/likesql/)。虽然它还没有正式的release版本(我还在努力),但是我想先把这个想法提出来,和大家讨论,希望我不是在重复发明轮子,或者是创造了另外一个Evil。

 

     LikeSql设计的主要目的就是让程序员更好地使用Java编写SQL,同时可以针对PreparedStatement的缺点做出改善。我把设计目的总结为以下的几点。

 

1. 容易调试

2. 容易编写和扩展

3. 容易理解

 

 

    说了那么多,终于可以上代码了,我们通过一些代码的例子来看几个LikeSql的特点吧。

(1)更简单的Insert语句

 

//测试Insert语句
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());
	}

    虽然这里稍微修改了insert语句的语法结构,但是value和column的对应关系一目了然。

 

(2)模板匹配和静态工厂方法

 

//测试update语句
public void testSimpleSql(){
		String eSql="UPDATE user SET gender='M', name='Tom' WHERE id=123";
		String eTpl="UPDATE user SET gender=?, name=? WHERE id=?";
		
		LikeSql uSql=DML.update("user")
		                .set(QuestExp.qt("gender=?, name=?")
                   		     .set(1, 'M')
                		     .set(2, "Tom"))
		                .where(QuestExp.qt("id=?").set(1,123));
		
		assertEquals(eSql,uSql.toString());
		assertEquals(eTpl,uSql.toTemplate());
	}
 
public void testAtStyleExpression(){
		String eSql="UPDATE user SET gender='M', name='Tom' WHERE id=123";
		String eTpl="UPDATE user SET gender=?, name=? WHERE id=?";
		
		LikeSql uSql=DML.update("user")
		                .set(AtExp.at("gender=@gender, name=@name")
                   		     .set("gender", 'M')
                		     .set("name", "Tom"))
		                .where(QuestExp.qt("id=?").set(1,123));
		
		assertEquals(eSql,uSql.toString());
		assertEquals(eTpl,uSql.toTemplate());
	}

    这里语法应该还是比较好懂的,QuestExp.qt(String pattern)方法参数是一个SQL的模板,类似PreparedStatement的语法,使用set方法来替换模板内?字符为所需要的值。AtExp则是另外一种模板,允许通过匹配以@开头的参数。

     LikeSql在设计的时候大量使用了静态工厂来创建实例,例如QuestExp.qt方法返回QuestExp实例,AtExp.at()也是一样。这样在使用JDK5以后的版本可以在语法上显得简洁点。

    以下是JDK5或以后版本的写法

import static com.googlecode.likesql.dml.DML;
import static com.googlecode.likesql.expression.QuestExp;

LikeSql uSql=update("user").set(qt("gender=?, name=?")
                   		     .set(1, 'M')
                		     .set(2, "Tom"))
		           .where(qt("id=?")
                                     .set(1,123));
 

    (3)自定义类型和转换器

 

//测试select语句
public void testLikeCriteria(){
		LikeSql sSql=DML.selectAll()
		    .from("user")
		    .where(QuestExp.qt("name like ?").set(1, LikeExp.like("T%d")));
		
		String eSql="SELECT * FROM user WHERE name like 'T%d'";
		String eTpl="SELECT * FROM user WHERE name like ?";
		
		assertEquals(eSql,sSql.toString());
		assertEquals(eTpl,sSql.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());
	}

     LikeSql允许你set更多的类型。

     LikeSql允许你往Sql注入自定义的类型,你只需要实现一个相应的转换器(com.googlecode.converter.Converter)来转换这个类型的实例为SQL字符串,而且只需很简单的配置就可以实现类型与转换器的匹配。

    以下是一个我写的String类型的转换器

public class StringConverter implements Converter {

    public String toTemplate(Object obj) {
		return String.valueOf(SqlConstant.CHAR_FOR_REPLACE);//return '?'
	}
	
	public String toSql(Object obj){
		return StringUtils.replaceFirst("'?'", SqlConstant.CHAR_FOR_REPLACE, StringUtils.filterSql(obj.toString()));//return a filtered sql string
	}
}
 

 

    其实要实现容易调试的目的,很简单,只需要规定包含SQL的某个类使用toString()方法返回完整的SQL便可。

 

    以下是LikeSql接口代码:

 

/**
 * <p>LikeSql represents a SQL expression containing a executable SQL 
 * string and its SQL template without sensitive data.</p>
 * A default conversion is to implement {@link Object#toString()} to return a SQL string expression.
 * 
 * @author Johny Huang
 * @see     java.lang.Object#toString()
 */
public interface LikeSql {
	
	/**
	 * Return the SQL template without sensitive data.
	 * @return SQL template without sensitive data.
	 */
	String toTemplate();
}
 

   (4)更友好,更简单的调试

   在Debug的时候,把断点设到继承LikeSql的变量,就可以很简单看到完整的SQL。而toTemplate()方法则是允许你返回不含敏感数据的SQL模板,例如"select * from table where name=?",方便做Logging。

 

/**
 * <p>LikeSql represents a SQL expression containing a executable SQL 
 * string and its SQL template without sensitive data.</p>
 * A default conversion is to implement {@link Object#toString()} to return a SQL string expression.
 * 
 * @author Johny Huang
 * @see     java.lang.Object#toString()
 */
public interface LikeSql {
	
	/**
	 * Return the SQL template without sensitive data.
	 * @return SQL template without sensitive data.
	 */
	String toTemplate();
}
 

以下是eclipse在debug时候的截图:


很感谢你可以一直看到这里,不知道你有没有被LikeSql的一些特性吸引呢?或者是你看到了LikeSql的缺陷?任何质疑和建议都欢迎。

 

  • 大小: 52.7 KB
   发表时间:2010-09-05   最后修改:2010-09-05
还有一个是, PreparedStatement 和你这个, 在字段数很多的时候都得敲很多字段名字, 似乎jdbc开发 都这样, 不使用反射机制的话。 这就体现了ORM的好处了。
0 请登录后投票
   发表时间:2010-09-06  
这个是在PreparedStatement上的封装吗?
你说用问号的方式找不到?号对应的列,但是参数不是也可以用":paramName"的方式来设置,用setObject(paramName,value);的方式来确定列吗?
还有你的支持复杂一些的查询吗?比如子查询,左连接,其实sql的语法还是蛮复杂的,你这样都实现了,会不会工作量太大了?
不过in的方式还是蛮好用的,谢谢lz提供了一种好的解决思路.
0 请登录后投票
   发表时间:2010-09-06   最后修改:2010-09-06
你这个完全是轮子,而且很原始和落后,建议楼主先看看ibatis的功能.
ibatis支持各种集合:数组,Collenction集合.preparedStatement都不需要用
不过对你的探索 还是赞赏一下
0 请登录后投票
   发表时间:2010-11-25  
xiaoyuwei 写道
你这个完全是轮子,而且很原始和落后,建议楼主先看看ibatis的功能.
ibatis支持各种集合:数组,Collenction集合.preparedStatement都不需要用
不过对你的探索 还是赞赏一下

并不是所有项目都需要ORM。不要将问题复杂化。不同的问题不同的解决方案。
支持LZ,加油。
0 请登录后投票
论坛首页 入门技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics