JDBC数据源连接池(4)---自定义数据源连接池
[续上文《JDBC数据源连接池(3)---Tomcat集成DBCP》]
我们已经 了解了DBCP,C3P0,以及Tomcat内置的数据源连接池,那么,这些数据源连接池是如何实现的呢?为了究其原理,我在这里写一个自定义的数据源连接池。
我先在com.itszt.utils包下新建一个Utils_5_mydscp 文件夹,在该文件夹下写自定义的数据源连接池工具;同时,我在src包下建config_utils_5文件夹,该文件夹下建一个属性配置文件mydscp.properties,该属性配置文件信息如下:
DRIVER_NAME=com.mysql.jdbc.Driver JDBC_URL=jdbc:mysql://localhost:3306/itszt2 USERNAME=root PASSWORD=2017 INITIAL_SIZE=10 MAX_ACTIVE=100 MAX_IDLE=20 MIN_IDLE=10
接着,分析该数据源连接池,需要一个解析属性配置文件的类,需要一个数据源(拟采用单例模式)类,还需要一个Connection类;为了降低耦合度,采用工厂模式获得数据源实例;考虑到异常的个性化处理,还可以自定义异常。
有了上述分析后,第一步,写一个解析属性配置文件的工具类MyDSCPConfig,代码如下:
import java.io.IOException; import java.io.InputStream; import java.util.Properties; public abstract class MyDSCPConfig { public static String DRIVER_NAME = null; public static String JDBC_URL = null; public static String USERNAME = null; public static String PASSWORD = null; public static Integer INITIAL_SIZE = null; public static Integer MAX_ACTIVE = null; public static Integer MAX_IDLE = null; public static Integer MIN_IDLE = null; /** * 解析属性配置文件 * * @param propertiesPath */ public static void parseConfig(String propertiesPath) { Properties properties = new Properties(); InputStream resourceAsStream = MyDSCPConfig.class.getClassLoader().getResourceAsStream(propertiesPath); try { properties.load(resourceAsStream); } catch (IOException e) { e.printStackTrace(); } DRIVER_NAME = properties.getProperty("DRIVER_NAME"); JDBC_URL = properties.getProperty("JDBC_URL"); USERNAME = properties.getProperty("USERNAME"); PASSWORD = properties.getProperty("PASSWORD"); INITIAL_SIZE = Integer.parseInt(properties.getProperty("INITIAL_SIZE")); MAX_ACTIVE = Integer.parseInt(properties.getProperty("MAX_ACTIVE")); MAX_IDLE = Integer.parseInt(properties.getProperty("MAX_IDLE")); MIN_IDLE = Integer.parseInt(properties.getProperty("MIN_IDLE")); } }
第二步,先采用适配器模式,写一个实现了Connection 接口的MyConnectionAdapter抽象类,便于MyConnectionAdapter的子类有选择地覆写所需的方法,MyConnectionAdapter类代码如下:
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; /** * 适配器 */ public abstract class MyConnectionAdapter implements Connection { @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @Override public Statement createStatement() throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return null; } @Override public CallableStatement prepareCall(String sql) throws SQLException { return null; } @Override public String nativeSQL(String sql) throws SQLException { return null; } @Override public void setAutoCommit(boolean autoCommit) throws SQLException { } @Override public boolean getAutoCommit() throws SQLException { return false; } @Override public void commit() throws SQLException { } @Override public void rollback() throws SQLException { } @Override public void close() throws SQLException { } @Override public boolean isClosed() throws SQLException { return false; } @Override public DatabaseMetaData getMetaData() throws SQLException { return null; } @Override public void setReadOnly(boolean readOnly) throws SQLException { } @Override public boolean isReadOnly() throws SQLException { return false; } @Override public void setCatalog(String catalog) throws SQLException { } @Override public String getCatalog() throws SQLException { return null; } @Override public void setTransactionIsolation(int level) throws SQLException { } @Override public int getTransactionIsolation() throws SQLException { return 0; } @Override public SQLWarning getWarnings() throws SQLException { return null; } @Override public void clearWarnings() throws SQLException { } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return null; } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { return null; } @Override public Map<String, Class<?>> getTypeMap() throws SQLException { return null; } @Override public void setTypeMap(Map<String, Class<?>> map) throws SQLException { } @Override public void setHoldability(int holdability) throws SQLException { } @Override public int getHoldability() throws SQLException { return 0; } @Override public Savepoint setSavepoint() throws SQLException { return null; } @Override public Savepoint setSavepoint(String name) throws SQLException { return null; } @Override public void rollback(Savepoint savepoint) throws SQLException { } @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return null; } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { return null; } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { return null; } @Override public Clob createClob() throws SQLException { return null; } @Override public Blob createBlob() throws SQLException { return null; } @Override public NClob createNClob() throws SQLException { return null; } @Override public SQLXML createSQLXML() throws SQLException { return null; } @Override public boolean isValid(int timeout) throws SQLException { return false; } @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { } @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { } @Override public String getClientInfo(String name) throws SQLException { return null; } @Override public Properties getClientInfo() throws SQLException { return null; } @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { return null; } @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { return null; } @Override public void setSchema(String schema) throws SQLException { } @Override public String getSchema() throws SQLException { return null; } @Override public void abort(Executor executor) throws SQLException { } @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { } @Override public int getNetworkTimeout() throws SQLException { return 0; } }
第三步,再写一个继承了MyConnectionAdapter的子类MyConnection,代码如下:
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; /** * 采用装饰者模式,基于Conneciton原有的close()功能,扩展其close()功能 */ public class MyConnection extends MyConnectionAdapter { private Connection connection; private MyDataSource dataSource; public MyConnection(Connection connection, MyDataSource dataSource) { this.connection = connection; this.dataSource = dataSource; } @Override public Statement createStatement() throws SQLException { return connection.createStatement(); } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return connection.prepareStatement(sql); } @Override public void close() throws SQLException { dataSource.recycle(this); } /* * 关闭连接,释放资源 */ public void dispose() { if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
第四步,写一个继承了SQLException的自定义异常MyConnectionExp,代码如下:
import java.sql.SQLException; public class MyConnectionExp extends SQLException { public MyConnectionExp(String reason) { super(reason); } }
第五步,写一个实现了DataSource接口的抽象类MyDataSourceAdapter,覆写接口的所有方法,该类代码如下:
import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; import javax.sql.DataSource; /** * 适配器 */ public abstract class MyDataSourceAdapter implements DataSource { @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @Override public Connection getConnection() throws SQLException { return null; } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } }
第六步,写一个继承了MyDataSourceAdapter 抽象类的子类MyDataSource,由于数据源连接池只能有一个,故采用单例模式,该子类代码如下:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.LinkedList; /** * 单例模式 */ public class MyDataSource extends MyDataSourceAdapter { private static MyDataSource myDataSource; private LinkedList<MyConnection> connectionQueue = new LinkedList<>(); private int totalActive = MyDSCPConfig.MAX_ACTIVE; static { // 加载数据库驱动 try { Class.forName(MyDSCPConfig.DRIVER_NAME); } catch (ClassNotFoundException e) { e.printStackTrace(); } } private MyDataSource() { } //懒汉模式,同步,确保线程安全 public static MyDataSource getInstance() { if (myDataSource == null) { synchronized (MyDataSource.class) { if (myDataSource == null) { myDataSource = new MyDataSource(); myDataSource.initialize(); } } } return myDataSource; } //初始化方法 public void initialize() { for (int i = 0; i < MyDSCPConfig.INITIAL_SIZE; i++) { MyConnection connection = produceConnection(); if (connection == null) { continue; } connectionQueue.add(connection); } } //构建一个连接的方法 private MyConnection produceConnection() { try { Connection connection = DriverManager.getConnection(MyDSCPConfig.JDBC_URL, MyDSCPConfig.USERNAME, MyDSCPConfig.PASSWORD); return new MyConnection(connection, this); } catch (SQLException e) { MyConnectionExp exp = new MyConnectionExp("获取连接失败!"); exp.printStackTrace(); } return null; } //从连接池中获取连接 @Override public Connection getConnection() throws SQLException { int num1 = connectionQueue.size(); MyConnection myConnection = null; if (this.totalActive > 0) { if (num1 >= 1) { myConnection = connectionQueue.pop(); this.totalActive--; } else { System.err.println("连接池已无空闲连接,需生产后才能使用!"); myConnection = produceConnection(); connectionQueue.add(myConnection); getConnection(); } int num2 = connectionQueue.size(); System.out.println("获取连接,原先有 " + num1 + " 个,现在有 " + num2 + " 个"); if (num2 < MyDSCPConfig.MIN_IDLE) { System.out.println("不足最小空闲连接数,生产一个新的!"); connectionQueue.add(produceConnection()); } } else { System.err.println("已有一次超出最大连接数!"); this.totalActive = MyDSCPConfig.MAX_ACTIVE; getConnection(); } return myConnection; } /* * 回收连接 */ protected void recycle(MyConnection myConnection) { if (connectionQueue.size() >= MyDSCPConfig.MAX_IDLE) { System.out.println("超出空闲连接数上限,不再回收!"); myConnection.dispose(); return; } else { connectionQueue.add(myConnection); this.totalActive++; } } }
第七步,基于工厂模式,写一个获取数据源连接池实例的类MyDataSourceFactory,该类代码如下:
/** * 工厂模式 */ public abstract class MyDataSourceFactory { public static MyDataSource createDataSource(String propertiesPath) { //1.解析配置文件中的配置 MyDSCPConfig.parseConfig(propertiesPath); //2.依据上一步骤的设置,生产我们的数据源 return MyDataSource.getInstance(); } }
最后,在com.itszt.demo文件夹中的LoginServlet.java的Servlet文件中,将产生Connection对象的方式修改为自定义的数据源连接池方式即可。
总结:在Web项目中,我们经常需要频繁连接数据库,采用数据源连接池的方式,能够有效地解决建立数据库连接时耗费较多CPU、时间等资源的问题,从而提高应用性能,改进用户体验。