# I* e- R$ _* m, yPreparedStatement比 Statement 更快 1 ]; k2 T+ j0 ]( R8 C( K6 t9 [2 U使用 PreparedStatement 最重要的一点好处是它拥有更佳的性能优势,SQL语句会预编译在数据库系统中。执行计划同样会被缓存起来,它允许数据库做参数化查询。使用预处理语句比普通的查询更快,因为它做的工作更少(数据库对SQL语句的分析,编译,优化已经在第一次查询前完成了)。为了减少数据库的负载,生产环境中德JDBC代码你应该总是使用PreparedStatement 。值得注意的一点是:为了获得性能上的优势,应该使用参数化sql查询而不是字符串追加的方式。下面两个SELECT 查询,第一个SELECT查询就没有任何性能优势。8 c. u8 I. g5 i/ H
SQL Query 1:字符串追加形式的PreparedStatement + W* p$ i2 J/ ?: ^+ Q* }. k, W12 y& y% l) W4 K7 d2 Y7 P, U
2% [- B3 F( S' [. N
String loanType = getLoanType();, u. w1 F2 T( a/ {" C
PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=" + loanType);$ v# C9 S" k0 p& t3 V' `, O3 ?
SQL Query 2:使用参数化查询的PreparedStatement / o6 b1 {1 l0 I 9 }; z" l: x' K9 z0 D/ K% d1 3 T- p y- I S0 ~8 F9 R2( i8 P+ h; M# ]5 P( P" v7 r
PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=?");3 y$ e" I% ?! X- V' j P I
prestmt.setString(1,loanType);; ]2 L$ F" J, a) T1 w
第二个查询就是正确使用PreparedStatement的查询,它比SQL1能获得更好的性能。+ u6 V: I4 L/ c
/ S/ B9 V/ g' ?3 ~9 h6 T- F, P
PreparedStatement可以防止SQL注入式攻击5 ~) k# l7 Y: J+ v/ N7 F7 u3 D+ V
如果你是做Java web应用开发的,那么必须熟悉那声名狼藉的SQL注入式攻击。去年Sony就遭受了SQL注入攻击,被盗用了一些Sony play station(PS机)用户的数据。在SQL注入攻击里,恶意用户通过SQL元数据绑定输入,比如:某个网站的登录验证SQL查询代码为: 8 f4 b2 P2 O+ y' N' ]& SstrSQL = "SELECT * FROM users WHERE name = '" + userName + "' and pw = '"+ passWord +"';" # E @+ ~( U8 A7 K6 g恶意填入:6 p- [* S; u& q3 |/ ]( E- l$ X4 `
% ~* }9 t! O, V huserName = "1' OR '1'='1";, ~) @; O4 L5 Y7 @( M% C `
passWord = "1' OR '1'='1";% P6 a3 I# Y3 J2 A0 e$ O. _' S
那么最终SQL语句变成了: 0 C+ p* Y/ K6 g( v4 i2 J( \. N7 \# ?& t+ o4 @
strSQL = "SELECT * FROM users WHERE name = '1' OR '1'='1' and pw = '1' OR '1'='1';": h \% I1 h' c" I; d$ M
因为WHERE条件恒为真,这就相当于执行:! t3 B5 V: {9 U
- _. ~; g2 {( X& LstrSQL = "SELECT * FROM users;" ; d* B% f8 Q, @因此可以达到无账号密码亦可登录网站。如果恶意用户要是更坏一点,用户填入: D8 m# u4 x i
$ y/ x$ C( d2 F v
strSQL = "SELECT * FROM users;"% ?# ~# v* ^& B+ {9 O
SQL语句变成了:+ k8 Z7 `7 e$ y2 V4 d8 U( q
# d; a* B. E$ S# t3 b" q" V; SstrSQL = "SELECT * FROM users WHERE name = 'any_value' and pw = ''; DROP TABLE users"+ L' \- }6 ?# j
这样一来,虽然没有登录,但是数据表都被删除了。 c+ K3 Y' f2 P0 h) T) ~
6 N A9 g0 H P. g% b
然而使用PreparedStatement的参数化的查询可以阻止大部分的SQL注入。在使用参数化查询的情况下,数据库系统(eg:MySQL)不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,因此就算参数中含有破坏性的指令,也不会被数据库所运行。7 b x- U0 k" P! {* u$ N3 ]
补充:避免SQL注入的第二种方式:) w3 M' i4 m- D. q( C$ V
在组合SQL字符串的时候,先对所传入的参数做字符取代(将单引号字符取代为连续2个单引号字符,因为连续2个单引号字符在SQL数据库中会视为字符中的一个单引号字符,譬如:$ q4 p3 u+ j0 W
8 D) b, ^4 B% P5 s
strSQL = "SELECT * FROM users WHERE name = '" + userName + "';" : I1 m) M) O" I8 V! j传入字符串:8 |# `. `) m7 Y4 P, u7 P/ R8 d
: C0 A. Z) V. {5 v- x1 m! \
userName = " 1' OR 1=1 " 5 k$ k: P9 _4 x把userName做字符替换后变成:7 l; c; u9 I& R+ M" y
7 M; t; X4 \, U$ Z3 W7 S3 \userName = " 1'' OR 1=1" : g( v; \8 n, `/ E2 }" X" g: Y最后生成的SQL查询语句为:1 X5 Y" x3 |; d3 J1 g7 t" t
) X G( j) z5 |9 [& ^* {
strSQL = "SELECT * FROM users WHERE name = '1'' OR 1=1'4 l1 v# d( Y5 m: ]2 h& i& L4 r9 J0 R
这样数据库就会去系统查找name为“1′ ‘ OR 1=1”的记录,而避免了SQL注入。 & I4 l, S) X" n, M' F# C0 b5 O4 t' X9 `3 ], k" k
比起凌乱的字符串追加似的查询,PreparedStatement查询可读性更好、更安全。4 Y- X5 @$ J- ?6 B i! b" F
PreparedStatement的局限性 H4 s, {0 w0 f
' k/ ^( j0 }, x+ k, y: x( z* i o5 t8 S
尽管PreparedStatement非常实用,但是它仍有一定的限制。+ r% k* b+ ~3 V' N1 [
1. 为了防止SQL注入攻击,PreparedStatement不允许一个占位符(?)有多个值,在执行有**IN**子句查询的时候这个问题变得棘手起来。下面这个SQL查询使用PreparedStatement就不会返回任何结果 4 h5 B/ G" e2 V6 h f/ `0 [$ q3 _( p- F # I F { P* ?SELECT * FROM loan WHERE loan_type IN (?) / T6 A+ j c' I M# _' i+ |preparedSatement.setString(1, "'personal loan', 'home loan', 'gold loan'"); * t; ]: O2 t) z1 A8 i/ q那如何解决这个问题呢?请你继续关注本博客,下期告诉你答案。 ' r5 H2 V/ E+ T2 A2 X: L/ y' U; u2 H
不算总结的总结2 j) b* w0 }- i. R