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

你还在用PreparedStatement吗?

阅读更多

    我先列举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
分享到:
评论
5 楼 lanxia39 2011-09-20  
想法很不错,那个javasqlbuilder也不错
4 楼 kqy929 2010-11-25  
xiaoyuwei 写道
你这个完全是轮子,而且很原始和落后,建议楼主先看看ibatis的功能.
ibatis支持各种集合:数组,Collenction集合.preparedStatement都不需要用
不过对你的探索 还是赞赏一下

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

相关推荐

    练习3:使用PreparedStatement插入宠物信息.zip

    PreparedStatement对象允许程序员将SQL语句作为模板,其中包含一个或多个占位符,这些占位符在运行时用实际值替换。这可以提高性能,因为数据库可以预先编译SQL语句,然后多次执行,而无需每次都解析它。此外,由于...

    PreparedStatement

    jdbc2.0版 PreparedStatement接口的用法

    connection 和 preparedstatement 的关闭问题

    - **PreparedStatement**:每个`PreparedStatement`实例都对应着一定的数据库资源占用,因此在不再使用时也应当及时关闭。一般情况下,可以在处理完相关的业务逻辑后立即关闭,或者在`try-catch-finally`结构中的...

    java中PreparedStatement和Statement的区别

    Java 中 PreparedStatement 和 ...只要全使用预编译语句,你就用不着对传入的数据做任何过虑。 因此,在任何时候都应该使用 PreparedStatement,而不是 Statement。这可以提高代码的可读性、可维护性、性能和安全性。

    Statement和PreparedStatement之间的区别

    PreparedStatement对象相比Statement对象具有更多的优点,因此在实际开发中,建议使用PreparedStatement对象来代替Statement对象。 知识点: 1. Statement对象和PreparedStatement对象的区别 2. PreparedStatement...

    PreparedStatement详细用法

    其优势不仅体现在性能和安全性上,还在于其提供的数据库无关性,以及对复杂数据类型处理的支持,这些都使得`PreparedStatement`成为现代Java应用程序与数据库交互时的首选工具。 综上所述,无论从性能、安全性还是...

    PreparedStatement和Statement

    当你知道SQL语句在运行时不会改变时,可以使用`Statement`。它的方法包括`executeQuery()`和`executeUpdate()`,分别用于执行返回结果集的查询和修改数据的DML(数据操纵语言)语句,如INSERT、UPDATE或DELETE。 ...

    PreparedStatement 详细用法

    通过使用`PreparedStatement`,不仅可以提高数据库操作的性能,还可以增强程序的安全性。对于初学者而言,掌握`PreparedStatement`的基本用法是非常重要的一步。希望本文能帮助读者更好地理解和应用`...

    JDBC基础教程之PreparedStatement.doc

    通过以上介绍可以看出,`PreparedStatement`不仅可以提高执行SQL语句的效率,还能有效防止SQL注入攻击,因为它使用参数化查询而非字符串拼接的方式构建SQL语句。因此,在开发涉及数据库操作的应用程序时,推荐优先...

    PreparedStatement 向数据库插入时间方法

    为了确保SQL查询的安全性和效率,通常会使用`PreparedStatement`来执行这样的操作。然而,在处理日期和时间类型的数据时,可能会遇到一些问题。例如,当我们尝试使用`java.util.Date`对象将日期传递给`...

    MySql练习3:使用PreparedStatement插入宠物信息.zip

    MySql练习3:使用PreparedStatement插入宠物信息.zip MySql练习3:使用PreparedStatement插入宠物信息.zip MySql练习3:使用PreparedStatement插入宠物信息.zip

    关于PreparedStatement插入Date类型值的方法.txt

    在Java编程语言中,使用`PreparedStatement`来执行SQL语句是一种常见的操作数据库的方式。这种方式不仅可以提高程序的性能,还可以有效地防止SQL注入攻击。当我们在应用程序中需要向数据库中插入日期(`Date`类型)...

    使用PreparedStatement的setString方法会自动在varchar后面补空格

    此实例意在解决预处理命令PreparedStatement的setString()方法,在sql2008数据库中写入数据时,会自动补足空格的问题, 同时此实例也解决了当存在自动补足空格的问题时,使用nvarchar可以使查找出来的数据与原输入...

    【性能】JDBC PreparedStatement和连接池PreparedStatement Cache学习记录

    在使用连接池和`PreparedStatement`时,需要注意以下几点: - **合理配置缓存大小**:根据应用的实际情况调整连接池的`PreparedStatement Cache`大小,过小可能导致频繁的预编译,过大则可能浪费内存。 - **避免过度...

    PreparedStatement接口

    NULL 博文链接:https://chaoyi.iteye.com/blog/2088080

    JDBC中PreparedStatement接口提供的execute、executeQuery和executeUpdate之间的区别及用法

    在使用 executeQuery 方法时,JDBC 会将查询结果封装在 ResultSet 对象中,以便程序可以遍历和处理查询结果。 例如,在获取所有的 AirEnvironmentPresent 记录时,可以使用 executeQuery 方法来执行SELECT 语句: `...

    JSP中的PreparedStatement对象操作数据库的使用教程

    这篇文章将详细介绍PreparedStatement对象在JSP中操作数据库的使用方法,包括创建对象、传递参数、以及确保参数数据类型一致性等关键知识点。 首先,PreparedStatement实例可以包含预编译的SQL语句,这意味着SQL...

    使用PreparedStatement访问数据库

    总的来说,`PreparedStatement`是Java数据库编程中推荐使用的接口,尤其在处理动态SQL和大量重复执行的SQL时,它能够提供更好的性能和安全性。在实际项目开发中,应当充分利用其特性,优化数据库操作。

Global site tag (gtag.js) - Google Analytics