基于MVC架构的Java Swing项目SQL记录导航实现求助
解决Java Swing MVC项目的数据库记录导航问题
先梳理你现有代码的核心问题:
- 成员变量
rs未被正确维护,getStocks方法每次查询都会覆盖它,且默认ResultSet是只能向前滚动的类型,无法实现上一条、第一条、最后一条等导航操作 nextRecord方法逻辑错误,调用getStocks(barcode)是获取记录列表,而非将当前记录数据填充到模型属性中- 数据库资源(Connection、Statement、ResultSet)未正确关闭,存在泄漏风险
- 内部类
Service的内存列表操作和数据库操作分离,导航逻辑未和数据层对齐
下面提供两种可行的导航实现方案,根据你的数据量选择:
方案1:基于内存列表的导航(推荐,适合数据量不大的场景)
这种方案先把所有数据库记录加载到内存列表,通过索引移动实现导航,无需一直保持数据库连接,稳定性更高。
修改MainModel代码步骤:
- 添加存储记录列表和当前索引的成员变量
private List<MainModel> stockList; private int currentIndex = -1;
- 添加加载所有记录并初始化导航的方法
// 加载所有库存记录到内存列表,并定位到第一条 public void loadAllStocks() { stockList = getStocks(null); currentIndex = stockList.isEmpty() ? -1 : 0; if (currentIndex != -1) { syncModelWithCurrentRecord(); } } // 将当前索引的记录数据同步到模型自身属性 private void syncModelWithCurrentRecord() { MainModel current = stockList.get(currentIndex); this.setStockCode(current.getStockCode()); this.setStockName(current.getStockName()); this.setStockType(current.getStockType()); this.setUnit(current.getUnit()); this.setBarcode(current.getBarcode()); this.setKdv(current.getKdv()); this.setExplanation(current.getExplanation()); this.setCreateDate(current.getCreateDate()); }
- 实现导航方法(下一条、上一条、第一条、最后一条)
// 下一条记录,返回是否成功 public boolean nextRecord() { if (stockList == null || currentIndex >= stockList.size() - 1) { return false; } currentIndex++; syncModelWithCurrentRecord(); return true; } // 上一条记录 public boolean previousRecord() { if (stockList == null || currentIndex <= 0) { return false; } currentIndex--; syncModelWithCurrentRecord(); return true; } // 第一条记录 public boolean firstRecord() { if (stockList == null || stockList.isEmpty()) { return false; } currentIndex = 0; syncModelWithCurrentRecord(); return true; } // 最后一条记录 public boolean lastRecord() { if (stockList == null || stockList.isEmpty()) { return false; } currentIndex = stockList.size() - 1; syncModelWithCurrentRecord(); return true; }
- 修复
getStocks方法的错误(重复设置stockCode)
public List<MainModel> getStocks(String Stock_Code) { try { PreparedStatement stmt = null; // 改用局部变量,避免成员变量被覆盖 ResultSet localRs = null; if (Stock_Code != null) { stmt = getConnection().prepareStatement("select stokKodu,stokAdi,stokTipi,birimi,barkodu,kdvTipi,aciklama,olusturmaTarihi from stockcard1 where stokKodu=? or stokAdi=?"); stmt.setString(1, Stock_Code); stmt.setString(2, Stock_Code); } else { stmt = getConnection().prepareStatement("select * from stockcard1"); } localRs = stmt.executeQuery(); List<MainModel> models = new ArrayList<>(); while(localRs.next()) { MainModel model = new MainModel(); model.setStockCode(localRs.getString("stokKodu")); // 删除重复的setStockCode行 model.setStockName(localRs.getString("stokAdi")); model.setStockType(localRs.getInt("stokTipi")); model.setUnit(localRs.getString("birimi")); model.setBarcode(localRs.getString("barkodu")); model.setKdv(localRs.getDouble("kdvTipi")); model.setExplanation(localRs.getString("aciklama")); model.setCreateDate(localRs.getString("olusturmaTarihi")); models.add(model); } // 关闭局部资源 localRs.close(); stmt.close(); return models; } catch (Exception ex){ Logger.getLogger(MainController.class.getName()).log(Level.SEVERE, null, ex); } return new ArrayList<>(); }
方案2:基于可滚动ResultSet的导航(适合大数据量场景)
这种方案直接操作数据库的可滚动ResultSet,无需加载所有数据到内存,但需要保持数据库连接,注意资源释放。
修改MainModel代码步骤:
- 修改
getConnection后的Statement创建,指定可滚动ResultSet类型
// 初始化可滚动的ResultSet public void initScrollableResultSet() { try { // 创建可滚动、只读的Statement statement = getConnection().prepareStatement( "select * from stockcard1", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY ); rs = statement.executeQuery(); // 定位到第一条记录并同步到模型 if (rs.first()) { syncModelWithResultSet(); } } catch (SQLException ex) { Logger.getLogger(MainModel.class.getName()).log(Level.SEVERE, null, ex); } } // 将当前ResultSet指针的记录同步到模型属性 private void syncModelWithResultSet() throws SQLException { this.setStockCode(rs.getString("stokKodu")); this.setStockName(rs.getString("stokAdi")); this.setStockType(rs.getInt("stokTipi")); this.setUnit(rs.getString("birimi")); this.setBarcode(rs.getString("barkodu")); this.setKdv(rs.getDouble("kdvTipi")); this.setExplanation(rs.getString("aciklama")); this.setCreateDate(rs.getString("olusturmaTarihi")); }
- 实现导航方法
public boolean nextRecord() { try { if (rs != null && rs.next()) { syncModelWithResultSet(); return true; } } catch (SQLException ex) { Logger.getLogger(MainModel.class.getName()).log(Level.SEVERE, null, ex); } return false; } public boolean previousRecord() { try { if (rs != null && rs.previous()) { syncModelWithResultSet(); return true; } } catch (SQLException ex) { Logger.getLogger(MainModel.class.getName()).log(Level.SEVERE, null, ex); } return false; } public boolean firstRecord() { try { if (rs != null && rs.first()) { syncModelWithResultSet(); return true; } } catch (SQLException ex) { Logger.getLogger(MainModel.class.getName()).log(Level.SEVERE, null, ex); } return false; } public boolean lastRecord() { try { if (rs != null && rs.last()) { syncModelWithResultSet(); return true; } } catch (SQLException ex) { Logger.getLogger(MainModel.class.getName()).log(Level.SEVERE, null, ex); } return false; } // 关闭数据库资源,在窗口关闭时调用 public void closeResources() { try { if (rs != null) rs.close(); if (statement != null) statement.close(); if (connection != null) connection.close(); } catch (SQLException ex) { Logger.getLogger(MainModel.class.getName()).log(Level.SEVERE, null, ex); } }
控制器层调用示例
在MainController中初始化导航,并绑定按钮事件:
// 初始化(以方案1为例) MainModel model = new MainModel(); model.loadAllStocks(); // 更新View显示模型数据 updateView(model); // 下一条按钮事件 btnNext.addActionListener(e -> { if (model.nextRecord()) { updateView(model); } }); // 上一条按钮事件 btnPrevious.addActionListener(e -> { if (model.previousRecord()) { updateView(model); } });
额外优化建议
- 将
connection和statement成员变量改为private,避免外部直接操作 - 在save/update/delete方法中添加资源关闭逻辑,避免泄漏
- 内部类
Service和数据库操作方法重名,建议重命名为InMemoryStockService,区分内存操作和数据库操作
内容的提问来源于stack exchange,提问作者weit
相关产品推荐
相关产品推荐

