目录
一、功能背景
在SpringBoot项目中使用第三方数据库进行业务处理,需手动配置连接信息,手段创建连接进行操作第三方数据库。
二、错误信息
Caused by: com.mysql.cj.exceptions.ConnectionIsClosedException: No operations allowed after connection closed.
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:105)
at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:151)
at com.mysql.cj.NativeSession.checkClosed(NativeSession.java:1171)
at com.mysql.cj.jdbc.ConnectionImpl.checkClosed(ConnectionImpl.java:573)
at com.mysql.cj.jdbc.ConnectionImpl.prepareStatement(ConnectionImpl.java:1595)
... 96 common frames omitted
三、相关代码
public List<Map<String, Object>> getDetailData() throws Exception {
String jdbcUrl = "jdbc:mysql://xxxxxxx:3306/demo";
String driverName = "com.mysql.cj.jdbc.Driver";
String username = "";
String password = "";
DBHelper dbHelper = new DBHelper(jdbcUrl, driverName, username, Base64.decodeStr(password));
Connection connection = dbHelper.conn;
if (ObjectUtil.isEmpty(connection)) {
throw new Exception("数据库连接异常");
}
String variablesSql = "show variables like 'lower_case_table_names'";
// 预执行加载
PreparedStatement pst = null;
// 结果集
ResultSet resultSet = null;
List<Map<String, Object>> list = new ArrayList<>();
try {
pst = connection.prepareStatement(variablesSql);
resultSet = pst.executeQuery();
ResultSetMetaData rsmd = resultSet.getMetaData();
int columnCount = rsmd.getColumnCount();
while (resultSet.next()) {
Map<String, Object> valueMap = new LinkedHashMap();
for (int i = 1; i <= columnCount; i++) {
// 通过序号获取列名,起始值为1
String columnName = rsmd.getColumnLabel(i);
// 通过列名获取值
String columnValue = resultSet.getString(columnName);
valueMap.put(columnName, columnValue);
}
list.add(valueMap);
}
} catch (Exception e) {
log.error("关闭数据库连接,出现异常", e);
throw new Exception("获取表信息,出现异常", e);
} finally {
try {
if (Objects.nonNull(resultSet)) {
resultSet.close();
}
if (Objects.nonNull(pst)) {
pst.close();
}
if (Objects.nonNull(connection)) {
connection.close();
}
} catch (Exception e) {
log.error("关闭数据库连接,出现异常", e);
}
}
return list;
}
四、问题原因
Mysql服务器默认的“wait_timeout”是8小时,也就是说一个connection空闲超过8个小时,Mysql将自动断开该connection。这就是问题的所在,在C3P0 pools中的connections如果空闲超过8小时,Mysql将其断开,而C3P0并不知道该connection已经失效,如果这时有Client请求connection,C3P0将该失效的Connection提供给Client,将会造成上面的异常。
四、解决办法
1、修改dbDriver
把spring.datasource.driverClassName修改为:com.mysql.jdbc.Driver
修改后代码为:
public List<Map<String, Object>> getDetailData() throws Exception {
String jdbcUrl = "jdbc:mysql://xxxxxxx:3306/demo";
String driverName = "com.mysql.jdbc.Driver";
String username = "";
String password = "";
DBHelper dbHelper = new DBHelper(jdbcUrl, driverName, username, Base64.decodeStr(password));
Connection connection = dbHelper.conn;
if (ObjectUtil.isEmpty(connection)) {
throw new Exception("数据库连接异常");
}
String variablesSql = "show variables like 'lower_case_table_names'";
// 预执行加载
PreparedStatement pst = null;
// 结果集
ResultSet resultSet = null;
List<Map<String, Object>> list = new ArrayList<>();
try {
pst = connection.prepareStatement(variablesSql);
resultSet = pst.executeQuery();
ResultSetMetaData rsmd = resultSet.getMetaData();
int columnCount = rsmd.getColumnCount();
while (resultSet.next()) {
Map<String, Object> valueMap = new LinkedHashMap();
for (int i = 1; i <= columnCount; i++) {
// 通过序号获取列名,起始值为1
String columnName = rsmd.getColumnLabel(i);
// 通过列名获取值
String columnValue = resultSet.getString(columnName);
valueMap.put(columnName, columnValue);
}
list.add(valueMap);
}
} catch (Exception e) {
log.error("关闭数据库连接,出现异常", e);
throw new Exception("获取表信息,出现异常", e);
} finally {
try {
if (Objects.nonNull(resultSet)) {
resultSet.close();
}
if (Objects.nonNull(pst)) {
pst.close();
}
if (Objects.nonNull(connection)) {
connection.close();
}
} catch (Exception e) {
log.error("关闭数据库连接,出现异常", e);
}
}
return list;
}
2、修改数据库配置
修改服务端连接超时时间
show global variables like 'wait_timeout';
set global wait_timeout=172800;
3、通过修改配置文件信息
#连接池配置
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5
spring.datasource.validation-query=SELECT 1
spring.datasource.test-on-borrow=false
spring.datasource.test-while-idle=true
spring.datasource.time-between-eviction-runs-millis=18800
配置datasource时需要配置相应的连接池参数,定是去检查连接的有效性,定时清理无效的连接。配置完重启服务即生效。
注:第三种方案不适用本文章对应的功能,只是一种解决方案。