PostgreSQL - Java
现代 PostgreSQL 与 Java 集成
Section titled “现代 PostgreSQL 与 Java 集成”将 Java 应用程序连接到 PostgreSQL 数据库是一个常见的需求。本教程将指导您完成现代的最佳实践,摒弃过时的手动 JAR 包管理和不安全的编码实践。
- Java 开发工具包(JDK)11 或更高版本。
- 一个现代构建自动化工具,如 Maven 或 Gradle。我们将使用 Maven 作为示例。
- 一个正在运行的 PostgreSQL 服务器实例。
步骤 1:使用 Maven 设置项目
Section titled “步骤 1:使用 Maven 设置项目”现代 Java 项目绝不应通过手动下载 JAR 文件来管理依赖项。相反,请使用 Maven 等构建工具。它会自动下载所需的库(称为“依赖项”)并管理您的项目结构。
在您项目的 pom.xml 文件中,添加 PostgreSQL JDBC 驱动依赖项:
<!-- pom.xml --><project ...> ... <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties>
<dependencies> <!-- PostgreSQL JDBC Driver --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.7.3</version> <!-- 在 Maven Central 检查最新版本 --> </dependency>
<!-- (可选但推荐)连接池,如 HikariCP --> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.1.0</version> </dependency> </dependencies> ...</project>务必前往 Maven Central 仓库 查看 PostgreSQL 驱动的最新版本。
步骤 2:安全地建立连接
Section titled “步骤 2:安全地建立连接”现代 Java 提供了 try-with-resources 语句,它会自动关闭数据库连接、语句和结果集等资源。这可以防止资源泄露并使代码更简洁。
最佳实践:避免在源代码中硬编码凭据。请使用环境变量或配置文件。
import java.sql.Connection;import java.sql.DriverManager;import java.sql.SQLException;
public class DatabaseConnector { // 理想情况下,从配置文件或环境变量中加载这些信息 private static final String URL = "jdbc:postgresql://localhost:5432/testdb"; private static final String USER = "postgres"; private static final String PASSWORD = "your_password";
public static void main(String[] args) { try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD)) { if (conn != null) { System.out.println("成功连接到 PostgreSQL 服务器。"); } else { System.out.println("连接失败。"); } } catch (SQLException e) { System.err.println("连接失败!请检查输出控制台"); e.printStackTrace(); } }}步骤 3:使用 PreparedStatement 进行 CRUD 操作
Section titled “步骤 3:使用 PreparedStatement 进行 CRUD 操作”一项关键的安全实践是始终使用 PreparedStatement,而不是通过拼接字符串来构建 SQL 查询。这可以防止 SQL 注入攻击,恶意用户可能通过这种方式操纵您的数据库。
让我们创建一个完整的 CRUD 示例类。首先是表设置 SQL:
-- 请先在 psql 或数据库客户端中运行此命令DROP TABLE IF EXISTS employees;CREATE TABLE employees ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, position TEXT NOT NULL, salary NUMERIC(10, 2));现在,完整的 Java 代码演示了安全的 CRUD 操作:
import java.sql.*;import java.util.ArrayList;import java.util.List;
// 一个简单的员工模型类class Employee { private int id; private String name; private String position; private double salary; // 为简洁起见,省略了构造函数、Getter、Setter 和 toString() 方法}
public class EmployeeDAO { private static final String URL = "jdbc:postgresql://localhost:5432/testdb"; private static final String USER = "postgres"; private static final String PASSWORD = "your_password";
// C: Create (创建) public void addEmployee(String name, String position, double salary) { String sql = "INSERT INTO employees(name, position, salary) VALUES(?, ?, ?)"; try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, name); pstmt.setString(2, position); pstmt.setDouble(3, salary); pstmt.executeUpdate(); System.out.println("添加了员工:" + name); } catch (SQLException e) { e.printStackTrace(); } }
// R: Read (读取) public Employee getEmployeeById(int id) { String sql = "SELECT * FROM employees WHERE id = ?"; Employee employee = null; try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setInt(1, id); ResultSet rs = pstmt.executeQuery(); if (rs.next()) { // 创建并填充员工对象(实现已省略) } } catch (SQLException e) { e.printStackTrace(); } return employee; }
// U: Update (更新) public void updateEmployeeSalary(int id, double newSalary) { String sql = "UPDATE employees SET salary = ? WHERE id = ?"; try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setDouble(1, newSalary); pstmt.setInt(2, id); int affectedRows = pstmt.executeUpdate(); System.out.println("更新了员工 ID " + id + " 的薪资。影响的行数:" + affectedRows); } catch (SQLException e) { e.printStackTrace(); } }
// D: Delete (删除) public void deleteEmployee(int id) { String sql = "DELETE FROM employees WHERE id = ?"; try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setInt(1, id); int affectedRows = pstmt.executeUpdate(); System.out.println("删除了员工 ID " + id + "。影响的行数:" + affectedRows); } catch (SQLException e) { e.printStackTrace(); } }}步骤 4:事务管理
Section titled “步骤 4:事务管理”事务(transaction)是作为单个逻辑工作单元执行的一系列操作。所有操作都必须成功,否则任何操作都不会被应用。这可以确保数据完整性。
示例:将“奖金”从一名员工的记录转移到特殊基金。两次更新都必须成功。
public void performTransaction() { Connection conn = null; try { conn = DriverManager.getConnection(URL, USER, PASSWORD); // 1. 禁用自动提交以开始事务 conn.setAutoCommit(false);
// 2. 执行多项操作 String sql1 = "UPDATE employees SET salary = salary - 5000 WHERE name = 'Alice'"; String sql2 = "UPDATE company_funds SET balance = balance + 5000";
try (Statement stmt = conn.createStatement()) { stmt.executeUpdate(sql1); stmt.executeUpdate(sql2); }
// 3. 如果所有操作都成功,则提交事务 conn.commit(); System.out.println("事务成功提交。");
} catch (SQLException e) { System.err.println("事务失败。正在回滚..."); if (conn != null) { try { // 4. 如果发生任何错误,则回滚整个事务 conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } finally { if (conn != null) { try { conn.setAutoCommit(true); // 重置为默认行为 conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }}最佳实践:连接池
Section titled “最佳实践:连接池”为每个请求打开一个新的数据库连接既慢又低效。在实际应用中,您应该使用连接池(如我们已添加到 pom.xml 中的 HikariCP)。连接池维护一组打开的数据库连接,可供您的应用程序重用,从而显著提高性能。
虽然完整的实现超出了本基础教程的范围,但集成连接池涉及在应用程序启动时创建一个 DataSource 对象,并从中获取连接而不是从 DriverManager 获取。