1. 程式人生 > >PreparedStatement與Statement的區別

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語句,足以毀掉整個應用。