今天同事遇到一个问题,简言之,就是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都是同一个,说明确实是拿的缓存的....
代码如下:
1 import java.sql.Connection; 2 import java.sql.DriverManager; 3 import java.sql.PreparedStatement; 4 import java.sql.SQLException; 5 6 import oracle.jdbc.OracleConnection; 7 8 9 public class Test 10 { 11 static Connection con = null; 12 public static void main(String args[]) throws ClassNotFoundException, SQLException 13 { 14 con = ((oracle.jdbc.OracleConnection)getConnection()); 15 PreparedStatement ps0 = getPS(con); 16 ps0.close(); 17 PreparedStatement ps1 = getPS(con); 18 ps1.close(); 19 System.out.println(ps0==ps1);//如果是true,说明cache发生了作用 20 21 22 con.close(); 23 con = null; 24 con = ((oracle.jdbc.OracleConnection)getConnection()); 25 PreparedStatement ps2 = getPS(con); 26 ps2.close(); 27 con.close(); 28 System.out.println(ps1==ps2);//如果是false,说明connection关闭后,会重新缓存 29 30 } 31 32 //获取PS实例 33 public static PreparedStatement getPS(Connection con)throws ClassNotFoundException, SQLException{ 34 35 String sql = "select * from cms_turbine t where t.mapping like ?"; 36 PreparedStatement ps1 = con.prepareStatement(sql); 37 ps1.setString(1, "hello"); 38 return ps1; 39 } 40 41 //建立连接 42 public static Connection getConnection(){ 43 if(con == null){ 44 try{ 45 Class.forName("oracle.jdbc.driver.OracleDriver"); 46 con = DriverManager.getConnection("jdbc:oracle:thin:@127.0.0.1:1523:XXX",xx,xx); 47 48 //下面两句是为了开启cache 49 ((oracle.jdbc.OracleConnection)con).setImplicitCachingEnabled(true); 50 ((oracle.jdbc.OracleConnection)con).setStatementCacheSize(10); 51 }catch(Exception e){ 52 e.printStackTrace(); 53 } 54 } 55 return con; 56 } 57 }
执行结果是: 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