Java操作PostgreSQL数据库commit提交失败问题排查
问题回顾
你在Java操作PostgreSQL时遇到了提交数据失败的问题,控制台抛出:
org.postgresql.util.PSQLException: This connection has been closed
堆栈追踪明确指向addToTable方法里的c.commit();行,而你确认getId(t.getName())和参数i的输出都符合预期,其他类似插入逻辑也能正常运行。
核心原因:类成员变量被意外覆盖并关闭
问题出在类成员变量c(Connection)的复用冲突上!
看你的addToTable方法流程:
- 创建新连接并赋值给类成员
c - 调用
getId(t.getName())方法 - 尝试对
c执行commit操作
而你的getId(对应类里的getTableId或getDrinkId)方法内部,又重新给类成员c赋值了一个新的连接,并且在方法结束前调用了c.close()关闭这个新连接。
这就导致:当getId执行完毕回到addToTable时,类成员c已经被替换成了那个被关闭的连接,此时再调用c.commit()自然会抛出连接已关闭的异常。
简单梳理一下时序:
addToTable:创建连接A → 赋值给类成员cgetId:创建连接B → 覆盖类成员c→ 查询完成后关闭连接BaddToTable:此时c是已关闭的连接B → 执行c.commit()→ 报错
解决方案
1. 优先推荐:使用局部变量存储连接和Statement
把Connection和Statement从类成员改成方法内的局部变量,让每个方法的连接完全独立,避免互相干扰。
修改后的addToTable方法:
@Override public void addToTable(T t, int i) { try { Class.forName("org.postgresql.Driver"); // 用局部变量存储连接,不再使用类成员c Connection conn = DriverManager.getConnection(connectionUrl, connectionUser, connectionPwd); conn.setAutoCommit(false); System.out.println("Opened database successfully"); Statement stmt = conn.createStatement(); int id = this.getId(t.getName()); String sql = "INSERT INTO app.my_table_name VALUES ('" + i + "', '" + id + "');"; stmt.executeUpdate(sql); stmt.close(); conn.commit(); conn.close(); } catch (Exception e) { System.err.println( e.getClass().getName()+": "+ e.getMessage()); for (StackTraceElement error : e.getStackTrace()) { System.out.println(error); } System.exit(0); } System.out.println("Row inserted successfully"); }
同步修改getTableId方法(同理getDrinkId):
@Override public int getTableId(String tableName) { try { Class.forName("org.postgresql.Driver"); // 局部变量存储连接 Connection conn = DriverManager.getConnection(connectionUrl, connectionUser, connectionPwd); conn.setAutoCommit(false); System.out.println("Opened database successfully"); Statement stmtQuery = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY ); int tableId = 0; String sql = "SELECT tl.id FROM app.tables tl WHERE tl.table_name = '" + tableName + "' LIMIT 1;"; ResultSet rs = stmtQuery.executeQuery(sql); while (rs.next()) { tableId = rs.getInt("id"); } conn.commit(); stmtQuery.close(); conn.close(); return tableId; } catch (Exception e) { System.err.println( e.getClass().getName()+": "+ e.getMessage() ); System.exit(0); } System.out.println("Row inserted successfully"); return 0; }
2. 进阶优化:使用数据库连接池
如果频繁创建关闭连接影响性能,可以引入连接池(比如HikariCP),它能帮你管理连接的复用,避免重复创建销毁连接的开销,同时也能避免此类成员变量冲突问题。
3. 不推荐:临时保存并恢复类成员连接
如果非要保留类成员变量的方式,可以在getId方法里先保存原有连接,执行完后恢复,但这种方式极易出错,不建议使用:
@Override public int getTableId(String tableName) { Connection originalConn = c; // 保存原有连接 try { Class.forName("org.postgresql.Driver"); c = DriverManager.getConnection(connectionUrl, connectionUser, connectionPwd); c.setAutoCommit(false); // ... 原有查询逻辑 c.close(); c = originalConn; // 恢复原有连接 return tableId; } catch (Exception e) { // ... 异常处理 } return 0; }
额外提醒:避免SQL注入风险
你的代码目前直接拼接字符串生成SQL,存在严重的SQL注入风险,建议改用PreparedStatement:
String sql = "INSERT INTO app.my_table_name VALUES (?, ?);"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, i); pstmt.setInt(2, id); pstmt.executeUpdate();
内容的提问来源于stack exchange,提问作者user12724454

