Java 连接 Oracle 数据库终极教程
本教程将分为以下几个核心部分:

- 环境准备:安装和配置所有必需的软件。
- 核心概念:理解 JDBC 是什么,以及它的工作原理。
- 实践步骤:通过详细的代码示例,学习 CRUD(增删改查)操作。
- 高级主题:连接池、事务处理和最佳实践。
- 常见问题与总结:解决常见问题并回顾关键点。
第一部分:环境准备
在开始编码之前,你必须确保以下环境已经准备就绪。
安装 Java Development Kit (JDK)
你需要安装 JDK 来编译和运行 Java 代码,推荐使用较新的 LTS(长期支持)版本,如 JDK 8, 11, 17 或 21。
- 下载地址:Oracle JDK 官网 或 OpenJDK 官网
- 验证安装:打开终端或命令提示符,输入以下命令:
java -version javac -version
如果显示版本号,则表示安装成功。
安装并配置 Oracle 数据库
你需要一个 Oracle 数据库实例来连接,最常见的选择是:

-
Oracle Database XE (Express Edition):
- 优点:免费、资源消耗小、易于安装,非常适合学习和开发。
- 下载地址:Oracle Database XE 21c 下载
- 安装:按照官方指引进行安装,安装过程中会设置一个管理员密码(默认用户名
SYS,SYSTEM等),请务必记住此密码。
-
使用 Docker 快速启动: 如果你想避免复杂的安装过程,可以使用 Docker 一键启动一个 Oracle XE 实例。
docker run -d -p 1521:1521 -e ORACLE_PWD=YourPassword gvenzl/oracle-xe:21c
这将在你的本地机器上启动一个 Oracle XE 数据库,端口为 1521,密码为
YourPassword。
下载 Oracle JDBC 驱动 (ojdbc.jar)
这是 Java 连接 Oracle 数据库的“桥梁”,你需要下载对应的 JDBC 驱动 jar 包。

