package org.openslx.imagemaster.db; import java.io.Closeable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Class for creating {@link PreparedStatement}s with named parameters. Based on * Named * Parameters for PreparedStatement */ public class MysqlStatement implements Closeable { private static final QueryCache cache = new QueryCache(); private final PreparsedQuery query; private final PreparedStatement statement; private final List openResultSets = new ArrayList<>(); MysqlStatement( Connection con, String sql ) throws SQLException { PreparsedQuery query; synchronized ( cache ) { query = cache.get( sql ); } if ( query == null ) { query = parse( sql ); synchronized ( cache ) { cache.put( sql, query ); } } this.query = query; this.statement = con.prepareStatement( query.sql, Statement.RETURN_GENERATED_KEYS ); } public String getQuery() { return query.sql; } /** * Returns the indexes for a parameter. * * @param name parameter name * @return parameter indexes * @throws IllegalArgumentException if the parameter does not exist */ private List getIndexes( String name ) { List indexes = query.indexMap.get( name ); if ( indexes == null ) { throw new IllegalArgumentException( "Parameter not found: " + name ); } return indexes; } /** * Sets a parameter. * * @param name parameter name * @param value parameter value * @throws SQLException if an error occurred * @throws IllegalArgumentException if the parameter does not exist * @see PreparedStatement#setObject(int, java.lang.Object) */ public void setObject( String name, Object value ) throws SQLException { List indexes = getIndexes( name ); for ( Integer index : indexes ) { statement.setObject( index, value ); } } /** * Sets a parameter. * * @param name parameter name * @param value parameter value * @throws SQLException if an error occurred * @throws IllegalArgumentException if the parameter does not exist * @see PreparedStatement#setString(int, java.lang.String) */ public void setString( String name, String value ) throws SQLException { List indexes = getIndexes( name ); for ( Integer index : indexes ) { statement.setString( index, value ); } } /** * Sets a parameter. * * @param name parameter name * @param value parameter value * @throws SQLException if an error occurred * @throws IllegalArgumentException if the parameter does not exist * @see PreparedStatement#setInt(int, int) */ public void setInt( String name, int value ) throws SQLException { List indexes = getIndexes( name ); for ( Integer index : indexes ) { statement.setInt( index, value ); } } /** * Sets a parameter. * * @param name parameter name * @param value parameter value * @throws SQLException if an error occurred * @throws IllegalArgumentException if the parameter does not exist * @see PreparedStatement#setLong(int, long) */ public void setLong( String name, long value ) throws SQLException { List indexes = getIndexes( name ); for ( Integer index : indexes ) { statement.setLong( index, value ); } } /** * Sets a parameter. * * @param name parameter name * @param value parameter value * @throws SQLException if an error occurred * @throws IllegalArgumentException if the parameter does not exist * @see PreparedStatement#setBoolean(int, boolean) */ public void setBoolean( String name, boolean value ) throws SQLException { List indexes = getIndexes( name ); for ( Integer index : indexes ) { statement.setBoolean( index, value ); } } /** * Sets a parameter. * * @param name parameter name * @param value parameter value * @throws SQLException if an error occurred * @throws IllegalArgumentException if the parameter does not exist * @see PreparedStatement#setBoolean(int, boolean) */ public void setBinary( String name, byte[] value ) throws SQLException { List indexes = getIndexes( name ); for ( Integer index : indexes ) { statement.setBytes( index, value ); } } /** * Executes the statement. * * @return true if the first result is a {@link ResultSet} * @throws SQLException if an error occurred * @see PreparedStatement#execute() */ public boolean execute() throws SQLException { return statement.execute(); } /** * Executes the statement, which must be a query. * * @return the query results * @throws SQLException if an error occurred * @see PreparedStatement#executeQuery() */ public ResultSet executeQuery() throws SQLException { ResultSet rs = statement.executeQuery(); openResultSets.add( rs ); return rs; } /** * Executes the statement, which must be an SQL INSERT, UPDATE or DELETE * statement; or an SQL statement that returns nothing, such as a DDL * statement. * * @return number of rows affected * @throws SQLException if an error occurred * @see PreparedStatement#executeUpdate() */ public int executeUpdate() throws SQLException { return statement.executeUpdate(); } /** * Retrieves any auto-generated keys created as a result of executing this * Statement object. If this Statement object did * not generate any keys, an empty ResultSet * object is returned. * *

* Note:If the columns which represent the auto-generated keys were not specified, * the JDBC driver implementation will determine the columns which best represent the * auto-generated keys. * * @return a ResultSet object containing the auto-generated key(s) * generated by the execution of this Statement object * @exception SQLException if a database access error occurs or * this method is called on a closed Statement * @throws SQLFeatureNotSupportedException if the JDBC driver does not support this method */ public ResultSet getGeneratedKeys() throws SQLException { ResultSet rs = statement.getGeneratedKeys(); openResultSets.add( rs ); return rs; } public int lastInsertId() throws SQLException { int result = -1; try ( ResultSet rs = statement.getGeneratedKeys() ) { if ( rs.next() ) { result = rs.getInt( 1 ); } } return result; } /** * Closes the statement. * * @see Statement#close() */ @Override public void close() { for ( ResultSet rs : openResultSets ) { try { rs.close(); } catch ( SQLException e ) { // } } try { statement.cancel(); } catch ( SQLException e ) { // Nothing to do } try { statement.close(); } catch ( SQLException e ) { // Nothing to do } } /** * Adds the current set of parameters as a batch entry. * * @throws SQLException if something went wrong */ public void addBatch() throws SQLException { statement.addBatch(); } /** * Executes all of the batched statements. * * See {@link Statement#executeBatch()} for details. * * @return update counts for each statement * @throws SQLException if something went wrong */ public int[] executeBatch() throws SQLException { return statement.executeBatch(); } // static methods private static PreparsedQuery parse( String query ) { int length = query.length(); StringBuffer parsedQuery = new StringBuffer( length ); Map> paramMap = new HashMap<>(); boolean inSingleQuote = false; boolean inDoubleQuote = false; boolean hasBackslash = false; int index = 1; for ( int i = 0; i < length; i++ ) { char c = query.charAt( i ); if ( hasBackslash ) { // Last char was a backslash, so we ignore the current char hasBackslash = false; } else if ( c == '\\' ) { // This is a backslash, next char will be escaped hasBackslash = true; } else if ( inSingleQuote ) { // End of quoted string if ( c == '\'' ) { inSingleQuote = false; } } else if ( inDoubleQuote ) { // End of quoted string if ( c == '"' ) { inDoubleQuote = false; } } else { // Not in string, look for named params if ( c == '\'' ) { inSingleQuote = true; } else if ( c == '"' ) { inDoubleQuote = true; } else if ( c == ':' && i + 1 < length && Character.isJavaIdentifierStart( query.charAt( i + 1 ) ) ) { int j = i + 2; while ( j < length && Character.isJavaIdentifierPart( query.charAt( j ) ) ) { j++; } String name = query.substring( i + 1, j ); c = '?'; // replace the parameter with a question mark i += name.length(); // skip past the end of the parameter List indexList = paramMap.get( name ); if ( indexList == null ) { indexList = new ArrayList<>(); paramMap.put( name, indexList ); } indexList.add( Integer.valueOf( index ) ); index++; } } parsedQuery.append( c ); } return new PreparsedQuery( parsedQuery.toString(), paramMap ); } // private helper classes private static class PreparsedQuery { private final Map> indexMap; private final String sql; public PreparsedQuery( String sql, Map> indexMap ) { this.sql = sql; this.indexMap = indexMap; } } private static class QueryCache extends LinkedHashMap { private static final long serialVersionUID = 1L; public QueryCache() { super( 30, (float)0.75, true ); } @Override protected boolean removeEldestEntry( Map.Entry eldest ) { return size() > 40; } } }