实现MySQL外键关联的JComboBox与JTextField联动功能
解决方案
核心思路拆解
要实现「选主表ID → 加载关联的子表ID列表 → 选子表ID显示对应内容」的联动效果,需要分三步改造:
- 先加载所有可选的主表(habito)ID到第一个下拉框
- 监听第一个下拉框的选择事件,根据选中的主ID加载关联的子表(tb_timer)ID到第二个下拉框
- 监听第二个下拉框的选择事件,根据选中的子ID查询对应内容并显示到文本框
第一步:新增DAO查询方法
在现有DAO类中添加三个针对性的查询方法:
// 获取所有habito的ID(从主表tb_habito查询,更规范;若仅需tb_timer中存在的关联ID,可改用SELECT DISTINCT fk_habito_id FROM tb_timer) public Integer[] obterTodosIdsHabito() throws Exception { String sql = "SELECT DISTINCT id FROM tb_habito"; try (Connection conn = ConexaoBD.obterConexao(); PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet rs = ps.executeQuery()) { int total = rs.last() ? rs.getRow() : 0; Integer[] idsHabito = new Integer[total]; rs.beforeFirst(); int contador = 0; while (rs.next()) { idsHabito[contador++] = rs.getInt("id"); } return idsHabito; } } // 根据habito ID获取关联的所有alarme ID public Integer[] obterIdsAlarmePorHabito(int idHabito) throws Exception { String sql = "SELECT id FROM tb_timer WHERE fk_habito_id = ?"; try (Connection conn = ConexaoBD.obterConexao(); PreparedStatement ps = conn.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { ps.setInt(1, idHabito); try (ResultSet rs = ps.executeQuery()) { int total = rs.last() ? rs.getRow() : 0; Integer[] idsAlarme = new Integer[total]; rs.beforeFirst(); int contador = 0; while (rs.next()) { idsAlarme[contador++] = rs.getInt("id"); } return idsAlarme; } } } // 根据alarme ID获取对应的触发时间 public String obterHoraToquePorAlarmeId(int idAlarme) throws Exception { String sql = "SELECT hora_toque FROM tb_timer WHERE id = ?"; try (Connection conn = ConexaoBD.obterConexao(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, idAlarme); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { return String.valueOf(rs.getTime("hora_toque")); } return null; } } }
第二步:改造JFrame代码
替换原有的初始化和绑定逻辑,添加下拉框联动监听:
public TelaAlarmes() { super("Alarme"); initComponents(); inicializarComboBoxHabito(); adicionarEventListeners(); setLocationRelativeTo(null); } // 初始化第一个下拉框:加载所有habito ID private void inicializarComboBoxHabito() { try { DAO dao = new DAO(); Integer[] idsHabito = dao.obterTodosIdsHabito(); cbbIdHabito.setModel(new DefaultComboBoxModel<>(idsHabito)); } catch (Exception e) { JOptionPane.showMessageDialog(null, "无法加载 habitos 的ID列表!"); e.printStackTrace(); } } // 添加下拉框联动监听 private void adicionarEventListeners() { // 第一个下拉框选择事件:加载关联的alarme ID cbbIdHabito.addActionListener(e -> { Integer idHabitoSelecionado = (Integer) cbbIdHabito.getSelectedItem(); if (idHabitoSelecionado != null) { carregarIdsAlarme(idHabitoSelecionado); } }); // 第二个下拉框选择事件:加载对应的触发时间 cbbAlarmeId.addActionListener(e -> { Integer idAlarmeSelecionado = (Integer) cbbAlarmeId.getSelectedItem(); if (idAlarmeSelecionado != null) { carregarHoraToque(idAlarmeSelecionado); } }); } // 根据选中的habito ID加载alarme ID到第二个下拉框 private void carregarIdsAlarme(int idHabito) { try { DAO dao = new DAO(); Integer[] idsAlarme = dao.obterIdsAlarmePorHabito(idHabito); cbbAlarmeId.setModel(new DefaultComboBoxModel<>(idsAlarme)); txtHoraToque.setText(""); // 清空文本框避免旧数据残留 } catch (Exception e) { JOptionPane.showMessageDialog(null, "无法加载关联的 alarme ID列表!"); e.printStackTrace(); } } // 根据选中的alarme ID加载触发时间到文本框 private void carregarHoraToque(int idAlarme) { try { DAO dao = new DAO(); String horaToque = dao.obterHoraToquePorAlarmeId(idAlarme); txtHoraToque.setText(horaToque != null ? horaToque : ""); } catch (Exception e) { JOptionPane.showMessageDialog(null, "无法加载 alarme 的触发时间!"); e.printStackTrace(); } }
为什么原有代码不符合需求?
原代码直接把所有Alarme对象塞到cbbIdHabito下拉框,导致下拉框显示的是Alarme类的默认toString值(类名+哈希码),完全没有实现「选主ID→关联子ID→显示内容」的联动逻辑。
内容的提问来源于stack exchange,提问作者Iann Schmith
相关产品推荐
相关产品推荐

