package com.j256.db; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.NClob; import java.sql.PreparedStatement; import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; import java.sql.Statement; import java.sql.Struct; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; import javax.sql.DataSource; /** * Connection that doesn't actually allocate the connection until it sees the first call. If the first call is to set * read-only to be true then it will use the read-only connection pool otherwise read-write. For the read-only * connections, it does not send any changes to read-only to the delegate. * * @see http://256.com/gray/docs/misc/performance_optimizing_spring_hibernate_transactions/ * * @author graywatson */ public class ReadOnlyDetectingConnection implements Connection { private final DataSource readOnlyDataSource; private final DataSource readWriteDataSource; private final String username; private final String password; private Connection delegate; private boolean isReadOnlyDataSource; public ReadOnlyDetectingConnection(DataSource readOnlyDataSource, DataSource readWriteDataSource, String username, String password) { this.readOnlyDataSource = readOnlyDataSource; this.readWriteDataSource = readWriteDataSource; this.username = username; this.password = password; } @Override public T unwrap(Class iface) throws SQLException { return getConnection(false).unwrap(iface); } @Override public boolean isWrapperFor(Class iface) throws SQLException { return getConnection(false).isWrapperFor(iface); } @Override public Statement createStatement() throws SQLException { return getConnection(false).createStatement(); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return getConnection(false).prepareStatement(sql); } @Override public CallableStatement prepareCall(String sql) throws SQLException { return getConnection(false).prepareCall(sql); } @Override public String nativeSQL(String sql) throws SQLException { return getConnection(false).nativeSQL(sql); } @Override public void setAutoCommit(boolean autoCommit) throws SQLException { getConnection(false).setAutoCommit(autoCommit); } @Override public boolean getAutoCommit() throws SQLException { return getConnection(false).getAutoCommit(); } @Override public void commit() throws SQLException { getConnection(false).commit(); } @Override public void rollback() throws SQLException { getConnection(false).rollback(); } @Override public void close() throws SQLException { getConnection(false).close(); } @Override public boolean isClosed() throws SQLException { return getConnection(false).isClosed(); } @Override public DatabaseMetaData getMetaData() throws SQLException { return getConnection(false).getMetaData(); } @Override public void setReadOnly(boolean readOnly) throws SQLException { // if we don't have a delegate yet if (delegate == null) { /* * Then we use the first set read-only call to determine whether to get the connection from the read-only * data-source or the read-write one. */ if (readOnly) { // assign the delegate but don't do the set read-only because the connection should be alreadyq // read-only getConnection(true); isReadOnlyDataSource = true; } else { getConnection(false).setReadOnly(false); } } else if (isReadOnlyDataSource) { // ignore setting read-only if the connection is always read-only } else { delegate.setReadOnly(readOnly); } } @Override public boolean isReadOnly() throws SQLException { return getConnection(false).isReadOnly(); } @Override public void setCatalog(String catalog) throws SQLException { getConnection(false).setCatalog(catalog); } @Override public String getCatalog() throws SQLException { return getConnection(false).getCatalog(); } @Override public void setTransactionIsolation(int level) throws SQLException { getConnection(false).setTransactionIsolation(level); } @Override public int getTransactionIsolation() throws SQLException { return getConnection(false).getTransactionIsolation(); } @Override public SQLWarning getWarnings() throws SQLException { return getConnection(false).getWarnings(); } @Override public void clearWarnings() throws SQLException { getConnection(false).clearWarnings(); } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return getConnection(false).createStatement(resultSetType, resultSetConcurrency); } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return getConnection(false).prepareStatement(sql, resultSetType, resultSetConcurrency); } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return getConnection(false).prepareCall(sql, resultSetType, resultSetConcurrency); } @Override public Map> getTypeMap() throws SQLException { return getConnection(false).getTypeMap(); } @Override public void setTypeMap(Map> map) throws SQLException { getConnection(false).setTypeMap(map); } @Override public void setHoldability(int holdability) throws SQLException { getConnection(false).setHoldability(holdability); } @Override public int getHoldability() throws SQLException { return getConnection(false).getHoldability(); } @Override public Savepoint setSavepoint() throws SQLException { return getConnection(false).setSavepoint(); } @Override public Savepoint setSavepoint(String name) throws SQLException { return getConnection(false).setSavepoint(name); } @Override public void rollback(Savepoint savepoint) throws SQLException { getConnection(false).rollback(savepoint); } @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { getConnection(false).releaseSavepoint(savepoint); } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return getConnection(false).createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return getConnection(false).prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return getConnection(false).prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { return getConnection(false).prepareStatement(sql, autoGeneratedKeys); } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return getConnection(false).prepareStatement(sql, columnIndexes); } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return getConnection(false).prepareStatement(sql, columnNames); } @Override public Clob createClob() throws SQLException { return getConnection(false).createClob(); } @Override public Blob createBlob() throws SQLException { return getConnection(false).createBlob(); } @Override public NClob createNClob() throws SQLException { return getConnection(false).createNClob(); } @Override public SQLXML createSQLXML() throws SQLException { return getConnection(false).createSQLXML(); } @Override public boolean isValid(int timeout) throws SQLException { return getConnection(false).isValid(timeout); } @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { try { getConnection(false).setClientInfo(name, value); } catch (SQLClientInfoException e) { throw e; } catch (SQLException e) { SQLClientInfoException scie = new SQLClientInfoException(); scie.initCause(e); throw scie; } } @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { try { getConnection(false).setClientInfo(properties); } catch (SQLClientInfoException e) { throw e; } catch (SQLException e) { SQLClientInfoException scie = new SQLClientInfoException(); scie.initCause(e); throw scie; } } @Override public String getClientInfo(String name) throws SQLException { return getConnection(false).getClientInfo(name); } @Override public Properties getClientInfo() throws SQLException { return getConnection(false).getClientInfo(); } @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return getConnection(false).createArrayOf(typeName, elements); } @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { return getConnection(false).createStruct(typeName, attributes); } @Override public void setSchema(String schema) throws SQLException { getConnection(false).setSchema(schema); } @Override public String getSchema() throws SQLException { return getConnection(false).getSchema(); } @Override public void abort(Executor executor) throws SQLException { getConnection(false).abort(executor); } @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { getConnection(false).setNetworkTimeout(executor, milliseconds); } @Override public int getNetworkTimeout() throws SQLException { return getConnection(false).getNetworkTimeout(); } private Connection getConnection(boolean readOnlyCandidate) throws SQLException { if (delegate == null) { DataSource dataSource = (readOnlyCandidate ? readOnlyDataSource : readWriteDataSource); if (username == null) { delegate = dataSource.getConnection(); } else { delegate = dataSource.getConnection(username, password); } } return delegate; } }