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使用中出现的重复结构进行封装。