在Java应用程序中与数据库进行交互是一项基础且核心的任务,它使得程序能够持久化存储数据、执行复杂查询并管理业务状态,这一过程的实现主要依赖于Java数据库连接(JDBC)API,这是一套由Sun Microsystems(现为Oracle)定义的标准接口,允许Java程序以统一的方式访问各种关系型数据库,下面,我们将详细探讨在Java中调用数据库的完整流程、最佳实践以及相关注意事项。

核心步骤:使用JDBC与数据库交互
使用JDBC进行数据库操作通常遵循一个固定的模式,包含以下六个关键步骤:
第一步:准备工作
在编写代码之前,必须确保项目中包含了对应数据库的JDBC驱动程序,驱动程序是数据库厂商提供的JDBC接口的具体实现,它充当了Java程序与特定数据库之间的桥梁。
- 获取驱动:可以从数据库官方网站下载JDBC驱动的JAR包,MySQL的驱动是
mysql-connector-java,PostgreSQL的驱动是postgresql-jdbc。 - 添加依赖:在现代Java项目中,通常使用构建工具如Maven或Gradle来管理依赖,只需在
pom.xml(Maven)或build.gradle(Gradle)文件中添加相应的依赖项即可,构建工具会自动下载并管理JAR包。
在Maven项目中添加MySQL驱动依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
第二步:加载驱动
在早期版本的JDBC中,需要显式地加载驱动类,这通常通过Class.forName()方法实现。
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
自JDBC 4.0(Java 6)以来,驱动程序通过Java的SPI(Service Provider Interface)机制自动注册,在现代JDBC驱动中,这一步通常是可选的,可以省略,但了解其历史和作用依然有益。
第三步:建立连接
这是与数据库进行通信的起点,需要使用DriverManager类的getConnection()方法,并提供三个参数:数据库URL、用户名和密码。
- 数据库URL:一个字符串,用于唯一标识数据库,其格式通常为:
jdbc:<subprotocol>:<subname>,MySQL的URL格式为jdbc:mysql://主机名:端口号/数据库名称。
String url = "jdbc:mysql://localhost:3306/my_database?useSSL=false&serverTimezone=UTC";
String username = "root";
String password = "your_password";
Connection connection = null;
try {
connection = DriverManager.getConnection(url, username, password);
System.out.println("数据库连接成功!");
} catch (SQLException e) {
System.err.println("数据库连接失败:" + e.getMessage());
}
第四步:创建与执行SQL语句
连接建立后,需要创建一个Statement对象来发送SQL语句到数据库,JDBC提供了三种主要的Statement对象:

| 类型 | 描述 | 适用场景 | 安全性 |
|---|---|---|---|
Statement |
用于执行静态SQL语句,简单直接。 | SQL语句固定,无参数。 | 低,易受SQL注入攻击。 |
PreparedStatement |
预编译的SQL语句,可以包含参数占位符。 | SQL语句需要多次执行,或包含外部输入参数。 | 高,有效防止SQL注入。 |
CallableStatement |
用于执行数据库存储过程。 | 调用数据库中已定义的存储过程。 | 高,取决于存储过程本身。 |
强烈推荐使用PreparedStatement,因为它不仅能防止SQL注入攻击,还能通过预编译提高执行效率。
示例:使用PreparedStatement插入数据
String sql = "INSERT INTO users (name, email) VALUES (?, ?)";
try (PreparedStatement pstmt = connection.prepareStatement(sql)) {
pstmt.setString(1, "张三");
pstmt.setString(2, "zhangsan@example.com");
int affectedRows = pstmt.executeUpdate();
System.out.println("成功插入 " + affectedRows + " 行数据。");
} catch (SQLException e) {
e.printStackTrace();
}
第五步:处理结果集
如果执行的是查询操作(SELECT),PreparedStatement的executeQuery()方法会返回一个ResultSet对象。ResultSet代表查询结果的数据表,我们可以通过移动其游标来遍历数据。
String querySql = "SELECT id, name, email FROM users";
try (PreparedStatement pstmt = connection.prepareStatement(querySql);
ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.printf("ID: %d, 姓名: %s, 邮箱: %s\n", id, name, email);
}
} catch (SQLException e) {
e.printStackTrace();
}
第六步:关闭资源
数据库连接是非常宝贵的资源,使用完毕后必须关闭,以释放数据库服务器和客户端的资源,关闭的顺序与创建的顺序相反:ResultSet -> Statement -> Connection。
最优雅和安全的方式是使用Java 7引入的try-with-resources语句,它可以自动关闭在try()括号内声明的所有资源,无论是否发生异常。
// try-with-resources示例,Connection, PreparedStatement, ResultSet都会被自动关闭
try (Connection conn = DriverManager.getConnection(url, username, password);
PreparedStatement pstmt = conn.prepareStatement(querySql);
ResultSet rs = pstmt.executeQuery()) {
// 处理结果集...
} catch (SQLException e) {
e.printStackTrace();
} // 所有资源都已自动关闭
进阶与优化:连接池
在高并发或频繁数据库操作的应用中,每次请求都创建和销毁连接会带来巨大的性能开销,为了解决这个问题,引入了数据库连接池技术。
连接池在程序启动时创建一定数量的数据库连接,并将它们维护在一个池中,当程序需要访问数据库时,它从池中“借用”一个连接,使用完毕后再“归还”,而不是物理关闭,常见的连接池实现有HikariCP、C3P0、Apache DBCP等,其中HikariCP因其高性能和稳定性而备受青睐,使用连接池是构建健壮、高性能Java应用的必备环节。

相关问答 (FAQs)
问题1:什么是SQL注入,为什么PreparedStatement能防止它?
解答:SQL注入是一种代码注入技术,攻击者通过在Web应用的输入字段中插入恶意的SQL代码,来欺骗服务器执行非预期的数据库操作,在一个登录验证中,如果程序使用简单的字符串拼接来构建SQL,攻击者输入' OR '1'='1,就可能绕过密码验证。
PreparedStatement能够有效防止SQL注入,其根本原因在于它将SQL命令和参数分开了处理。PreparedStatement会首先将SQL语句模板发送给数据库进行预编译,形成一个执行计划,之后,无论传入什么参数,这些参数都只会被当作纯数据处理,而不会被解析为SQL命令的一部分,这样,即使参数中包含恶意代码,数据库也不会执行它,从而从根本上杜绝了SQL注入的风险。
问题2:为什么在现代Java应用中强烈推荐使用连接池,而不是直接通过DriverManager获取连接?
解答:推荐使用连接池主要基于以下几个关键原因:
- 性能提升:建立数据库连接是一个相对耗时的操作,涉及网络协议握手、认证等,连接池通过复用已建立的连接,避免了每次请求都创建和销毁连接的开销,显著降低了响应延迟,提高了系统吞吐量。
- 资源管理:连接池可以对连接进行统一管理和监控,例如设置最大连接数、连接超时时间、空闲连接回收策略等,这可以防止因程序错误或高并发导致的连接数耗尽问题,保护数据库服务器的稳定性。
- 可扩展性:在负载增加时,连接池能够更高效地管理有限的连接资源,使得应用能更好地处理并发请求,提高了应用的水平扩展能力。
直接使用DriverManager每次都获取新连接,对于小型、低并发的应用或许可行,但对于任何有一定规模或性能要求的生产环境,这都是不可接受的,使用连接池是现代Java应用开发的行业标准和最佳实践。