zoukankan      html  css  js  c++  java
  • 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使用中出现的问题和不便进行了分析,在接下来的文章中,将会逐步地解决这些问题。

  • 相关阅读:
    复利计算器2.01
    复利计算器2.0
    0429团队3.0
    0428 团队项目合作2.0作业
    "数学口袋精灵"bug
    操作系统-实验2
    博客评论
    复利计算升级
    0408 结对合作
    0406复利计算5.0
  • 原文地址:https://www.cnblogs.com/baiyuxuan/p/14329578.html
Copyright © 2011-2022 走看看