Browse Source

优化数据库连接的缓存,并且在数据源信息被修改后,自动重建连接

sunyj 8 years ago
parent
commit
7438e588a5

+ 13 - 37
kanban-console/src/main/java/com/uas/kanban/model/DataSource.java

@@ -1,10 +1,9 @@
 package com.uas.kanban.model;
 
-import java.sql.SQLException;
+import java.util.Objects;
 
 import org.mongodb.morphia.annotations.Entity;
 
-import com.alibaba.druid.pool.DruidDataSource;
 import com.uas.kanban.annotation.FieldProperty;
 import com.uas.kanban.base.BaseEntity;
 
@@ -93,40 +92,17 @@ public class DataSource extends BaseEntity {
 				+ lastModified + ", version=" + version + ", code=" + code + "]";
 	}
 
-	public DruidDataSource toDruidDataSource() throws SQLException {
-		DruidDataSource druidDataSource = new DruidDataSource();
-		druidDataSource.setDriverClassName(driverClassName);
-		druidDataSource.setUrl(url);
-		druidDataSource.setUsername(username);
-		druidDataSource.setPassword(password);
-		// 配置初始化大小、最小、最大
-		druidDataSource.setInitialSize(1);
-		druidDataSource.setMinIdle(1);
-		druidDataSource.setMaxActive(10);
-		// 配置获取连接等待超时的时间
-		druidDataSource.setMaxWait(60000);
-		// 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-		druidDataSource.setTimeBetweenEvictionRunsMillis(6000);
-		// 配置一个连接在池中最小生存的时间,单位是毫秒
-		druidDataSource.setMinEvictableIdleTimeMillis(300000);
-		druidDataSource.setValidationQuery("SELECT 1 FROM DUAL");
-		druidDataSource.setTestWhileIdle(true);
-		druidDataSource.setTestOnBorrow(true);
-		druidDataSource.setTestOnReturn(false);
-		// 连接泄漏处理,Druid提供了RemoveAbandanded相关配置,用来关闭长时间不使用的连接(例如忘记关闭连接)
-		druidDataSource.setRemoveAbandoned(true);
-		druidDataSource.setRemoveAbandonedTimeout(1800);
-		// 关闭abanded连接时输出错误日志
-		druidDataSource.setLogAbandoned(true);
-		// 打开PSCache,并且指定每个连接上PSCache的大小
-		druidDataSource.setPoolPreparedStatements(true);
-		druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
-		druidDataSource.setTimeBetweenLogStatsMillis(60000);
-		// 配置监控统计拦截的filters, 监控统计:"stat",防SQL注入:"wall",组合使用: "stat,wall"
-		druidDataSource.setFilters("stat,slf4j,wall");
-		druidDataSource.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");
-		druidDataSource.init();
-		return druidDataSource;
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj == null || getClass() != obj.getClass() || !(obj instanceof DataSource)) {
+			return false;
+		}
+		DataSource other = (DataSource) obj;
+		return Objects.equals(code, other.getCode()) && Objects.equals(name, other.getName())
+				&& Objects.equals(driverClassName, other.getDriverClassName()) && Objects.equals(url, other.getUrl())
+				&& Objects.equals(username, other.getUsername()) && Objects.equals(password, other.getPassword());
 	}
-
 }

+ 111 - 20
kanban-console/src/main/java/com/uas/kanban/support/DataSourceManager.java

@@ -3,6 +3,7 @@ package com.uas.kanban.support;
 import java.sql.SQLException;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
