PreparedStatement與Statement的區別
PreparedStatement jdk的解釋是
An object that represents a precompiled SQL statement.
A SQL statement is precompiled and stored in a PreparedStatement object.
主要特點是:
1、提高了安全性,可以防止SQL注入;
2、除錯不方便,看不到sql語句,需要額外使用p6spy等輔助包;
2、預編譯語句。
並不是說PreparedStatement在所有的DB上都不會提高效率,PreparedStatement需要伺服器端的支援,比如在Oracle上就會有顯著效果。而MySQL比較明確地說明了不支援PreparedStatement。至於為什麼預編譯就會提高效率呢?因為oracle中會將所有的sql語句先編譯,叫做“執行計劃”,放在oracle內部的一個特定的快取中,每次遇到相同的sql,就會預先呼叫快取中,如果不預編譯,每次都用statement,那麼每次都要編譯,在緩衝中會有很多重複的“執行計劃”,影響資料庫的效能。
還有一點就是在使用setObject()的時候,記得一定要使用帶targetSqlType引數的方法,來提高效率。
以下是mysql驅動包中有關setObject()的原始碼
public void setObject(int parameterIndex, Object parameterObj, int targetSqlType, int scale) throws SQLException { if (parameterObj == null) { setNull(parameterIndex, java.sql.Types.OTHER); } else { try { switch (targetSqlType) { case Types.BOOLEAN: if (parameterObj instanceof Boolean) { setBoolean(parameterIndex, ((Boolean) parameterObj) .booleanValue()); break; } else if (parameterObj instanceof String) { setBoolean(parameterIndex, "true" .equalsIgnoreCase((String) parameterObj) || !"0".equalsIgnoreCase((String) parameterObj)); break; } else if (parameterObj instanceof Number) { int intValue = ((Number) parameterObj).intValue(); setBoolean(parameterIndex, intValue != 0); break; } else { throw new SQLException("No conversion from " + parameterObj.getClass().getName() + " to Types.BOOLEAN possible.", SQLError.SQL_STATE_ILLEGAL_ARGUMENT); } case Types.BIT: case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: case Types.REAL: case Types.FLOAT: case Types.DOUBLE: case Types.DECIMAL: case Types.NUMERIC: setNumericObject(parameterIndex, parameterObj, targetSqlType, scale); break; case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: if (parameterObj instanceof BigDecimal) { setString( parameterIndex, (StringUtils .fixDecimalExponent(StringUtils .consistentToString((BigDecimal) parameterObj)))); } else { setString(parameterIndex, parameterObj.toString()); } break; case Types.CLOB: if (parameterObj instanceof java.sql.Clob) { setClob(parameterIndex, (java.sql.Clob) parameterObj); } else { setString(parameterIndex, parameterObj.toString()); } break; case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: case Types.BLOB: if (parameterObj instanceof byte[]) { setBytes(parameterIndex, (byte[]) parameterObj); } else if (parameterObj instanceof java.sql.Blob) { setBlob(parameterIndex, (java.sql.Blob) parameterObj); } else { setBytes(parameterIndex, StringUtils.getBytes( parameterObj.toString(), this.charConverter, this.charEncoding, this.connection .getServerCharacterEncoding(), this.connection.parserKnowsUnicode())); } break; case Types.DATE: case Types.TIMESTAMP: java.util.Date parameterAsDate; if (parameterObj instanceof String) { ParsePosition pp = new ParsePosition(0); java.text.DateFormat sdf = new java.text.SimpleDateFormat( getDateTimePattern((String) parameterObj, false), Locale.US); parameterAsDate = sdf.parse((String) parameterObj, pp); } else { parameterAsDate = (java.util.Date) parameterObj; } switch (targetSqlType) { case Types.DATE: if (parameterAsDate instanceof java.sql.Date) { setDate(parameterIndex, (java.sql.Date) parameterAsDate); } else { setDate(parameterIndex, new java.sql.Date( parameterAsDate.getTime())); } break; case Types.TIMESTAMP: if (parameterAsDate instanceof java.sql.Timestamp) { setTimestamp(parameterIndex, (java.sql.Timestamp) parameterAsDate); } else { setTimestamp(parameterIndex, new java.sql.Timestamp(parameterAsDate .getTime())); } break; } break; case Types.TIME: if (parameterObj instanceof String) { java.text.DateFormat sdf = new java.text.SimpleDateFormat( getDateTimePattern((String) parameterObj, true), Locale.US); setTime(parameterIndex, new java.sql.Time(sdf.parse( (String) parameterObj).getTime())); } else if (parameterObj instanceof Timestamp) { Timestamp xT = (Timestamp) parameterObj; setTime(parameterIndex, new java.sql.Time(xT.getTime())); } else { setTime(parameterIndex, (java.sql.Time) parameterObj); } break; case Types.OTHER: setSerializableObject(parameterIndex, parameterObj); break; default: throw new SQLException(Messages .getString("PreparedStatement.16"), //$NON-NLS-1$ SQLError.SQL_STATE_GENERAL_ERROR); } } catch (Exception ex) { if (ex instanceof SQLException) { throw (SQLException) ex; } throw new SQLException( Messages.getString("PreparedStatement.17") //$NON-NLS-1$ + parameterObj.getClass().toString() + Messages.getString("PreparedStatement.18") //$NON-NLS-1$ + ex.getClass().getName() + Messages.getString("PreparedStatement.19") + ex.getMessage(), //$NON-NLS-1$ SQLError.SQL_STATE_GENERAL_ERROR); } } }
不帶targetSqlType引數的setObject()方法
public void setObject(int parameterIndex, Object parameterObj) throws SQLException { if (parameterObj == null) { setNull(parameterIndex, java.sql.Types.OTHER); } else { if (parameterObj instanceof Byte) { setInt(parameterIndex, ((Byte) parameterObj).intValue()); } else if (parameterObj instanceof String) { setString(parameterIndex, (String) parameterObj); } else if (parameterObj instanceof BigDecimal) { setBigDecimal(parameterIndex, (BigDecimal) parameterObj); } else if (parameterObj instanceof Short) { setShort(parameterIndex, ((Short) parameterObj).shortValue()); } else if (parameterObj instanceof Integer) { setInt(parameterIndex, ((Integer) parameterObj).intValue()); } else if (parameterObj instanceof Long) { setLong(parameterIndex, ((Long) parameterObj).longValue()); } else if (parameterObj instanceof Float) { setFloat(parameterIndex, ((Float) parameterObj).floatValue()); } else if (parameterObj instanceof Double) { setDouble(parameterIndex, ((Double) parameterObj).doubleValue()); } else if (parameterObj instanceof byte[]) { setBytes(parameterIndex, (byte[]) parameterObj); } else if (parameterObj instanceof java.sql.Date) { setDate(parameterIndex, (java.sql.Date) parameterObj); } else if (parameterObj instanceof Time) { setTime(parameterIndex, (Time) parameterObj); } else if (parameterObj instanceof Timestamp) { setTimestamp(parameterIndex, (Timestamp) parameterObj); } else if (parameterObj instanceof Boolean) { setBoolean(parameterIndex, ((Boolean) parameterObj) .booleanValue()); } else if (parameterObj instanceof InputStream) { setBinaryStream(parameterIndex, (InputStream) parameterObj, -1); } else if (parameterObj instanceof java.sql.Blob) { setBlob(parameterIndex, (java.sql.Blob) parameterObj); } else if (parameterObj instanceof java.sql.Clob) { setClob(parameterIndex, (java.sql.Clob) parameterObj); } else if (parameterObj instanceof java.util.Date) { setTimestamp(parameterIndex, new Timestamp( ((java.util.Date) parameterObj).getTime())); } else if (parameterObj instanceof BigInteger) { setString(parameterIndex, parameterObj.toString()); } else { setSerializableObject(parameterIndex, parameterObj); } } }
所以大家不要為了省事,而使用不帶targetSqlType引數的setObject()方法。
Statement是PreparedStatement的父介面,
主要特點是:
1、易於除錯;
2、不進行預編譯操作,減少了進行預編譯的開銷。單次執行PreparedStatement要比Statement要慢一些
這裡有個對比:http://www.onjava.com/lpt/a/1480
Table 19-3: OCI driver timings (in milliseconds) | ||
Inserts |
Statement |
PreparedStatement |
1 |
10 |
113 |
1,000 |
2,804 |
1,412 |
The important thing to notice about the graph is that it's not until about 65 inserts that the PreparedStatement
object outperforms the Statement
object. 65 inserts
綜上:
Statement和PreparedStatement,都有其優缺點,但總體而言,強烈建議使用Statement的同學改為使用PreparedStatement,如果希望除錯方便,再加個p6spy等包做輔助,看到的sql語句效果更好。畢竟許多應用中,都要考安全性、大使用者量時候的效能問題。像hibernate、toplink這種jpa在使用jdbc的時候,如果資料庫端支援,都很統一的使用了PreparedStatement。
如:oracle.toplink.essentials.internal.databaseaccess.DatabasePlatform類。
順便再講一下,程式設計師寫出來的sql語句是最值得去關注的,一條效率差的sql語句,足以毀掉整個應用。