从关闭JDBC连接到try-with-resources

164 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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()之中的代码,写在最后面的代码会被最先关闭。 通过这个功能,在写代码的过程中,就不需要考虑复杂的关闭过程。