Java 连接 Oracle 数据库终极教程

本教程将分为以下几个核心部分:

java oracle教程
(图片来源网络,侵删)
  1. 环境准备:安装和配置所有必需的软件。
  2. 核心概念:理解 JDBC 是什么,以及它的工作原理。
  3. 实践步骤:通过详细的代码示例,学习 CRUD(增删改查)操作。
  4. 高级主题:连接池、事务处理和最佳实践。
  5. 常见问题与总结:解决常见问题并回顾关键点。

第一部分:环境准备

在开始编码之前,你必须确保以下环境已经准备就绪。

安装 Java Development Kit (JDK)

你需要安装 JDK 来编译和运行 Java 代码,推荐使用较新的 LTS(长期支持)版本,如 JDK 8, 11, 17 或 21。

  • 下载地址Oracle JDK 官网OpenJDK 官网
  • 验证安装:打开终端或命令提示符,输入以下命令:
    java -version
    javac -version

    如果显示版本号,则表示安装成功。

安装并配置 Oracle 数据库

你需要一个 Oracle 数据库实例来连接,最常见的选择是:

java 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 包。

java oracle教程
(图片来源网络,侵删)
  • 重要: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)

    1. 创建一个新的 Java 项目。
    2. 将下载好的 ojdbcX.jar 文件复制到项目的 lib 目录下。
    3. 在 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,通常是 XEXEPDB1最推荐使用服务名

完整代码示例

下面是一个完整的 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 的示例:

  1. 添加 Maven 依赖

    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>5.0.1</version>
    </dependency>
  2. 修改代码以使用 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);
    }
}

第五部分:常见问题与总结

常见问题

  1. java.lang.ClassNotFoundException: oracle.jdbc.OracleDriver

    • 原因ojdbc.jar 文件没有被添加到项目的类路径中。
    • 解决:检查你的 IDE 或构建工具(Maven/Gradle)的配置,确保驱动包已正确引入。
  2. java.sql.SQLException: No suitable driver found for jdbc:oracle:thin:...

    • 原因:通常是 URL 格式错误,或者 Class.forName() 没有成功加载驱动(虽然现代 JDK 常常不需要这步,但最好检查)。
    • 解决:仔细检查数据库 URL 是否正确,包括主机、端口和服务名/SID。
  3. java.sql.SQLException: Invalid username/password; logon denied

    • 原因:提供的数据库用户名或密码错误。
    • 解决:确认你的用户名和密码,注意大小写。
  4. IO Error: The Network Adapter could not establish the connection

    • 原因:无法连接到数据库服务器。
    • 解决
      • 确认 Oracle 数据库服务已经启动。
      • 检查防火墙是否阻止了 1521 端口。
      • 确认数据库主机地址和端口号是否正确。

最佳实践总结

  1. 始终使用 PreparedStatement:它可以防止 SQL 注入攻击,并且对于预编译的 SQL 有更好的性能。
  2. 总是使用 try-with-resources:确保 Connection, Statement, ResultSet 等资源在使用后能被自动关闭,避免资源泄漏。
  3. 使用连接池:在生产环境中,绝对不要直接使用 DriverManager,务必使用 HikariCP 等高性能连接池。
  4. 合理处理事务:对于涉及多个步骤的业务逻辑,使用事务来保证数据的一致性。
  5. 使用 ORM 框架:对于复杂的项目,考虑使用 MyBatis, Hibernate 等 ORM(对象关系映射)框架,它们可以让你用面向对象的方式操作数据库,大大减少重复的 JDBC 代码,并提高开发效率。

希望这份详细的教程能帮助你顺利掌握 Java 连接 Oracle 数据库的技能!祝你学习愉快!