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语句,足以毁掉整个应用。