package com.j256.db; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import java.util.logging.Logger; import javax.sql.DataSource; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; /** * A connection pool factory that creates pools that wraps a DataSource using two HikariCP connection pools -- one for * read-only connections and one for read-write connections. Really any connection pool should be improved with * something like this code. * *

* The whole point of this is to detect if a connection is read-only or read-write and allocate the connection from the * correct pool to lower JDBC overhead related to setting read status on the connection each time. *

* * @see http://256.com/gray/docs/misc/performance_optimizing_spring_hibernate_transactions/ * * @author graywatson */ public class ReadOnlyDetectingPooledDataSource implements DataSource { private static final long DEFAULT_CONNECTION_TIMEOUT_MILLIS = 5000; private static final int MINIMUM_READ_ONLY_POOL_SIZE = 1; private static final int MINIMUM_READ_WRITE_POOL_SIZE = 1; private String url; private String user; private String password; private Properties dataSourceProperties; private boolean defaultAutoCommit; private DataSource dataSource; private String dataSourceClassName; private HikariDataSource readOnlyPooledDataSource; private HikariDataSource readWritePooledDataSource; public void initialize() { HikariConfig readOnlyConf = buildConfig(MINIMUM_READ_ONLY_POOL_SIZE, true); readOnlyPooledDataSource = new HikariDataSource(readOnlyConf); HikariConfig readWriteConf = buildConfig(MINIMUM_READ_WRITE_POOL_SIZE, false); readWritePooledDataSource = new HikariDataSource(readWriteConf); } @Override public Connection getConnection() { return new ReadOnlyDetectingConnection(readOnlyPooledDataSource, readWritePooledDataSource, null, null); } @Override public Connection getConnection(String username, String password) { return new ReadOnlyDetectingConnection(readOnlyPooledDataSource, readWritePooledDataSource, username, password); } @Override public int getLoginTimeout() throws SQLException { // both pools have the same settings so we just get from the read-only one return readOnlyPooledDataSource.getLoginTimeout(); } @Override public void setLoginTimeout(int seconds) throws SQLException { readOnlyPooledDataSource.setLoginTimeout(seconds); readWritePooledDataSource.setLoginTimeout(seconds); } @Override public PrintWriter getLogWriter() throws SQLException { // both pools have the same settings so we just get from the read-only one return readOnlyPooledDataSource.getLogWriter(); } @Override public void setLogWriter(PrintWriter out) throws SQLException { readOnlyPooledDataSource.setLogWriter(out); readWritePooledDataSource.setLogWriter(out); } @Override public boolean isWrapperFor(Class iface) throws SQLException { // both pools have the same settings so we just get from the read-only one return readOnlyPooledDataSource.isWrapperFor(iface); } @Override public T unwrap(Class iface) throws SQLException { // both pools have the same settings so we just get from the read-only one return readOnlyPooledDataSource.unwrap(iface); } public void close() { if (readOnlyPooledDataSource != null) { readOnlyPooledDataSource.close(); readOnlyPooledDataSource = null; } if (readWritePooledDataSource != null) { readWritePooledDataSource.close(); readWritePooledDataSource = null; } } public void setDataSourceProperties(Properties dataSourceProperties) { this.dataSourceProperties = dataSourceProperties; } public void setUrl(String url) { this.url = url; } public void setUser(String user) { this.user = user; } public void setPassword(String password) { this.password = password; } public void setDataSourceClassName(String dataSourceClassName) { this.dataSourceClassName = dataSourceClassName; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public void setDefaultAutoCommit(boolean defaultAutoCommit) { this.defaultAutoCommit = defaultAutoCommit; } /** * Here for Java 7 only. * * WARNING: do not add an @Overide here because Java6 doesn't have this method. */ @SuppressWarnings("all") public Logger getParentLogger() { return Logger.getLogger(getClass().getSimpleName()); } private HikariConfig buildConfig(int minimumPoolSize, boolean readOnly) { HikariConfig conf = new HikariConfig(); if (dataSource == null) { conf.setDataSourceClassName(dataSourceClassName); } else { conf.setDataSource(dataSource); } conf.setConnectionTimeout(DEFAULT_CONNECTION_TIMEOUT_MILLIS); conf.setMinimumIdle(minimumPoolSize); // default auto-commit setting for connections conf.setAutoCommit(defaultAutoCommit); // default read-only setting for connections conf.setReadOnly(readOnly); // turn on jmx stuff conf.setRegisterMbeans(true); if (dataSourceProperties != null) { conf.setDataSourceProperties(dataSourceProperties); } conf.addDataSourceProperty("url", url); if (user != null) { conf.addDataSourceProperty("user", user); } if (password != null) { conf.addDataSourceProperty("password", password); } return conf; } }