/** * Copyright 2013-2014 Guoqiang Chen, Shanghai, China. All rights reserved. * * Email: subchen@gmail.com * URL: http://subchen.github.io/ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrick.dao.orm.utils; import java.io.InputStream; import java.io.Reader; import java.math.BigDecimal; import java.net.URL; import java.sql.*; import java.sql.Date; import java.util.*; /** * This class wraps around a {@link PreparedStatement} and allows the programmer to set parameters by name instead * of by index. This eliminates any confusion as to which parameter index represents what. This also means that * rearranging the SQL statement or adding a parameter doesn't involve renumbering your indices. * Code such as this: * * <pre><code> * Connection conn = getConnection(); * String sql = "select * from my_table where name=? or address=?"; * PreparedStatement p = conn.prepareStatement(sql); * p.setString(1, "bob"); * p.setString(2, "123"); * ResultSet rs = p.executeQuery(); * </code></pre> * * Can be replaced with: * * <pre><code> * Connection conn = getConnection(); * String sql = "select * from my_table where name=:name or address=:address"; * NamedParameterStatement p = new NamedParameterStatement(conn, sql); * p.setString("name", "bob"); * p.setString("address", "123"); * ResultSet rs = p.executeQuery(); * </code></pre> */ public class NamedParameterStatement extends PreparedStatementWrapper { private static final HashMap<String, Map<String, List<Integer>>> nameIndexCache = new HashMap<String, Map<String, List<Integer>>>(); private static final HashMap<String, String> parsedSqlCache = new HashMap<String, String>(); private final String parsedSql; private final Map<String, List<Integer>> nameIndexMap; /** * Creates a NamedParameterStatement. Wraps a call to * c.{@link Connection#prepareStatement(java.lang.String) prepareStatement}. * @param conn the database connection * @param sql the parameterized sql * @throws SQLException if the statement could not be created */ public NamedParameterStatement(Connection conn, String sql) throws SQLException { if (nameIndexCache.containsKey(sql)) { nameIndexMap = nameIndexCache.get(sql); parsedSql = parsedSqlCache.get(sql); } else { nameIndexMap = new HashMap<String, List<Integer>>(); parsedSql = parseNamedSql(sql, nameIndexMap); nameIndexCache.put(sql, nameIndexMap); parsedSqlCache.put(sql, parsedSql); } ps = conn.prepareStatement(parsedSql); } /** * Returns the indexes for a parameter. * @param name parameter name * @return parameter indexes * @throws IllegalArgumentException if the parameter does not exist */ private List<Integer> getIndexes(String name) { List<Integer> indexes = nameIndexMap.get(name); if (indexes == null) { throw new IllegalArgumentException("Parameter not found: " + name); } return indexes; } /** * Parses a sql with named parameters. The parameter-index mappings * are put into the map, and the parsed sql is returned. * @param sql sql with named parameters * @return the parsed sql */ private static String parseNamedSql(String sql, Map<String, List<Integer>> nameIndexMap) { // I was originally using regular expressions, but they didn't work well for ignoring // parameter-like strings inside quotes. int length = sql.length(); StringBuffer parsedSql = new StringBuffer(length); boolean inSingleQuote = false; boolean inDoubleQuote = false; int index = 1; for (int i = 0; i < length; i++) { char c = sql.charAt(i); if (inSingleQuote) { if (c == '\'') { inSingleQuote = false; } } else if (inDoubleQuote) { if (c == '"') { inDoubleQuote = false; } } else { if (c == '\'') { inSingleQuote = true; } else if (c == '"') { inDoubleQuote = true; } else if (c == ':' && i + 1 < length && Character.isJavaIdentifierStart(sql.charAt(i + 1))) { int j = i + 2; while (j < length && Character.isJavaIdentifierPart(sql.charAt(j))) { j++; } String name = sql.substring(i + 1, j); c = '?'; // replace the parameter with a question mark i += name.length(); // skip past the end if the parameter List<Integer> indexList = nameIndexMap.get(name); if (indexList == null) { indexList = new LinkedList<Integer>(); nameIndexMap.put(name, indexList); } indexList.add(index); index++; } } parsedSql.append(c); } return parsedSql.toString(); } public void setArray(String name, Array value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setArray(index, value); } } public void setAsciiStream(String name, InputStream value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setAsciiStream(index, value); } } public void setAsciiStream(String name, InputStream value, int length) throws SQLException { for (Integer index : getIndexes(name)) { ps.setAsciiStream(index, value, length); } } public void setBigDecimal(String name, BigDecimal value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setBigDecimal(index, value); } } public void setBinaryStream(String name, InputStream value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setBinaryStream(index, value); } } public void setBinaryStream(String name, InputStream value, int length) throws SQLException { for (Integer index : getIndexes(name)) { ps.setBinaryStream(index, value, length); } } public void setBinaryStream(String name, InputStream value, long length) throws SQLException { for (Integer index : getIndexes(name)) { ps.setBinaryStream(index, value, length); } } public void setBlob(String name, Blob value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setBlob(index, value); } } public void setBlob(String name, InputStream value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setBlob(index, value); } } public void setBlob(String name, InputStream value, long length) throws SQLException { for (Integer index : getIndexes(name)) { ps.setBlob(index, value, length); } } public void setBoolean(String name, boolean value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setBoolean(index, value); } } public void setByte(String name, byte value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setByte(index, value); } } public void setBytes(String name, byte[] value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setBytes(index, value); } } public void setCharacterStream(String name, Reader value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setCharacterStream(index, value); } } public void setCharacterStream(String name, Reader value, int length) throws SQLException { for (Integer index : getIndexes(name)) { ps.setCharacterStream(index, value, length); } } public void setCharacterStream(String name, Reader value, long length) throws SQLException { for (Integer index : getIndexes(name)) { ps.setCharacterStream(index, value, length); } } public void setClob(String name, Clob value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setClob(index, value); } } public void setClob(String name, Reader value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setClob(index, value); } } public void setClob(String name, Reader value, long length) throws SQLException { for (Integer index : getIndexes(name)) { ps.setClob(index, value, length); } } public void setDate(String name, Date value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setDate(index, value); } } public void setDate(String name, Date value, Calendar cal) throws SQLException { for (Integer index : getIndexes(name)) { ps.setDate(index, value, cal); } } public void setDouble(String name, double value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setDouble(index, value); } } public void setFloat(String name, float value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setFloat(index, value); } } public void setInt(String name, int value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setInt(index, value); } } public void setLong(String name, long value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setLong(index, value); } } public void setNCharacterStream(String name, Reader value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setNCharacterStream(index, value); } } public void setNCharacterStream(String name, Reader value, long length) throws SQLException { for (Integer index : getIndexes(name)) { ps.setNCharacterStream(index, value, length); } } public void setNClob(String name, NClob value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setNClob(index, value); } } public void setNClob(String name, Reader value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setNClob(index, value); } } public void setNClob(String name, Reader value, long length) throws SQLException { for (Integer index : getIndexes(name)) { ps.setNClob(index, value, length); } } public void setNString(String name, String value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setNString(index, value); } } public void setNull(String name, int sqlType) throws SQLException { for (Integer index : getIndexes(name)) { ps.setNull(index, sqlType); } } public void setObject(String name, Object value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setObject(index, value); } } public void setObject(String name, Object value, int targetSqlType) throws SQLException { for (Integer index : getIndexes(name)) { ps.setObject(index, value, targetSqlType); } } public void setObject(String name, Object value, int targetSqlType, int scaleOrLength) throws SQLException { for (Integer index : getIndexes(name)) { ps.setObject(index, value, targetSqlType, scaleOrLength); } } public void setRef(String name, Ref value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setRef(index, value); } } public void setRowId(String name, RowId value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setRowId(index, value); } } public void setShort(String name, short value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setShort(index, value); } } public void setSQLXML(String name, SQLXML value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setSQLXML(index, value); } } public void setString(String name, String value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setString(index, value); } } public void setTime(String name, Time value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setTime(index, value); } } public void setTime(String name, Time value, Calendar cal) throws SQLException { for (Integer index : getIndexes(name)) { ps.setTime(index, value, cal); } } public void setTimestamp(String name, Timestamp value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setTimestamp(index, value); } } public void setTimestamp(String name, Timestamp value, Calendar cal) throws SQLException { for (Integer index : getIndexes(name)) { ps.setTimestamp(index, value, cal); } } @SuppressWarnings("deprecation") public void setUnicodeStream(String name, InputStream value, int length) throws SQLException { for (Integer index : getIndexes(name)) { ps.setUnicodeStream(index, value, length); } } public void setURL(String name, URL value) throws SQLException { for (Integer index : getIndexes(name)) { ps.setURL(index, value); } } }