携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
最近在某个开源社区的PR过程中,由于要用到对jdbc Connection的使用,因此复习了好多年前的学习内容。在印象中,JDBC的使用是java程序员的入门操作。但是这个入门操作也会难倒不少人,经常出现在初级java的面试题中。那就是关于jdbc Connection的关闭。Statement的关闭和Result的关闭过程。由于已经习惯于连接池的使用,这些基础的东西也显得不那么常见了。
1.常规的关闭连接的方法
关于jdbc的使用,有如下代码:
private void createTable(SinkInfo sinkInfo) {
List<StreamSinkFieldEntity> fieldList = fieldEntityMapper.selectBySinkId(sinkInfo.getId());
// set columns
List<MySQLColumnInfo> columnList = new ArrayList<>();
for (StreamSinkFieldEntity field : fieldList) {
MySQLColumnInfo columnInfo = new MySQLColumnInfo();
columnInfo.setName(field.getFieldName());
columnInfo.setType(field.getFieldType());
columnInfo.setComment(field.getFieldComment());
columnList.add(columnInfo);
}
MySQLSinkDTO mySQLSink = MySQLSinkDTO.getFromJson(sinkInfo.getExtParams());
MySQLTableInfo tableInfo = MySQLSinkDTO.getTableInfo(mySQLSink, columnList);
String url = mySQLSink.getJdbcUrl();
String user = mySQLSink.getUsername();
String password = mySQLSink.getPassword();
String dbName = tableInfo.getDbName();
String tableName = tableInfo.getTableName();
Connection conn = null;
try {
conn = MySQLJdbcUtils.getConnection(url, user, password);
// 1. create database if not exists
MySQLJdbcUtils.createDb(conn, dbName);
// 2. table not exists, create it
MySQLJdbcUtils.createTable(conn, tableInfo);
// 3. table exists, add columns - skip the exists columns
MySQLJdbcUtils.addColumns(conn, dbName, tableName, columnList);
} catch (Throwable e) {
String errMsg = "create MySQL table failed: " + e.getMessage();
LOG.error(errMsg, e);
throw new Exception(errMsg);
} finally {
try {
// 4. close connection.
if (null != conn) {
conn.close();
conn = null;
}
} catch (Throwable e) {
String errMsg = "close MySQL connection failed: " + e.getMessage();
throw new Exception(errMsg);
}
}
}
在上述代码中,要根据所需要的表信息,创建jdbc连接之后,判断表是否存在,对应的字段是否在表中存在,如果不存在,则创建。这个业务流程实际上非常简单。但是问题在于代码的后半部分,关于连接关闭的部分,需要一个冗长的finally来处理。而且在执行conn的close方法的过程中,还会有异常抛出。那么这时候就会产生一个新的问题,就是当catch中的异常出现的时候,抛出了异常Exception e1,假如此时如果finally中关闭connection也出现了异常,那么此时系统中又抛出了异常Exception e2,那么系统中最终只会抛出e2异常。e1异常将会被丢弃。这就是上述关闭连接的问题。
2.try-with-resource的方法
对于这个问题,在jdk 1.8中会有优化,解决办法就是 try-with-resource.具体怎么解决呢,先看代码:
private void createTable(SinkInfo sinkInfo) {
List<StreamSinkFieldEntity> fieldList = fieldEntityMapper.selectBySinkId(sinkInfo.getId());
// set columns
List<MySQLColumnInfo> columnList = new ArrayList<>();
for (StreamSinkFieldEntity field : fieldList) {
MySQLColumnInfo columnInfo = new MySQLColumnInfo(field.getFieldName(), field.getFieldType(),
field.getFieldComment());
columnList.add(columnInfo);
}
final MySQLSinkDTO mySQLSink = MySQLSinkDTO.getFromJson(sinkInfo.getExtParams());
final MySQLTableInfo tableInfo = MySQLSinkDTO.getTableInfo(mySQLSink, columnList);
try (Connection conn = MySQLJdbcUtils.getConnection(mySQLSink.getJdbcUrl(), mySQLSink.getUsername(),
mySQLSink.getPassword())) {
// 1. create database if not exists
MySQLJdbcUtils.createDb(conn, tableInfo.getDbName());
// 2. table not exists, create it
MySQLJdbcUtils.createTable(conn, tableInfo);
// 3. table exists, add columns - skip the exists columns
MySQLJdbcUtils.addColumns(conn, tableInfo.getDbName(), tableInfo.getTableName(), columnList);
} catch (Throwable e) {
String errMsg = "create MySQL table failed: " + e.getMessage();
LOG.error(errMsg, e);
throw new Exception(errMsg);
}
}
这样一来之前的代码就被优化了不少。可以避免finally代码块了。这是因为try()中的内容,在try代码块结束之后都会被自动关闭。而能够写在try()中的内容,都是实现了AutoCloseable接口的类。
3.对其他代码的优化
通过try-with-resource之后,我们还可以如下优化:
public static boolean checkColumnExist(final Connection conn, final String dbName, final String tableName,
final String column)
throws Exception {
boolean result = false;
final String checkTableSql = MySQLSqlBuilder.getCheckColumn(dbName, tableName, column);
try (Statement stmt = conn.createStatement();
ResultSet resultSet = stmt.executeQuery(checkTableSql)) {
if (Objects.nonNull(resultSet)) {
if (resultSet.next()) {
result = true;
}
}
}
return result;
}
在上述代码中,Statement和ResultSet都会被自动关闭。 在try()之中的代码,写在最后面的代码会被最先关闭。 通过这个功能,在写代码的过程中,就不需要考虑复杂的关闭过程。