JDBC工具类——JdbcUtils(0)

JDBC工具类——JdbcUtils(0)

前言

本系列文章介绍JDBC工具类——JdbcUtils的封装,部分实现参考了Spring框架的JdbcTemplate

完整项目地址:https://github.com/byx2000/JdbcUtils

JDBC使用示例

在JDBC中,一共有查询更新两种操作。

下面是一段查询操作的代码:

Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;

try
{
    // 加载驱动
    Class.forName("org.sqlite.JDBC");

    // 获取连接
    conn = DriverManager.getConnection("jdbc:sqlite::resource:test.db", "", "");

    // 构造语句
    String sql = "SELECT * FROM users WHERE password = ?";
    stmt = conn.prepareStatement(sql);
    stmt.setObject(1, "456");
    
    // 执行语句,获取结果集
    rs = stmt.executeQuery();

    // 处理结果集
    while (rs.next())
    {
        ...
    }
}
catch (Exception e)
{
    // 处理异常
    ...
}
finally
{
    // 释放资源
    if (rs != null)   try { rs.close(); }   catch (SQLException ignored) {}
    if (stmt != null) try { stmt.close(); } catch (SQLException ignored) {}
    if (conn != null) try { conn.close(); } catch (SQLException ignored) {}
}

下面是一段更新操作的代码:

Connection conn = null;
PreparedStatement stmt = null;

try
{
    // 加载驱动
    Class.forName("org.sqlite.JDBC");

    // 获取连接
    conn = DriverManager.getConnection("jdbc:sqlite::resource:test.db", "", "");

    // 构造语句
    String sql = "INSERT INTO users(username, password) VALUES(?, ?)";
    stmt = conn.prepareStatement(sql);
    stmt.setObject(1, "byx");
    stmt.setObject(2, "123456");
    
    // 执行语句,获取影响行数
    int count = stmt.executeUpdate();
}
catch (Exception e)
{
    // 处理异常
    ...
}
finally
{
    // 释放资源
    if (stmt != null) try { stmt.close(); } catch (SQLException ignored) {}
    if (conn != null) try { conn.close(); } catch (SQLException ignored) {}
}

上面两段代码存在着许多“坏味道”。

数据库配置

使用JDBC操作数据库之前,需要加载数据库驱动和获取连接,加载数据库驱动需要数据库驱动类名,获取连接需要指定连接字符串用户名密码。在上面两段代码中,这些配置信息都是硬编码在Java代码中的。

// 加载驱动
Class.forName("org.sqlite.JDBC");
// 获取连接
conn = DriverManager.getConnection("jdbc:sqlite::resource:test.db", "", "");

这样会有什么问题呢?主要有以下两个方面:

  • 首先,在开发阶段,为了方便调试,我们的项目连接的是自己电脑上的测试数据库;到了项目部署时,则需要连接到生产数据库,这时就需要修改数据库配置信息。如果这些配置信息是写在Java代码里的,我们就要修改相应的Java代码,然后重新编译程序。这样非常麻烦,只要切换运行环境,就意味着要重新编译项目,为什么不能“一次编译,到处运行”呢?
  • 另一方面,在大型项目中,开发人员和部署人员是分开的,而部署人员不一定看得懂Java代码,所以也不能指望部署人员替我们修改Java代码中的配置信息。

中间步骤

每次使用JDBC时,都需要遵循一些基本步骤:

  • 加载驱动
  • 获取连接
  • 构造语句
  • 执行语句
  • 处理结果集(更新操作没有此步)
  • 释放资源

这些步骤的出现顺序是固定的,而且代码结构也很相似,如果项目中多次使用了JDBC,那么就会产生大量重复代码。

结果集处理

对于JDBC的查询操作来说,得到查询结果后,还需要对结果集进行处理。虽然每一段客户程序都可能对结果集进行不同的处理。但是在某些时候,它们的处理过程是大同小异的。

例如,其中一段客户代码对结果集的处理如下:

List<User> users = new ArrayList<>();
while (rs.next())
{
    User user = new User();
    user.setId(rs.getInt("id"));
    user.setUsername(rs.getString("username"));
    user.setPassword(rs.getString("password"));
    users.add(user);
}

另一段客户代码对结果集的处理如下:

List<Book> books = new ArrayList<>();
while (rs.next())
{
    Book book = new Book();
    book.setId(rs.getInt("id"));
    book.setName(rs.getString("name"));
    book.setAuthor(rs.getString("author"));
    books.add(book);
}

这两段代码虽然是不同的,但它们本质上都在做同一件事:把结果集的每一行转换成一个JavaBean,然后把每一行数据封装成一个列表。这也是一种代码重复,但是这种重复怎么消除呢?

资源释放

使用完JDBC后,需要释放各种资源,包括ConnectionStatementResultSet(如果是更新操作,则不用释放ResultSet)。

finally
{
    // 释放资源
    if (rs != null)   try { rs.close(); }   catch (SQLException ignored) {}
    if (stmt != null) try { stmt.close(); } catch (SQLException ignored) {}
    if (conn != null) try { conn.close(); } catch (SQLException ignored) {}
}

资源释放的顺序是有讲究的,需要与资源获取的顺序相反。更要命的是,这些资源的close方法还会抛出异常,所以上面代码的finally块中出现了嵌套的try{...}catch{...}结构。这些释放资源的代码十分容易出错和遗忘。

异常处理

由于JDBC的API抛出的SQLException是检查异常,因此每段使用JDBC的代码都存在一个异常处理结构:

try
{
    // 操作数据库
}
catch (SQLException e)
{
    // 异常处理
}
finally
{
    // 释放资源
}

虽然对异常进行处理是良好的编程习惯,但是如果在每个操作数据库的地方都套一层又臭又长的异常处理,那也太麻烦了。事实上,我们往往有更加优雅的异常处理方法(如果使用Spring框架,那么可以使用Spring中的AOP在项目最高层对异常进行统一处理,而不需要单独在每个数据库访问方法中进行处理)。

总结

上面对原生的JDBC API使用中出现的问题和不便进行了分析,在接下来的文章中,将会逐步地解决这些问题。

posted @ 2021-01-26 11:58  baiyuxuan  阅读(65)  评论(0编辑  收藏  举报