@@ -28,44 +29,134 @@ public class DataSourceManager {
 	private BaseDao<DataSource> dataSourceDao;
 
 	/**
-	 * 以数据源 code 为键,缓存 DruidDataSource 数据库连接,避免重复建立 DruidDataSource
+	 * 以数据源 code 为键,缓存数据库连接,避免重复建立连接
 	 */
-	private Map<String, DruidDataSource> dataSources = new HashMap<>();
+	private Map<String, DataSourceCache> dataSourceCaches = new HashMap<>();
 
 	/**
-	 * 根据数据源 {@link DataSource} 的 code 获取 DruidDataSource
+	 * 根据数据源 {@link DataSource} 的 code 获取 NewbieJdbc 对象
 	 * 
 	 * @param dataSourceCode
 	 *            数据源 code
-	 * @return DruidDataSource 对象
+	 * @return NewbieJdbc 对象
 	 * @throws SQLException
 	 */
-	public DruidDataSource getDataSource(@NotEmpty("dataSourceCode") String dataSourceCode) throws SQLException {
-		DruidDataSource druidDataSource = dataSources.get(dataSourceCode);
-		if (druidDataSource != null) {
-			return druidDataSource;
-		}
+	public NewbieJdbc getJdbc(@NotEmpty("dataSourceCode") String dataSourceCode) throws SQLException {
+		return getCache(dataSourceCode).getJdbc();
+	}
+
+	/**
+	 * 根据数据源 {@link DataSource} 的 code 获取数据库连接缓存
+	 * 
+	 * @param dataSourceCode
+	 *            数据源 code
+	 * @return 数据库连接缓存
+	 * @throws SQLException
+	 */
+	private DataSourceCache getCache(@NotEmpty("dataSourceCode") String dataSourceCode) throws SQLException {
+		DataSourceCache dataSourceCache = dataSourceCaches.get(dataSourceCode);
 		DataSource dataSource = dataSourceDao.findOne(dataSourceCode);
 		if (dataSource == null) {
 			throw new IllegalStateException("数据源不存在:" + dataSourceCode);
 		}
 		dataSourceDao.checkFields(dataSource);
-		druidDataSource = dataSource.toDruidDataSource();
-		dataSources.put(dataSourceCode, druidDataSource);
-		return druidDataSource;
+		// 存在数据库连接缓存
+		if (dataSourceCache != null) {
+			// 数据源信息没有更改,则使用缓存
+			if (Objects.equals(dataSourceCache.getDataSource(), dataSource)) {
+				return dataSourceCache;
+			} else {
+				// 否则先清除旧的连接,再新建连接
+				dataSourceCache.getDruidDataSource().close();
+			}
+		}
+		dataSourceCache = new DataSourceCache(dataSource);
+		dataSourceCaches.put(dataSourceCode, dataSourceCache);
+		return dataSourceCache;
 	}
 
 	/**
-	 * 根据数据源 {@link DataSource} 的 code 获取 NewbieJdbc 对象
+	 * 数据库连接缓存
 	 * 
-	 * @param dataSourceCode
-	 *            数据源 code
-	 * @return NewbieJdbc 对象
-	 * @throws SQLException
+	 * @author sunyj
+	 * @since 2017年9月15日 下午9:12:32
 	 */
-	public NewbieJdbc getJdbc(@NotEmpty("dataSourceCode") String dataSourceCode) throws SQLException {
-		DruidDataSource druidDataSource = getDataSource(dataSourceCode);
-		return new NewbieJdbcSupport(druidDataSource);
+	private class DataSourceCache {
+
+		/**
+		 * 数据源信息
+		 */
+		private DataSource dataSource;
+
+		/**
+		 * 数据库连接
+		 */
+		private DruidDataSource druidDataSource;
+
+		/**
+		 * jdbc
+		 */
+		private NewbieJdbcSupport jdbc;
+
+		public DataSourceCache(@NotEmpty("dataSource") DataSource dataSource) {
+			this.dataSource = dataSource;
+		}
+
+		public DataSource getDataSource() {
+			return dataSource;
+		}
+
+		public DruidDataSource getDruidDataSource() throws SQLException {
+			if (druidDataSource == null) {
+				druidDataSource = toDruidDataSource();
+				// 如果在 toDruidDataSource 方法里 init,出现异常的话,druidDataSource 会一直为
+				// null,这样会不断创建连接,因此先使 druidDataSource 不为 null ,再 init
+				druidDataSource.init();
+			}
+			return druidDataSource;
+		}
+
+		public NewbieJdbcSupport getJdbc() throws SQLException {
+			if (jdbc == null) {
+				jdbc = new NewbieJdbcSupport(getDruidDataSource());
+			}
+			return jdbc;
+		}
+
+		private DruidDataSource toDruidDataSource() throws SQLException {
+			DruidDataSource ds = new DruidDataSource();
+			ds.setDriverClassName(dataSource.getDriverClassName());
+			ds.setUrl(dataSource.getUrl());
+			ds.setUsername(dataSource.getUsername());
+			ds.setPassword(dataSource.getPassword());
+			// 配置初始化大小、最小、最大
+			ds.setInitialSize(1);
+			ds.setMinIdle(1);
+			ds.setMaxActive(10);
+			// 配置获取连接等待超时的时间
+			ds.setMaxWait(60000);
+			// 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+			ds.setTimeBetweenEvictionRunsMillis(6000);
+			// 配置一个连接在池中最小生存的时间,单位是毫秒
+			ds.setMinEvictableIdleTimeMillis(300000);
+			ds.setValidationQuery("SELECT 1 FROM DUAL");
+			ds.setTestWhileIdle(true);
+			ds.setTestOnBorrow(true);
+			ds.setTestOnReturn(false);
+			// 连接泄漏处理,Druid提供了RemoveAbandanded相关配置,用来关闭长时间不使用的连接(例如忘记关闭连接)
+			ds.setRemoveAbandoned(true);
+			ds.setRemoveAbandonedTimeout(1800);
+			// 关闭abanded连接时输出错误日志
+			ds.setLogAbandoned(true);
+			// 打开PSCache,并且指定每个连接上PSCache的大小
+			ds.setPoolPreparedStatements(true);
+			ds.setMaxPoolPreparedStatementPerConnectionSize(20);
+			ds.setTimeBetweenLogStatsMillis(60000);
+			// 配置监控统计拦截的filters, 监控统计:"stat",防SQL注入:"wall",组合使用: "stat,wall"
+			ds.setFilters("stat,slf4j,wall");
+			ds.setConnectionProperties("druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000");
+			return ds;
+		}
 	}
 
 }