今天同事遇到一个问题,简言之,就是PreparedStatement的预编译究竟是怎么发挥作用的...
嘿嘿,说来惭愧,我以前就只知道PreparedStatement比Statement要好,要防SQL注入,就要用PS。
But,它的预编译作用,我却没关注过。现在经过一番研究,我不敢说我已经“了解”了,但是总归是思路清晰了一些,
我把我理解的整理出来希望对大家也有所帮助,有不对的地方请大家指正。
预编译
首先要说的就是“预编译”。我们知道数据库在执行SQL语句的时候,首先要解析这个SQL语句,有一个“执行计划”的概念(差不多就是指寻找执行这个sql语句的一个最优路径)。对于经常执行的sql,数据库会把执行计划给缓存起来,对应的key就是该条sql。那么当再次执行同样的sql的时候,数据库就不再解析了,直接根据key返回相应的执行计划。这就节省了很多时间。
PreparedStatement(后面简称PS)
当我们说到PS的时候,可能我们会关心几个问题:
预编译发生在什么时候
PS何时被缓存起来的
回答第一个问题:
“在创建PreparedStatement 对象时就指定了SQL语句,该语句立即发送给DBMS进行编译”。
回答第二个问题:
是当我们执行PS的close()方法时~~~~
TEST
我针对PS cache,做了一个测验,想法是针对同一个SQL,如果我每次取到的PS都是同一个,说明确实是拿的缓存的....
代码如下:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import oracle.jdbc.OracleConnection;
public class Test {
static Connection con = null;
public static void main(String args[]) throws ClassNotFoundException, SQLException {
con = ((oracle.jdbc.OracleConnection) getConnection());
PreparedStatement ps0 = getPS(con);
ps0.close();
PreparedStatement ps1 = getPS(con);
ps1.close();
System.out.println(ps0 == ps1);//如果是true,说明cache发生了作用
con.close();
con = null;
con = ((oracle.jdbc.OracleConnection) getConnection());
PreparedStatement ps2 = getPS(con);
ps2.close();
con.close();
System.out.println(ps1 == ps2);//如果是false,说明connection关闭后,会重新缓存
}
//获取PS实例
public static PreparedStatement getPS(Connection con) throws ClassNotFoundException, SQLException {
String sql = "select * from cms_turbine t where t.mapping like ?";
PreparedStatement ps1 = con.prepareStatement(sql);
ps1.setString(1, "hello");
return ps1;
}
//建立连接
public static Connection getConnection() {
if (con == null) {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
con = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1523:XXX", xx, xx);
//下面两句是为了开启cache
((oracle.jdbc.OracleConnection) con).setImplicitCachingEnabled(true);
((oracle.jdbc.OracleConnection) con).setStatementCacheSize(10);
} catch (Exception e) {
e.printStackTrace();
}
}
return con;
}
}
执行结果是: true
false
说明同一个connection中两次取到的PS是同一个实例,不同connection不共用PS。
Note:
如果想要reuse PS,那再用完一个之后一定要close,意味着释放资源。在上面的代码中,如果main方法改写如下:
public static void main(String args[]) throws ClassNotFoundException, SQLException
{
con = ((oracle.jdbc.OracleConnection)getConnection());
PreparedStatement ps0 = getPS(con);
ps0.close();
PreparedStatement ps1 = getPS(con);
// ps1.close(); ps1不close,后面ps2拿到的还是ps0吗?
System.out.println(ps0==ps1);
PreparedStatement ps2 = getPS(con);
ps2.close();
con.close();
System.out.println(ps0==ps2);
}
执行结果是:true (因为ps0 close了)
false(因为ps1没有close,所以ps2并没有能拿到缓存中的PS)
参考:
http://www.linuxidc.com/Linux/2011-07/38776.htm
http://docs.oracle.com/cd/E11882_01/java.112/e10589/stmtcach.htm#JJDBC28649