上一个章节,我们讲了xml文件的解析框架XMLConfiguration的使用,不懂的可以参考
Apache Commons Configuration读取xml配置具体使用。
这个章节主要实现自己的数据库连接池,封装自己的BasicDataSource类。实现自己业务的数据池。下面开始我们的项目构建。
1.1.1. maven依赖。
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> <version>2.1.1</version> <exclusions> <exclusion> <artifactId>commons-logging</artifactId> <groupId>commons-logging</groupId> </exclusion> </exclusions> </dependency> <dependency> <groupId>commons-configuration</groupId> <artifactId>commons-configuration</artifactId> <version>1.8</version> </dependency> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.8.0</version> </dependency> <dependency> <groupId>commons-jxpath</groupId> <artifactId>commons-jxpath</artifactId> <version>1.3</version> </dependency>
1.1.2. 配置文件
数据库采用读写分离,所以定义了2个数据源配置,配置文件da2s.xml存放在src根目录下具体的配置如下:
<?xml version="1.0" encoding="UTF-8"?> <da2s-configuration> <DefaultConnectionPool>3000</DefaultConnectionPool> <connectionPool name="3000"> <dbtype>MYSQL</dbtype> <driverClassName>com.mysql.jdbc.Driver</driverClassName> <url>jdbc:mysql://localhost:3306/springok</url> <username>root</username> <password></password> <datasourceProperty> <defaultAutoCommit>false</defaultAutoCommit> <initialSize>10</initialSize> <maxActive>10</maxActive> <maxIdle>5</maxIdle> <minIdle>5</minIdle> <maxWait>3000</maxWait> <validationQuery>select 1</validationQuery> <testOnBorrow>true</testOnBorrow> <removeAbandoned>true</removeAbandoned> <removeAbandonedTimeout>180</removeAbandonedTimeout> <logAbandoned>true</logAbandoned> </datasourceProperty> </connectionPool> <connectionPool name="5000"> <dbtype>MYSQL</dbtype> <driverClassName>com.mysql.jdbc.Driver</driverClassName> <url>jdbc:mysql://localhost:3306/springok</url> <username>root</username> <password></password> <datasourceProperty> <defaultAutoCommit>false</defaultAutoCommit> <initialSize>10</initialSize> <maxActive>10</maxActive> <maxIdle>5</maxIdle> <minIdle>5</minIdle> <maxWait>3000</maxWait> <validationQuery>select 1</validationQuery> <testOnBorrow>true</testOnBorrow> <removeAbandoned>true</removeAbandoned> <removeAbandonedTimeout>180</removeAbandonedTimeout> <logAbandoned>true</logAbandoned> </datasourceProperty> </connectionPool> </da2s-configuration>
1.1.3. DataSourceManager的实现(核心部分)
package cn.xhgg.test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.util.Enumeration; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.sql.DataSource; import org.apache.commons.configuration.XMLConfiguration; import org.apache.commons.dbcp2.BasicDataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; public class DataSourceManager { private final static Logger log = LoggerFactory.getLogger(DataSourceManager.class); static private ConcurrentHashMap<String, DataSource> pools = new ConcurrentHashMap<>(); static private ConcurrentHashMap<String, String> dbTypes = new ConcurrentHashMap<>(); static private String catalina_base = System.getProperty("catalina.base"); static private String log_file_name = "dbcp2_exception.log"; static private String DefaultConnectionPoolName = null; /** * 建构函数私有以防止其它对象创建本类实例 */ private DataSourceManager() { initDataSource(); } /** * 返回唯一实例.如果是第一次调用此方法,则创建实例 * * @return DBConnectionManager 唯一实例 */ static public DataSourceManager getInstance() { return DataSourceManager2Holder.instance; } /** 该类的一个对象,整个系统公用这一个对象。 */ private static class DataSourceManager2Holder { private static DataSourceManager instance = new DataSourceManager(); } /** * 根据指定属性创建连接池实例. * * @param props * 连接池属性 */ private void initDataSource() { XMLConfiguration config; try { config = new XMLConfiguration("da2s.xml"); config.setThrowExceptionOnMissing(false); } catch (org.apache.commons.configuration.ConfigurationException exc) { log.error("GlobalConfigurationException", exc); throw new RuntimeException(exc); } DefaultConnectionPoolName = config.getString("DefaultConnectionPool"); // 该项未配置,则值为null log.debug("DefaultConnectionPoolName is " + DefaultConnectionPoolName + "..."); List<?> poolList = config.getList("connectionPool.dbtype"); String connPoolName = new String(); String dbtype = new String(); String driverClassName = new String(); String url = new String(); String username = new String(); String password = new String(); boolean defaultAutoCommit = false; boolean defaultReadOnly = false; int initialSize = 0; int maxActive = 0; int maxIdle = 0; int minIdle = 0; long maxWait = 0; String validationQuery = new String(); boolean testOnBorrow = true; boolean removeAbandoned = true; int removeAbandonedTimeout = 0; boolean logAbandoned = true; long maxConnLifetimeMillis = 0; try { for (int i = 0, j = poolList.size(); i < j; i++) { connPoolName = config.getString("connectionPool(" + i + ")[@name]"); dbtype = config.getString("connectionPool(" + i + ").dbtype"); driverClassName = config.getString("connectionPool(" + i + ").driverClassName"); url = config.getString("connectionPool(" + i + ").url"); username = config.getString("connectionPool(" + i + ").username"); password = config.getString("connectionPool(" + i + ").password"); defaultAutoCommit = config.getBoolean("connectionPool(" + i + ").datasourceProperty.defaultAutoCommit", false); defaultReadOnly = config.getBoolean("connectionPool(" + i + ").datasourceProperty.defaultReadOnly", false); initialSize = config.getInt("connectionPool(" + i + ").datasourceProperty.initialSize", 3); maxActive = config.getInt("connectionPool(" + i + ").datasourceProperty.maxActive", 50); maxIdle = config.getInt("connectionPool(" + i + ").datasourceProperty.maxIdle", 20); minIdle = config.getInt("connectionPool(" + i + ").datasourceProperty.minIdle", 5); maxWait = config.getLong("connectionPool(" + i + ").datasourceProperty.maxWait", 3000); maxConnLifetimeMillis = config.getLong("connectionPool(" + i + ").datasourceProperty.maxLifetime", 600000);// 10分钟 validationQuery = config.getString("connectionPool(" + i + ").datasourceProperty.validationQuery", "select 1"); testOnBorrow = config.getBoolean("connectionPool(" + i + ").datasourceProperty.testOnBorrow", true); removeAbandoned = config.getBoolean("connectionPool(" + i + ").datasourceProperty.removeAbandoned", true); removeAbandonedTimeout = config.getInt("connectionPool(" + i + ").datasourceProperty.removeAbandonedTimeout", 180); logAbandoned = config.getBoolean("connectionPool(" + i + ").datasourceProperty.logAbandoned", true); BasicDataSource bds2 = new BasicDataSource(); bds2.setDriverClassName(driverClassName); bds2.setUrl(url); bds2.setUsername(username); bds2.setPassword(password); bds2.setDefaultAutoCommit(defaultAutoCommit); bds2.setDefaultReadOnly(defaultReadOnly); bds2.setDefaultTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); // 初始化连接数 bds2.setInitialSize(initialSize); // 最小空闲连接 bds2.setMinIdle(minIdle); // 最大空闲连接 bds2.setMaxIdle(maxIdle); // 超时回收时间(以毫秒为单位) bds2.setMaxWaitMillis(maxWait); // 最大连接数 bds2.setMaxTotal(maxActive); bds2.setTestOnBorrow(testOnBorrow); bds2.setValidationQuery(validationQuery); // 一个连接的最大存活毫秒数。如果超过这个时间,则连接在下次激活、钝化、校验时都将会失败。如果设置为0或小于0的值,则连接的存活时间是无限的。 bds2.setMaxConnLifetimeMillis(maxConnLifetimeMillis); // 空闲对象驱逐线程运行时的休眠毫秒数,如果设置为非正数,则不运行空闲对象驱逐线程。 long timeBetweenEvictionRunsMillis = 1000; bds2.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); // 超时取回 bds2.setLogAbandoned(logAbandoned); bds2.setAbandonedUsageTracking(logAbandoned); bds2.setRemoveAbandonedOnMaintenance(removeAbandoned); bds2.setRemoveAbandonedOnBorrow(removeAbandoned); bds2.setRemoveAbandonedTimeout(removeAbandonedTimeout); pools.put(connPoolName, bds2); dbTypes.put(connPoolName, dbtype); log.debug("Init DataSource " + connPoolName + "..."); } } catch (Exception e) { log.error("Init DataSource " + connPoolName + "...ERROR", e); throw new RuntimeException(e); } } /** * 动态添加连接池 */ public boolean addDataSource(String key, DataSource datasource) { pools.put(key, datasource); return true; } /** * 动态删除连接池 */ public void removeDataSource(String key) { if (key == null) return; BasicDataSource cds = (BasicDataSource) pools.remove(key); try { cds.close(); } catch (SQLException e) { log.error("Close DS Error key={}", key, e); } cds = null; } /** * 获取一个默认的可用连接. * * @return DataSource */ public DataSource getDataSource() { if (DefaultConnectionPoolName == null || DefaultConnectionPoolName.trim().isEmpty()) return null; else return (DataSource) pools.get(DefaultConnectionPoolName); } /** * 获取一个可用连接. * * @param name * 连接池名字 * @return DataSource */ public DataSource getDataSource(String name) { return (DataSource) pools.get(name); } /** * close all connection. <br> * 关闭所有闲置连接. */ public synchronized void shutdown() { Enumeration<String> allkeys = pools.keys(); while (allkeys.hasMoreElements()) { String poolName = (String) allkeys.nextElement(); log.warn("DataSourceManager shutdown pool[{}]...", poolName); BasicDataSource cpds = (BasicDataSource) pools.remove(poolName); try { cpds.close(); } catch (SQLException e) { log.error("Close DS Error key={}", poolName, e); } cpds = null; dbTypes.remove(poolName); } } public String getDefaultDataSourceName() { return DefaultConnectionPoolName; } public String getDBType(String name) { return (String) dbTypes.get(name); } public Set<String> getPoolNames() { return pools.keySet(); } /** * 获取一个默认的可用连接. * * @return DataSource */ public Connection getConnection() { return getConnection(DefaultConnectionPoolName); } /** * 获取一个可用连接. * * @param name * 连接池名字 * @return DataSource */ public Connection getConnection(String name) { // Objects.requireNonNull(name, "PoolName should not be null"); Preconditions.checkNotNull(name, "PoolName should not be null"); Preconditions.checkArgument(!name.trim().isEmpty(), "PoolName should not be empty"); BasicDataSource thisDS = (BasicDataSource)pools.get(name); //String dbType = dbTypes.get(name); Preconditions.checkState(thisDS != null, "DataSource " + name + " is null"); Preconditions.checkState(!thisDS.isClosed(), "DataSource " + name + " has closed"); try { Connection conn = thisDS.getConnection(); //setCallerInfo(conn); return conn; } catch (SQLException e) { throw new RuntimeException(e); } } /** * 获取直接的数据库conn * * @param driverClassName * @param dbUrl * @param userName * @param password * @return * @throws InstantiationException * @throws IllegalAccessException * @throws ClassNotFoundException * @throws SQLException */ public static Connection getDirectJDBCConnection(String driverClassName, String dbUrl, String userName, String password) throws InstantiationException, IllegalAccessException, ClassNotFoundException, SQLException { Class.forName(driverClassName).newInstance(); Connection conn = DriverManager.getConnection(dbUrl, userName, password); return conn; } }
1.1.4. JdbcUtils工具类实现
package cn.xhgg.test; import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; public class JdbcUtils { // 使用ThreadLocal存储当前线程中的Connection对象 private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>(); /** * 获取读数据源 * @return * @throws SQLException */ public static DataSource getReadDataSource(){ return DataSourceManager.getInstance().getDataSource("3000"); } /** * 获取写数据源 * @return */ public static DataSource getWriteDataSource(){ return DataSourceManager.getInstance().getDataSource("3000"); } public static Connection getConnection() throws SQLException { // 从当前线程中获取Connection Connection conn = threadLocal.get(); if (conn == null) { throw new SQLException("no connection init"); } return conn; } public static Connection getConnection(boolean isCreate) throws SQLException { // 从当前线程中获取Connection Connection conn = threadLocal.get(); if (conn == null&&isCreate) { loadReadConnection(); } return getConnection(); } /** * @Method: startTransaction * @Description: 开启事务 * */ public static void loadReadConnection() { try { Connection conn = threadLocal.get(); if (conn == null) { conn = getReadDataSource().getConnection(); // 把 conn绑定到当前线程上 threadLocal.set(conn); } // 开启事务 conn.setAutoCommit(false); } catch (Exception e) { throw new RuntimeException(e); } } /** * @Method: startTransaction * @Description: 开启事务 * */ public static void startTransaction() { try { Connection conn = threadLocal.get(); if (conn == null) { conn = getWriteDataSource().getConnection(); // 把 conn绑定到当前线程上 threadLocal.set(conn); } // 开启事务 conn.setAutoCommit(false); } catch (Exception e) { throw new RuntimeException(e); } } /** * @Method: rollback * @Description:回滚事务 * */ public static void rollback() { try { // 从当前线程中获取Connection Connection conn = threadLocal.get(); if (conn != null) { // 回滚事务 conn.rollback(); } } catch (Exception e) { throw new RuntimeException(e); } } /** * @Method: commit * @Description:提交事务 * */ public static void commit() { try { // 从当前线程中获取Connection Connection conn = threadLocal.get(); if (conn != null) { conn.commit(); } } catch (Exception e) { throw new RuntimeException(e); } } /** * @Method: close * @Description:关闭数据库连接(注意,并不是真的关闭,而是把连接还给数据库连接池) * */ public static void close() { try { // 从当前线程中获取Connection Connection conn = threadLocal.get(); if (conn != null) { conn.close(); // 解除当前线程上绑定conn threadLocal.remove(); } } catch (Exception e) { throw new RuntimeException(e); } } public static boolean isClosed(){ try { // 从当前线程中获取Connection Connection conn = threadLocal.get(); if (conn != null) { return false; } } catch (Exception e) { throw new RuntimeException(e); } return true; } }
1.1.5. 测试
Connection connection = JdbcUtils.getConnection(true); System.out.println(connection);
输出如下图:
测试OK。
1.1.6. 思考获取数据库连接与ThreadLocal
用户的请求处理过程: controller层-->>service层-->>dao层。
所以先实例化controller层并初始化service实例对象,调用service方法,service实例化依赖的dao层。并进行数据库的操作。然后依次返回。
OpenSessionInView模式不就是解决session关闭,不能在层之间传递的问题。
逻辑如下图: