JDBC工具类——JdbcUtils(1)

JDBC工具类——JdbcUtils(1)

前言

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

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

分离数据库配置

根据上一篇文章的分析,我们知道,不应该把数据库配置信息硬编码在Java代码中,因为这会导致维护困难。为了解决这个问题,可以把数据库的配置信息单独放在一个外部的配置文件中,然后工具类通过读取配置文件来获取数据库配置。

数据库的配置信息主要包括数据库驱动类名、连接字符串、用户名、密码,这四个配置可以单独放在一个db.properties文件中,而db.properties文件可以放在resources文件夹中,这样工具类就可以从classpath下读取配置文件

以下是db.properties的内容:

# JDBC驱动类
jdbc.driver=org.sqlite.JDBC
# 连接字符串
jdbc.url=jdbc:sqlite::resource:test.db
# 用户名
jdbc.username=
# 密码
jdbc.password=

JdbcUtils的静态代码块中读取db.properties中的相关配置,并加载驱动:

public class JdbcUtils
{
    private static String url;
    private static String username;
    private static String password;

    static
    {
        try
        {
            // 读取resources目录下的db.properties文件
            InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties");
            Properties properties = new Properties();
            properties.load(in);

            // 读取驱动类名、连接字符串、用户名和密码
            String driver = properties.getProperty("jdbc.driver");
            url = properties.getProperty("jdbc.url");
            username = properties.getProperty("jdbc.username");
            password = properties.getProperty("jdbc.password");

            // 加载驱动
            Class.forName(driver);
        }
        catch (NullPointerException | IOException e)
        {
            throw new RuntimeException("找不到db.properties文件,请在resources目录下创建db.properties文件,并写入数据库配置", e);
        }
        catch (ClassNotFoundException e)
        {
            throw new RuntimeException("找不到数据库驱动类", e);
        }
    }
    ...
}

封装连接获取

每次使用JDBC之前都要获取连接,而获取连接的代码都是固定的,因此可以提取成一个公共方法。

JdbcUtils中封装一个getConnection方法用于获取连接:

public class JdbcUtils
{
    ...
    public static Connection getConnection() throws SQLException
    {
        return DriverManager.getConnection(url, username, password);
    }
    ...
}

客户代码可以通过调用JdbcUtils.getConnection()来获取连接。

注意,当客户代码第一次调用JdbcUtils中的方法时,JdbcUtils中的静态代码块将会执行,此时数据库的配置信息会被读取,JdbcUtils.getConnection也就能成功获取到连接。

封装资源释放

在查询操作中,需要依次释放ResultSetStatementConnection

在更新操作中,需要依次释放StatementConnection

针对查询和更新操作对释放资源的不同处理,在JdbcUtils中添加两个close方法

public class JdbcUtils
{
    ...
    public static void close(ResultSet rs, Statement stmt, Connection conn)
    {
        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) {}
    }

    public static void close(Statement stmt, Connection conn)
    {
        if (stmt != null) try { stmt.close(); } catch (SQLException ignored) {}
        if (conn != null) try { conn.close(); } catch (SQLException ignored) {}
    }
    ...
}

查询操作中可以通过调用JdbcUtils.close(rs, stmt, conn)来释放资源,而更新操作中可以通过调用JdbcUtils.close(stmt, conn)来释放资源

封装语句创建

首先观察一段典型的创建语句代码:

String sql = "INSERT INTO users(username, password) VALUES(?, ?)";
stmt = conn.prepareStatement(sql);
stmt.setObject(1, "byx");
stmt.setObject(2, "123456");

基本流程如下:

  • 得到一条带参数的sql语句
  • 从连接获取PreparedStatement
  • 设置PreparedStatement的参数

其中,sql语句以及参数应该由用户程序指定,而conn来自之前JdbcUtils.getConnection方法,所以可以将他们都设置为方法参数。

JdbcUtils中添加一个createPreparedStatement方法,用于创建PreparedStatement

public class JdbcUtils
{
    ...
    public static PreparedStatement createPreparedStatement(Connection conn, String sql, Object... params) 
        throws SQLException
    {
        PreparedStatement stmt = conn.prepareStatement(sql);
        for (int i = 0; i < params.length; ++i)
        {
            stmt.setObject(i + 1, params[i]);
        }
        return stmt;
    }
    ...
}

其中,conn表示数据库连接,sql表示带参数的sql字符串,params是一个不定参数数组,用于传递sql中的参数值。

外部程序可以通过调用JdbcUtils.createPreparedStatement(conn, "XXX", ...)来创建一个PreparedStatement

总结

到目前为止,我们已经成功将JDBC操作中的公共代码抽取成了几个独立的函数,JDBC的使用已经得到一定的简化了

查询操作的代码如下:

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

try
{
    // 获取连接
    conn = JdbcUtils.getConnection();

    // 构造语句
    stmt = JdbcUtils.createPreparedStatement(conn,
            "SELECT * FROM users WHERE password = ?",
            "456");

    // 执行语句,获取结果集
    rs = stmt.executeQuery();

    // 处理结果集
    while (rs.next())
    {
        ...
    }
}
catch (Exception e)
{
    // 处理异常
    ...
}
finally
{
    // 释放资源
    JdbcUtils.close(rs, stmt, conn);
}

更新操作的代码如下:

Connection conn = null;
PreparedStatement stmt = null;

try
{
    // 获取连接
    conn = JdbcUtils.getConnection();

    // 构造语句
    stmt = JdbcUtils.createPreparedStatement(conn,
            "INSERT INTO users(username, password) VALUES(?, ?)",
            "byx", "123456");

    // 执行语句,获取影响行数
    int count = stmt.executeUpdate();
}
catch (Exception e)
{
    // 处理异常
    ...
}
finally
{
    // 释放资源
    JdbcUtils.close(stmt, conn);
}

但是,这样的封装还是非常浅的,因为代码中还存在着许多结构上的重复。例如,所有查询操作都需要依次进行获取连接、创建语句、处理结果集、释放资源这几个操作,其中只有处理结果集是会变化的,其他步骤对于所有查询操作来说都是相同的。同时,整个操作过程需要用try{...}catch{...}finally{...}结构包起来,这些结构上的重复无法通过提取公共方法的方式抽象出来。

在下一篇文章中,将介绍如何对JDBC使用中出现的重复结构进行封装。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

byx2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值