- 重要:JDBC 驱动的版本必须与你的 Oracle 数据库版本和 JDK 版本兼容。
- 下载地址:Oracle JDBC 驱动下载页面
- 通常你需要下载 "Oracle JDBC driver for ODBC" 下的 jar 文件。
- 对于 Oracle 19c 数据库,你可能需要下载
ojdbc8.jar(JDK 8) 或ojdbc11.jar(JDK 11+)。
配置项目
-
IDE (如 IntelliJ IDEA 或 Eclipse):
- 创建一个新的 Java 项目。
- 将下载好的
ojdbcX.jar文件复制到项目的lib目录下。 - 在 IDE 中,右键点击该 jar 文件,选择 "Add as Library" 或 "Build Path -> Add to Build Path",将其添加到项目的类路径中。
-
Maven 项目 (推荐): 在你的
pom.xml文件中添加以下依赖,Maven 会自动帮你管理依赖版本。<dependency> <groupId>com.oracle.database.jdbc</groupId> <artifactId>ojdbc11</artifactId> <version>21.9.0.0</version> <!-- 请根据你的数据库和JDK版本选择合适的版本 --> </dependency>注意:
ojdbc8用于 JDK 8,ojdbc11用于 JDK 11 及以上。
第二部分:核心概念 - JDBC
JDBC (Java Database Connectivity) 是 Java API,用于定义客户端如何访问数据库,它提供了一种标准的方法,允许 Java 程序执行 SQL 语句并处理结果。
JDBC 的核心思想是驱动程序,Oracle 提供了一个实现了 JDBC API 的 ojdbc.jar 文件,当你的 Java 代码请求连接 Oracle 数据库时,JDBC API 会加载这个 Oracle 驱动,由驱动来完成与数据库底层的通信。
第三部分:实践步骤 - CRUD 操作
我们将创建一个简单的 Java 类,演示如何对 Oracle 数据库进行增、删、改、查操作。
假设我们已经在 Oracle 数据库中创建了一个测试表:
-- 在 SQL*Plus, SQL Developer 或其他工具中执行
CREATE TABLE employees (
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR2(100) NOT NULL,
email VARCHAR2(100) UNIQUE,
salary NUMBER(10, 2),
hire_date DATE DEFAULT SYSDATE
);
-- 插入一些初始数据
INSERT INTO employees (name, email, salary) VALUES ('张三', 'zhangsan@example.com', 8000);
INSERT INTO employees (name, email, salary) VALUES ('李四', 'lisi@example.com', 9500);
COMMIT;
让我们在 Java 代码中操作这个表。
建立数据库连接
这是所有操作的第一步,你需要提供数据库的 URL、用户名和密码。
- 数据库 URL 格式:
jdbc:oracle:thin:@<host>:<port>:<service_name_or_sid>thin:表示使用纯 Java 驱动,无需客户端库。<host>:数据库服务器的 IP 地址或主机名(本地为localhost)。<port>:Oracle 监听端口(默认为1521)。<service_name_or_sid>:数据库的服务名或 SID,对于 XE,通常是XE或XEPDB1。最推荐使用服务名。
完整代码示例
下面是一个完整的 OracleJdbcExample.java 文件,包含了所有 CRUD 操作。
import java.sql.*;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class OracleJdbcExample {
// --- 数据库连接信息 ---
// 请根据你的实际情况修改这些值
private static final String DB_URL = "jdbc:oracle:thin:@localhost:1521/XE"; // 或 XEPDB1
private static final String USER = "system"; // 你的数据库用户名
private static final String PASS = "your_password"; // 你的数据库密码
public static void main(String[] args) {
// 1. 加载 JDBC 驱动 (对于现代JDK,通常可以省略此步,但显式写出更清晰)
try {
Class.forName("oracle.jdbc.OracleDriver");
} catch (ClassNotFoundException e) {
System.err.println("找不到 Oracle JDBC 驱动");
e.printStackTrace();
return;
}
// 使用 try-with-resources 语句,确保连接、语句和结果集在使用后自动关闭
try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
System.out.println("成功连接到 Oracle 数据库!");
// --- 示例操作 ---
// insertEmployee(conn, "王五", "wangwu@example.com", 7500.00);
// updateEmployeeSalary(conn, 1, 8500.00);
// deleteEmployee(conn, 2);
// getEmployeeById(conn, 1);
// listAllEmployees(conn);
} catch (SQLException e) {
System.err.println("数据库连接或操作失败");
e.printStackTrace();
}
}
// --- C: Create (插入数据) ---
public static void insertEmployee(Connection conn, String name, String email, double salary) throws SQLException {
String sql = "INSERT INTO employees (name, email, salary) VALUES (?, ?, ?)";
// 使用 PreparedStatement 防止 SQL 注入
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, name);
pstmt.setString(2, email);
pstmt.setDouble(3, salary);
int affectedRows = pstmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("成功插入新员工: " + name);
}
}
}
// --- R: Read (查询数据) - 查询单个员工 ---
public static void getEmployeeById(Connection conn, int id) throws SQLException {
String sql = "SELECT id, name, email, salary, hire_date FROM employees WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
Employee emp = mapResultSetToEmployee(rs);
System.out.println("查询到员工: " + emp);
} else {
System.out.println("未找到 ID 为 " + id + " 的员工");
}
}
}
}
// --- R: Read (查询数据) - 查询所有员工 ---
public static void listAllEmployees(Connection conn) throws SQLException {
String sql = "SELECT id, name, email, salary, hire_date FROM employees ORDER BY id";
try (Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
System.out.println("--- 所有员工列表 ---");
while (rs.next()) {
Employee emp = mapResultSetToEmployee(rs);
System.out.println(emp);
}
}
}
// --- U: Update (更新数据) ---
public static void updateEmployeeSalary(Connection conn, int id, double newSalary) throws SQLException {
String sql = "UPDATE employees SET salary = ? WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setDouble(1, newSalary);
pstmt.setInt(2, id);
int affectedRows = pstmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("成功更新 ID 为 " + id + " 的员工薪资为 " + newSalary);
} else {
System.out.println("未找到 ID 为 " + id + " 的员工,更新失败");
}
}
}
// --- D: Delete (删除数据) ---
public static void deleteEmployee(Connection conn, int id) throws SQLException {
String sql = "DELETE FROM employees WHERE id = ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
int affectedRows = pstmt.executeUpdate();
if (affectedRows > 0) {
System.out.println("成功删除 ID 为 " + id + " 的员工");
} else {
System.out.println("未找到 ID 为 " + id + " 的员工,删除失败");
}
}
}
// 辅助方法:将 ResultSet 映射到 Employee 对象
private static Employee mapResultSetToEmployee(ResultSet rs) throws SQLException {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
double salary = rs.getDouble("salary");
Date hireDate = rs.getDate("hire_date");
return new Employee(id, name, email, salary, hireDate.toLocalDate());
}
}
// 一个简单的 Employee 实体类
class Employee {
private int id;
private String name;
private String email;
private double salary;
private LocalDate hireDate;
public Employee(int id, String name, String email, double salary, LocalDate hireDate) {
this.id = id;
this.name = name;
this.email = email;
this.salary = salary;
this.hireDate = hireDate;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", salary=" + salary +
", hireDate=" + hireDate +
'}';
}
}
第四部分:高级主题
使用连接池
直接使用 DriverManager.getConnection() 每次都会创建一个新的物理连接,这是一个非常耗费资源的操作,在高并发应用中,性能会急剧下降。
连接池 的作用是:在应用启动时创建一组数据库连接,并将它们放入一个“池”中,当需要连接时,从池中获取一个用完后,再归还给池,而不是关闭,这大大提高了性能。
- 常用连接池:HikariCP (目前性能最好,是 Spring Boot 的默认选择), Apache DBCP, C3P0。
使用 HikariCP 的示例:
-
添加 Maven 依赖:
<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency> -
修改代码以使用 HikariCP:
import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; public class HikariCPExample { private static HikariDataSource dataSource; static { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:oracle:thin:@localhost:1521/XE"); config.setUsername("system"); config.setPassword("your_password"); config.setDriverClassName("oracle.jdbc.OracleDriver"); // 连接池配置 config.setMaximumPoolSize(10); // 最大连接数 config.setMinimumIdle(5); // 最小空闲连接数 config.setConnectionTimeout(30000); // 连接超时时间 (ms) dataSource = new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void main(String[] args) { try (Connection conn = getConnection()) { System.out.println("从 HikariCP 连接池中成功获取连接!"); // ... 在这里执行你的数据库操作 ... } catch (SQLException e) { e.printStackTrace(); } finally { // 应用关闭时,关闭数据源 if (dataSource != null && !dataSource.isClosed()) { dataSource.close(); } } } }
事务管理
事务是一组原子性的 SQL 操作,要么全部成功,要么全部失败,这在处理业务逻辑(如银行转账)时至关重要。
ACID 特性:
- 原子性:事务内的操作不可分割。
- 一致性:事务使数据库从一个一致状态变为另一个一致状态。
- 隔离性:并发事务之间互不干扰。
- 持久性:事务一旦提交,其结果就是永久的。
Java 中管理事务:
默认情况下,每个 SQL 语句都在一个单独的事务中自动提交,要手动管理事务,需要关闭自动提交。
public void transferMoney(Connection conn, int fromId, int toId, double amount) throws SQLException {
// 关闭自动提交
conn.setAutoCommit(false);
try {
// 1. 扣款
String sql1 = "UPDATE employees SET salary = salary - ? WHERE id = ?";
try (PreparedStatement pstmt1 = conn.prepareStatement(sql1)) {
pstmt1.setDouble(1, amount);
pstmt1.setInt(2, fromId);
pstmt1.executeUpdate();
}
// 2. 检查余额是否充足 (模拟业务逻辑)
// ... 省略查询逻辑 ...
// 3. 增款
String sql2 = "UPDATE employees SET salary = salary + ? WHERE id = ?";
try (PreparedStatement pstmt2 = conn.prepareStatement(sql2)) {
pstmt2.setDouble(1, amount);
pstmt2.setInt(2, toId);
pstmt2.executeUpdate();
}
// 如果所有操作都成功,则提交事务
conn.commit();
System.out.println("转账成功!");
} catch (SQLException e) {
// 如果发生任何异常,则回滚事务
conn.rollback();
System.out.println("转账失败,已回滚!");
e.printStackTrace();
} finally {
// 恢复自动提交模式,以免影响后续操作
conn.setAutoCommit(true);
}
}
第五部分:常见问题与总结
常见问题
-
java.lang.ClassNotFoundException: oracle.jdbc.OracleDriver- 原因:
ojdbc.jar文件没有被添加到项目的类路径中。 - 解决:检查你的 IDE 或构建工具(Maven/Gradle)的配置,确保驱动包已正确引入。
- 原因:
-
java.sql.SQLException: No suitable driver found for jdbc:oracle:thin:...- 原因:通常是 URL 格式错误,或者
Class.forName()没有成功加载驱动(虽然现代 JDK 常常不需要这步,但最好检查)。 - 解决:仔细检查数据库 URL 是否正确,包括主机、端口和服务名/SID。
- 原因:通常是 URL 格式错误,或者
-
java.sql.SQLException: Invalid username/password; logon denied- 原因:提供的数据库用户名或密码错误。
- 解决:确认你的用户名和密码,注意大小写。
-
IO Error: The Network Adapter could not establish the connection- 原因:无法连接到数据库服务器。
- 解决:
- 确认 Oracle 数据库服务已经启动。
- 检查防火墙是否阻止了 1521 端口。
- 确认数据库主机地址和端口号是否正确。
最佳实践总结
- 始终使用
PreparedStatement:它可以防止 SQL 注入攻击,并且对于预编译的 SQL 有更好的性能。 - 总是使用
try-with-resources:确保Connection,Statement,ResultSet等资源在使用后能被自动关闭,避免资源泄漏。 - 使用连接池:在生产环境中,绝对不要直接使用
DriverManager,务必使用 HikariCP 等高性能连接池。 - 合理处理事务:对于涉及多个步骤的业务逻辑,使用事务来保证数据的一致性。
- 使用 ORM 框架:对于复杂的项目,考虑使用 MyBatis, Hibernate 等 ORM(对象关系映射)框架,它们可以让你用面向对象的方式操作数据库,大大减少重复的 JDBC 代码,并提高开发效率。
希望这份详细的教程能帮助你顺利掌握 Java 连接 Oracle 数据库的技能!祝你学习愉快!
