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后,需要释放各种资源,包括Connection
、Statement
和ResultSet
(如果是更新操作,则不用释放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使用中出现的问题和不便进行了分析,在接下来的文章中,将会逐步地解决这些问题。