Skip to content

PostgreSQL - Java

将 Java 应用程序连接到 PostgreSQL 数据库是一个常见的需求。本教程将指导您完成现代的最佳实践,摒弃过时的手动 JAR 包管理和不安全的编码实践。

  • Java 开发工具包(JDK)11 或更高版本。
  • 一个现代构建自动化工具,如 Maven 或 Gradle。我们将使用 Maven 作为示例。
  • 一个正在运行的 PostgreSQL 服务器实例。

现代 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 驱动的最新版本。

现代 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();
}
}
}

事务(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();
}
}
}
}

为每个请求打开一个新的数据库连接既慢又低效。在实际应用中,您应该使用连接池(如我们已添加到 pom.xml 中的 HikariCP)。连接池维护一组打开的数据库连接,可供您的应用程序重用,从而显著提高性能。

虽然完整的实现超出了本基础教程的范围,但集成连接池涉及在应用程序启动时创建一个 DataSource 对象,并从中获取连接而不是从 DriverManager 获取。