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也就能成功获取到连接。
封装资源释放
在查询操作中,需要依次释放ResultSet、Statement和Connection。
在更新操作中,需要依次释放Statement和Connection。
针对查询和更新操作对释放资源的不同处理,在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使用中出现的重复结构进行封装。