Java开发者PostgreSQL CRUD操作实战指南
1. 项目概述作为一名长期从事Java后端开发的工程师我经常需要与各种数据库打交道。PostgreSQL作为一款功能强大的开源关系型数据库在企业级应用中占据着重要地位。今天我想分享一个完整的PostgreSQL CRUD操作指南专门针对Java开发者。这个教程将带你从零开始逐步实现PostgreSQL数据库的连接、创建、读取、更新和删除操作。不同于官方文档的抽象描述我会结合自己多年实战经验分享在实际项目中遇到的典型问题和解决方案。无论你是刚接触PostgreSQL的新手还是需要快速回顾基础操作的老手这篇指南都能提供实用价值。2. 环境准备与配置2.1 PostgreSQL安装与设置首先需要在本地或服务器上安装PostgreSQL。我推荐使用最新稳定版本目前是PostgreSQL 16因为它包含了性能优化和新特性。安装完成后记得执行以下关键步骤初始化数据库集群initdb -D /usr/local/pgsql/data启动服务pg_ctl -D /usr/local/pgsql/data -l logfile start创建测试数据库createdb testdb创建用户并设置密码createuser -P myuser注意生产环境中务必修改默认的postgres用户密码并合理配置pg_hba.conf文件的访问权限。2.2 Java项目依赖配置在Maven项目中我们需要添加PostgreSQL JDBC驱动依赖dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId version42.6.0/version /dependency对于Gradle项目则在build.gradle中添加implementation org.postgresql:postgresql:42.6.03. 数据库连接管理3.1 建立数据库连接创建Connection对象是操作数据库的第一步。以下是标准的连接代码import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class PostgreSQLConnection { private static final String URL jdbc:postgresql://localhost:5432/testdb; private static final String USER myuser; private static final String PASSWORD mypassword; public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }3.2 连接池配置实际项目中直接使用DriverManager获取连接效率低下。我推荐使用HikariCP连接池import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; public class ConnectionPool { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:postgresql://localhost:5432/testdb); config.setUsername(myuser); config.setPassword(mypassword); config.setMaximumPoolSize(10); config.setConnectionTimeout(30000); dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }4. CRUD操作实现4.1 创建表结构我们先创建一个简单的用户表作为示例public void createTable() { String sql CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP); try (Connection conn ConnectionPool.getConnection(); Statement stmt conn.createStatement()) { stmt.executeUpdate(sql); } catch (SQLException e) { e.printStackTrace(); } }4.2 插入数据Create使用PreparedStatement防止SQL注入public int insertUser(String username, String email) { String sql INSERT INTO users (username, email) VALUES (?, ?); try (Connection conn ConnectionPool.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { pstmt.setString(1, username); pstmt.setString(2, email); int affectedRows pstmt.executeUpdate(); if (affectedRows 0) { try (ResultSet rs pstmt.getGeneratedKeys()) { if (rs.next()) { return rs.getInt(1); } } } } catch (SQLException e) { e.printStackTrace(); } return -1; }4.3 查询数据Read4.3.1 查询单个用户public User getUserById(int id) { String sql SELECT * FROM users WHERE id ?; User user null; try (Connection conn ConnectionPool.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setInt(1, id); try (ResultSet rs pstmt.executeQuery()) { if (rs.next()) { user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setEmail(rs.getString(email)); user.setCreatedAt(rs.getTimestamp(created_at)); } } } catch (SQLException e) { e.printStackTrace(); } return user; }4.3.2 分页查询public ListUser getUsers(int page, int size) { String sql SELECT * FROM users ORDER BY id LIMIT ? OFFSET ?; ListUser users new ArrayList(); try (Connection conn ConnectionPool.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setInt(1, size); pstmt.setInt(2, (page - 1) * size); try (ResultSet rs pstmt.executeQuery()) { while (rs.next()) { User user new User(); user.setId(rs.getInt(id)); user.setUsername(rs.getString(username)); user.setEmail(rs.getString(email)); user.setCreatedAt(rs.getTimestamp(created_at)); users.add(user); } } } catch (SQLException e) { e.printStackTrace(); } return users; }4.4 更新数据Updatepublic boolean updateUserEmail(int id, String newEmail) { String sql UPDATE users SET email ? WHERE id ?; try (Connection conn ConnectionPool.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, newEmail); pstmt.setInt(2, id); return pstmt.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }4.5 删除数据Deletepublic boolean deleteUser(int id) { String sql DELETE FROM users WHERE id ?; try (Connection conn ConnectionPool.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setInt(1, id); return pstmt.executeUpdate() 0; } catch (SQLException e) { e.printStackTrace(); return false; } }5. 高级特性与优化5.1 批量操作PostgreSQL的批量插入可以显著提高性能public void batchInsertUsers(ListUser users) { String sql INSERT INTO users (username, email) VALUES (?, ?); try (Connection conn ConnectionPool.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { for (User user : users) { pstmt.setString(1, user.getUsername()); pstmt.setString(2, user.getEmail()); pstmt.addBatch(); } pstmt.executeBatch(); } catch (SQLException e) { e.printStackTrace(); } }5.2 事务管理public boolean transferPoints(int fromUserId, int toUserId, int points) { String deductSql UPDATE user_points SET points points - ? WHERE user_id ?; String addSql UPDATE user_points SET points points ? WHERE user_id ?; Connection conn null; try { conn ConnectionPool.getConnection(); conn.setAutoCommit(false); try (PreparedStatement deductStmt conn.prepareStatement(deductSql); PreparedStatement addStmt conn.prepareStatement(addSql)) { deductStmt.setInt(1, points); deductStmt.setInt(2, fromUserId); deductStmt.executeUpdate(); addStmt.setInt(1, points); addStmt.setInt(2, toUserId); addStmt.executeUpdate(); conn.commit(); return true; } } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); return false; } finally { if (conn ! null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }6. 常见问题与解决方案6.1 连接超时问题症状出现Connection timed out或Connection refused错误。解决方案检查PostgreSQL服务是否运行ps aux | grep postgres验证pg_hba.conf配置是否正确检查防火墙设置是否阻止了5432端口增加连接超时时间在JDBC URL中添加connectTimeout306.2 性能优化技巧为常用查询字段创建索引CREATE INDEX idx_users_username ON users(username); CREATE INDEX idx_users_email ON users(email);使用EXPLAIN分析查询计划public void explainQuery(int userId) { String sql EXPLAIN ANALYZE SELECT * FROM users WHERE id ?; try (Connection conn ConnectionPool.getConnection(); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setInt(1, userId); try (ResultSet rs pstmt.executeQuery()) { while (rs.next()) { System.out.println(rs.getString(1)); } } } catch (SQLException e) { e.printStackTrace(); } }合理设置连接池参数maximumPoolSize根据应用负载调整idleTimeout避免长时间空闲连接占用资源connectionTimeout设置合理的等待时间6.3 数据类型映射PostgreSQL与Java类型对应关系PostgreSQL类型Java类型注意事项SERIALint自增主键VARCHARString需指定长度TEXTString不限长度文本INTEGERintBIGINTlongNUMERICBigDecimal精确计算时使用BOOLEANbooleanDATEjava.sql.DateTIMESTAMPjava.sql.Timestamp包含日期和时间JSONBString可使用PGobject处理7. 安全最佳实践7.1 防止SQL注入始终使用PreparedStatement而不是字符串拼接// 错误做法易受SQL注入攻击 String sql SELECT * FROM users WHERE username username ; // 正确做法 String sql SELECT * FROM users WHERE username ?; PreparedStatement pstmt conn.prepareStatement(sql); pstmt.setString(1, username);7.2 敏感数据保护不要将数据库凭证硬编码在代码中使用环境变量或配置中心String dbUrl System.getenv(DB_URL); String dbUser System.getenv(DB_USER); String dbPassword System.getenv(DB_PASSWORD);对敏感字段如密码使用加密存储public String hashPassword(String plainPassword) { return BCrypt.hashpw(plainPassword, BCrypt.gensalt()); } public boolean checkPassword(String plainPassword, String hashedPassword) { return BCrypt.checkpw(plainPassword, hashedPassword); }7.3 最小权限原则为应用数据库用户分配最小必要权限-- 只授予必要的表权限 GRANT SELECT, INSERT, UPDATE, DELETE ON users TO app_user; -- 撤销不必要的权限 REVOKE ALL ON DATABASE testdb FROM PUBLIC;8. 测试与调试8.1 单元测试示例使用JUnit测试数据库操作import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.*; TestInstance(TestInstance.Lifecycle.PER_CLASS) class UserDAOTest { private UserDAO userDAO; BeforeAll void setup() { userDAO new UserDAO(); // 初始化测试数据库 } Test void testInsertAndGetUser() { int userId userDAO.insertUser(testuser, testexample.com); assertTrue(userId 0); User user userDAO.getUserById(userId); assertNotNull(user); assertEquals(testuser, user.getUsername()); } AfterAll void cleanup() { // 清理测试数据 } }8.2 集成测试建议使用测试容器启动PostgreSQL实例Testcontainers class IntegrationTest { Container private static final PostgreSQLContainer? postgres new PostgreSQLContainer(postgres:13-alpine); BeforeAll static void setup() { System.setProperty(DB_URL, postgres.getJdbcUrl()); System.setProperty(DB_USER, postgres.getUsername()); System.setProperty(DB_PASSWORD, postgres.getPassword()); } }为每个测试用例使用独立的事务确保测试隔离ExtendWith(SpringExtension.class) SpringBootTest Transactional class TransactionalTest { Autowired private UserService userService; Test void testTransactionalOperation() { // 测试会自动回滚 } }9. 性能监控与调优9.1 监控SQL执行使用JDBC拦截器记录慢查询public class LoggingConnection extends DelegatingConnection { private static final Logger logger LoggerFactory.getLogger(LoggingConnection.class); private static final long SLOW_QUERY_THRESHOLD 100; // 毫秒 public LoggingConnection(Connection delegate) { super(delegate); } Override public Statement createStatement() throws SQLException { return new LoggingStatement(super.createStatement()); } private class LoggingStatement extends DelegatingStatement { public LoggingStatement(Statement delegate) { super(delegate); } Override public ResultSet executeQuery(String sql) throws SQLException { long start System.currentTimeMillis(); try { return super.executeQuery(sql); } finally { long duration System.currentTimeMillis() - start; if (duration SLOW_QUERY_THRESHOLD) { logger.warn(Slow query ({}ms): {}, duration, sql); } } } } }9.2 使用连接池监控HikariCP提供了丰富的监控指标HikariPoolMXBean poolProxy dataSource.getHikariPoolMXBean(); System.out.println(Active connections: poolProxy.getActiveConnections()); System.out.println(Idle connections: poolProxy.getIdleConnections()); System.out.println(Total connections: poolProxy.getTotalConnections()); System.out.println(Threads awaiting connection: poolProxy.getThreadsAwaitingConnection());9.3 PostgreSQL性能视图查询pg_stat_activity获取当前活动会话public void monitorActiveSessions() { String sql SELECT pid, usename, application_name, state, query FROM pg_stat_activity WHERE state active; try (Connection conn ConnectionPool.getConnection(); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql)) { while (rs.next()) { System.out.printf(PID: %d, User: %s, App: %s, State: %s, Query: %s%n, rs.getInt(pid), rs.getString(usename), rs.getString(application_name), rs.getString(state), rs.getString(query)); } } catch (SQLException e) { e.printStackTrace(); } }10. 实际项目经验分享在多年的PostgreSQL开发中我积累了一些宝贵的经验连接管理总是确保在finally块中关闭Connection、Statement和ResultSet。Java 7的try-with-resources语法是最佳选择。批量操作当需要插入或更新大量数据时批量操作比单条执行效率高10-100倍。但要注意批量大小通常100-1000条一批比较合适。索引策略不是索引越多越好。每个索引都会增加写操作的开销。监控查询性能只为真正需要的查询添加索引。事务边界合理划分事务范围。过大的事务会导致锁竞争和性能问题过小的事务可能无法保证数据一致性。连接池配置最大连接数不要超过数据库的max_connections设置空闲连接超时应大于数据库的idle_in_transaction_session_timeout考虑使用不同的连接池配置给读写操作和只读操作JSONB类型PostgreSQL的JSONB类型非常强大适合存储半结构化数据。但要注意频繁更新的JSONB字段可能导致表膨胀。分区表对于大表超过1亿行考虑使用分区表提高查询性能。可以按时间范围或ID范围分区。扩展功能PostgreSQL有许多有用的扩展如pg_trgm模糊搜索、postgis地理空间数据、hstore键值存储等根据需求合理使用。备份策略除了常规的pg_dump备份考虑设置WAL归档和PITR时间点恢复以应对数据灾难。版本升级PostgreSQL大版本升级通常需要停机时间。规划好升级窗口并提前测试应用兼容性。

相关新闻

制造业采购降本:直连工厂模式与实施路径

制造业采购降本:直连工厂模式与实施路径

1. 采购降本的核心痛点与破局思路在制造业摸爬滚打十几年,我见过太多企业被供应链成本拖垮的案例。去年帮一家汽配企业做成本审计时发现,他们采购的铝合金型材到厂价竟比原材料成本高出83%——其中物流和中间商抽成就占了总成本的35%。这绝非个例&#x…

2026/10/15 3:45:43 阅读更多 →
Natron ColorBars 节点完全指南:SMPTE RP 219 彩条生成器的参数详解与广播安全输出

Natron ColorBars 节点完全指南:SMPTE RP 219 彩条生成器的参数详解与广播安全输出

音视频视频处理图形学桌面应用 【免费下载链接】Natron Open-source video compositing software. Node-graph based. Similar in functionalities to Adobe After Effects and Nuke by The Foundry. 项目地址: https://gitcode.com/gh_mirrors/na/Natron 点击查看 …

2026/10/15 3:44:42 阅读更多 →
foobar2000界面太老旧?这份foobox-cn主题完整指南帮你快速换上新皮肤

foobar2000界面太老旧?这份foobox-cn主题完整指南帮你快速换上新皮肤

foobar2000界面太老旧?这份foobox-cn主题完整指南帮你快速换上新皮肤 【免费下载链接】foobox-cn DUI 配置 for foobar2000 项目地址: https://gitcode.com/GitHub_Trending/fo/foobox-cn 如果你也打开foobar2000就面对一片灰扑扑的默认窗口,或者…

2026/10/15 3:44:42 阅读更多 →

最新新闻

LLM request timed out排查指南:OpenClaw接入第三方API超时解决方案

LLM request timed out排查指南:OpenClaw接入第三方API超时解决方案

LLM request timed out 这个报错,接第三方 API 的朋友应该不陌生。OpenClaw 本身是个开源智能体框架,跑起来很轻,但一旦把模型源切到第三方兼容接口,超时就像定时炸弹一样时不时爆一下。这篇文章把我自己调通的全过程写出来&#…

2026/10/15 5:36:21 阅读更多 →
Zabbix 监控 IBM TSM / Spectrum Protect 客户端调度服务:App TSM Client Scheduler 模板深度解析

Zabbix 监控 IBM TSM / Spectrum Protect 客户端调度服务:App TSM Client Scheduler 模板深度解析

【免费下载链接】community-templates Zabbix Community Templates repository 项目地址: https://gitcode.com/gh_mirrors/co/community-templates 点击查看 免费下载 本文围绕 Zabbix 社区模板仓库中的 App TSM Client Scheduler 模板展开,讲解如何基…

2026/10/15 5:36:21 阅读更多 →
foobox-cn 完整指南:快速为 foobar2000 换上专业外观的 5 个要点

foobox-cn 完整指南:快速为 foobar2000 换上专业外观的 5 个要点

foobox-cn 完整指南:快速为 foobar2000 换上专业外观的 5 个要点 【免费下载链接】foobox-cn DUI 配置 for foobar2000 项目地址: https://gitcode.com/GitHub_Trending/fo/foobox-cn 还在用 foobar2000 默认的灰黑界面,想美化又怕弄坏自己的配置…

2026/10/15 5:36:21 阅读更多 →
赛题理解决定数据挖掘比赛成败——资金流入流出预测实战解析

赛题理解决定数据挖掘比赛成败——资金流入流出预测实战解析

新手参加数据挖掘比赛,最容易犯的毛病就是拿到数据直接开干,跑完模型看分数不高就抓瞎。我见过太多人连赛题都没读明白就开始调参,最后折腾两周发现方向从一开始就是错的。今天拿天池这个资金流入流出预测赛题当案例,掰开揉碎讲讲…

2026/10/15 5:36:21 阅读更多 →
2024安徽安全员ABC证考试真题题库(含答案解析)

2024安徽安全员ABC证考试真题题库(含答案解析)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 5:36:21 阅读更多 →
R语言算术运算在医学数据分析中的核心应用与实战技巧

R语言算术运算在医学数据分析中的核心应用与实战技巧

刚接触R的时候,很多人的第一反应是:“这不就是一门统计编程语言吗?重点应该先学tidyverse、ggplot2、建模包吧?”但真正拿医学数据上手跑过几轮之后,你会发现一个很朴素的真相:所有复杂分析——从清洗数据、…

2026/10/15 5:35:21 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →