0

I have a JTable that is populated by an Access DB using a ResultSet &AbstractTableModel. I have a method that deletes the record from the DB correctly but am having trouble refreshing the current view of the table model. I've looked at similar posts and have tried using fireTableRowsDeleted and fireTableDataChanged but have had no luck. I also noticed that other posts mention the use of the DefaultTableModel as it has add/remove row methods but the code I have working is from my Java textbook I had used last year (professor never reached this point so I was trying to learn this myself)...

Here's the class for the custom JFrame:

class AdministrationFrame extends JFrame
{
 //set variable for location of database
 private static final File DB_FILE = 
   new File("C:\\Eclipse\\EmpInOutBoard - TEMP.accdb"); 
 
 //database URL
 private static final String DB_URL = "jdbc:ucanaccess://" + DB_FILE.getAbsolutePath();
 
 //set default query to retrieve all users sorted by last name
 private static final String DEFAULT_QUERY = 
   "SELECT EmployeeNo, FirstName, LastName, DisplayName, GroupName, CompanyName "
    + "FROM Employees "
    + "WHERE DisabledState = false "
    + "ORDER BY LastName";
 
 //layout for window
 private final BorderLayout layout;
 
 //administration window
 private final JFrame adminFrame;
 
 private final JPanel tablePanel;
 private final JPanel tablePanel2;
 private final JPanel buttonPanel;
 
 //items for tablePanel
 private final JLabel filterLabel;
 private final JTextField filterTextField;
 private final JButton filterButton;
 
 //items for buttonPanel
 private final JButton updateButton;
 private final JButton deleteButton;
 
 private String employeeID;
 
 private Connection conn;
 
 private JTable resultTable;
 
 private static ResultSetTableModel tblModel;
 
 public AdministrationFrame()
 {
  layout = new BorderLayout(10, 10);
  setLayout(layout);
  
  //place GUI components on JFrame's content pane
  adminFrame = new JFrame("Employee Modification Panel");
  
  //set up JPanels
  tablePanel = new JPanel();
  tablePanel2 = new JPanel();
  
  String tablePanelTitle = "Employee Details";
  tablePanel.setBorder(BorderFactory.createTitledBorder(null, 
    tablePanelTitle, TitledBorder.CENTER, TitledBorder.TOP,
    new Font("Arial", Font.BOLD + Font.ITALIC, 22), Color.BLACK));
  
  tablePanel2.setLayout(new BoxLayout(tablePanel2, BoxLayout.Y_AXIS));
  
  buttonPanel = new JPanel();
  
  //set up items in each JPanel
  filterLabel = new JLabel("Filter:");
  filterLabel.setAlignmentX(LEFT_ALIGNMENT);
  filterTextField = new JTextField();
  filterTextField.setAlignmentX(LEFT_ALIGNMENT);
  filterButton = new JButton("Apply Filter");
  filterButton.setAlignmentX(LEFT_ALIGNMENT);
  
  updateButton = new JButton("Add/Update");
  deleteButton = new JButton("Delete");
  
  //create ResultSetTableModel and display database table
  try
  {
   //create TableModel for results of the default query
   tblModel = new ResultSetTableModel(DB_URL, DEFAULT_QUERY);
   
   //create JTable based on the tblModel
   resultTable = new JTable(tblModel)
   {
    @Override
    public Dimension getPreferredScrollableViewportSize()
    {
     return new Dimension(600, 250);
    }
   };
   
   resultTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
   resultTable.getTableHeader().setResizingAllowed(false); //disable column resizing
   resultTable.getTableHeader().setReorderingAllowed(false); //disable column dragging
   resultTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); //sets table to only allow selection of single row
   resultTable.getSelectionModel().addListSelectionListener(new RowListener()); //register event handlers
   final JScrollPane tablePane = new JScrollPane(resultTable);
   
   //add items to JPanels
   tablePanel2.add(filterLabel);
   tablePanel2.add(Box.createRigidArea(new Dimension(0, 2)));
   tablePanel2.add(filterTextField);
   tablePanel2.add(Box.createRigidArea(new Dimension(0, 10)));
   tablePanel2.add(filterButton);
   
   tablePanel.add(tablePane);
   tablePanel.add(tablePanel2);
      
   buttonPanel.add(updateButton);
   buttonPanel.add(deleteButton);


   //add JPanels to frame
   adminFrame.add(tablePanel, BorderLayout.NORTH);
   adminFrame.add(buttonPanel, BorderLayout.SOUTH);   

   final TableRowSorter<TableModel> sorter = new TableRowSorter<TableModel>(tblModel);
   resultTable.setRowSorter(sorter);
   
   //create listener for filterButton
   filterButton.addActionListener(new ActionListener()
   {
    //pass filter text to Listener
    public void actionPerformed(ActionEvent e)
    {
     String text = filterTextField.getText();
     
     if (text.length() == 0)
      sorter.setRowFilter(null);
     else
     {
      try
      {
       //make filter case-insensitive
       sorter.setRowFilter(RowFilter.regexFilter("(?i)" + text));
      }
      catch (PatternSyntaxException pse)
      {
       JOptionPane.showMessageDialog(null, "Bad regex pattern",
         "Bad regex pattern", JOptionPane.ERROR_MESSAGE);
      }
     }
    }
   });
   
   deleteButton.addActionListener(new ActionListener()
   {
    @Override
    public void actionPerformed(ActionEvent e)
    {
     if(employeeID != null && !employeeID.isEmpty())
     {
      try
      {
       deleteFromTable(employeeID);
       JOptionPane.showMessageDialog(null, "User " + employeeID + " deleted from the table",
         "Successful Deletion", JOptionPane.PLAIN_MESSAGE);
       
       //tblModel.fireTableRowsDeleted(resultTable.getSelectedRow(), resultTable.getSelectedRow());

      } 
      catch (SQLException e1) 
      {
       e1.printStackTrace();
      }
     }
     else
     {
      JOptionPane.showMessageDialog(null, "No user selected. Cannot perform delete.",
        "ERROR", JOptionPane.ERROR_MESSAGE);
     }
    }
   });
   
   pack();
   
   //dispose of window when user quits application
   //(do not want to close application)
   adminFrame.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
   adminFrame.setSize(800, 400);
   adminFrame.setVisible(true);
   adminFrame.setLocationRelativeTo(null);
   adminFrame.setResizable(false);
   
   //ensure database is closed when user quits application
   adminFrame.addWindowListener(new WindowAdapter()
   {
    //disconnect from database and exit when window has closed
    public void windowClosed(WindowEvent event)
    {
     tblModel.disconnectFromDatabase();
     System.exit(0);
    }
   });
  }
  catch (SQLException sqlException)
  {
   JOptionPane.showMessageDialog(null, sqlException.getMessage(),
     "Database error", JOptionPane.ERROR_MESSAGE);
   tblModel.disconnectFromDatabase();
   System.exit(1); //terminate application
  }
 }
 
 private class RowListener implements ListSelectionListener
 {
  @Override
  public void valueChanged(ListSelectionEvent event)
  {
   if(!event.getValueIsAdjusting())
   {
    int row = resultTable.getSelectedRow();
    if(row == -1) //no row found
     JOptionPane.showMessageDialog(adminFrame, "Selected row not found in filtered set",
       null, JOptionPane.WARNING_MESSAGE);
    else
    {
     employeeID = resultTable.getValueAt(row, resultTable.getColumn("EmployeeNo").getModelIndex()).toString();
    }
   }
  }
 };

 //updates DB to delete selected user/record from the table
 public void deleteFromTable(String empID) throws SQLException
 {
  PreparedStatement prepStmnt = null;
  
  String deleteSQL = "DELETE "
       + "FROM Employees "
       + "WHERE EmployeeNo = ?";
  try
  {
   conn = DriverManager.getConnection(DB_URL);
   conn.setAutoCommit(false);
   
   prepStmnt = conn.prepareStatement(deleteSQL);
   prepStmnt.setString(1, empID);
   prepStmnt.executeUpdate();
   conn.commit();
  }
  catch (SQLException sqlExcep)
  {
   sqlExcep.printStackTrace();
   if(conn != null)
   {
    try
    {
     System.err.print("Rolling back transaction");
     conn.rollback();
    }
    catch (SQLException excep)
    {
     excep.printStackTrace();
    }
   }
   System.exit(1); //terminate application
  }
  finally //close the connection
  {
   if(prepStmnt != null)
    prepStmnt.close();
   
   conn.setAutoCommit(true);
   conn.close();
  }
 }
 
}//end class EmpInOutBoard

And here's the class for the AbstractTableModel (slightly modified from my textbook):

class ResultSetTableModel extends AbstractTableModel
{
 private final Connection connection;
 private final Statement statement;
 private ResultSet resultSet;
 private ResultSetMetaData resultSetMetaData;
 private int numRowCount;

 //track DB connection status
 private boolean dbConnStatus = false;
 
 //constructor initializes rSet and obtains its
 //metadata object; also determines number of rows
 public ResultSetTableModel(String url, String query) throws SQLException
 {
  //connect to the database
  connection = DriverManager.getConnection(url);
  
  //create statement to query database
  statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
  
  //update database connection status
  dbConnStatus = true;
  
  //set query and execute it
  setQuery(query);
 }
 
 //get class that represents column type
 @SuppressWarnings({ "unchecked", "rawtypes" })
 public Class getColumnClass(int column) throws IllegalStateException
 {
  //ensure database connection is available
  if(!dbConnStatus)
   throw new IllegalStateException("No connection to the Database");
  
  //determine Java class of column
  try
  {
   String className = resultSetMetaData.getColumnClassName(column + 1);
   
   //return Class object that represents class Name
   return Class.forName(className);
  }
  catch (Exception e)
  {
   e.printStackTrace();
  }
  
  return Object.class; //if problems occur above, assume type Object
 }
 
 //remove row in the ResultSet
 public void removeRow(int row)
 {
//  try
//  {
//   resultSet.absolute(row);
//   resultSet.deleteRow();
//  }
//  catch (SQLException e)
//  {
//   e.printStackTrace();
//  }
//  fireTableRowsDeleted(row, row);
 }
 
 //get the number of columns in the ResultSet
 public int getColumnCount() throws IllegalStateException
 {
  //ensure database connection is available
  if(!dbConnStatus)
   throw new IllegalStateException("No connection to the Database");
  
  //determine number of columns
  try
  {
   return resultSetMetaData.getColumnCount();
  }
  catch (SQLException sqlException)
  {
   sqlException.printStackTrace();
  }
  
  return 0; //if problem occur above, return 0 for number of columns
 }
 
 //get name of a particular column in ResultSet
 public String getColumnName(int column) throws IllegalStateException
 {
  //ensure database connection is available
  if(!dbConnStatus)
   throw new IllegalStateException("No connection to the Database");
  
  //determine column name
  try
  {
   return resultSetMetaData.getColumnName(column + 1);
  }
  catch (SQLException sqlException)
  {
   sqlException.printStackTrace();
  }
  
  return ""; //if problems occur above, return empty string for column name 
 }
 
 //return number of rows in ResultSet
 public int getRowCount() throws IllegalStateException
 {
  //ensure database connection is available
  if(!dbConnStatus)
   throw new IllegalStateException("No connection to the Database");
  
  return numRowCount; 
 }
 
 //obtain value in particular row and column
 public Object getValueAt(int row, int column) throws IllegalStateException
 {
  //ensure database connection is available
  if(!dbConnStatus)
   throw new IllegalStateException("No connection to the Database");
  
  //obtain a value at specified ResultSet row and column
  try
  {
   resultSet.absolute(row + 1);
   return resultSet.getObject(column + 1);
  }
  catch (SQLException sqlException)
  {
   sqlException.printStackTrace();
  }
  
  return ""; //if problems occur above, return empty string object  
 }
 
 //set new database query string
 public void setQuery(String query) throws SQLException, IllegalStateException
 {
  //ensure database connection is available
  if(!dbConnStatus)
   throw new IllegalStateException("No connection to the Database");
  
  //specify query and execute it
  resultSet = statement.executeQuery(query);
  
  //obtain metadata for ResultSet
  resultSetMetaData = resultSet.getMetaData();
  
  //determine number of rows in ResultSet
  resultSet.last(); //move to last row
  numRowCount = resultSet.getRow(); //get row number
  
  //notify JTable that model has changed
  fireTableStructureChanged();
 }
 
 //close Statement and Connection
 public void disconnectFromDatabase()
 {
  //ensure database connection is available
  if(dbConnStatus);
  
  //determine number of columns
  try
  {
   resultSet.close();
   statement.close();
   connection.close();
  }
  catch (SQLException sqlException)
  {
   sqlException.printStackTrace();
  }
  finally
  {
   dbConnStatus = false;
  }
  
 }
} //end class ResultSetTableModel

As you can see, I've tried to add a removeRow method within the second class but I end up getting an error: attempt to assign to non-updatable column. What am I missing to refresh my table model? I am willing to change this to use DefaultTableModel with its built-in methods if this would be easier/simpler. Thanks

Probius
  • 79
  • 10
  • You have a number of things you need to change. The `ResultSetTableModel#removeRow` method is heading in the right direction, but you also need to update the `numRowCount`. The JavaDocs for [`ResultSet`](https://docs.oracle.com/javase/8/docs/api/java/sql/ResultSet.html) also has a example of how you might use `insertRow` in a simular fashion – MadProgrammer May 08 '17 at 21:15
  • So, I suggest calling the `ResultSetTableModel#removeRow` over `deleteFromTable` as there is more of a relationship between the `TableModel` and the `ResultSet` – MadProgrammer May 08 '17 at 21:15
  • @MadProgrammer Thanks for the suggestion - I'll check the JavaDocs like you said. I figured if I could get the delete portion nailed, then the insert shouldn't be too hard to figure out afterwards. – Probius May 08 '17 at 21:43
  • I changed my `removeRow` method to take the employeeID instead of the row number. Then using a similar method to iterate through the result set [here](https://docs.oracle.com/cd/A87860_01/doc/java.817/a83724/samapp7.htm#1017338), I found the matching record and was able to delete it successfully. I then used `resultSet.last()` and `numRowCount = resultSet.getRow()` to update that variable. Lastly I fired off `fireTableRowsDeleted` to update the table but it disappears instead (I can post the code but wasn't sure if that would be valid as 'answer') – Probius May 09 '17 at 21:15
  • If the solution you have solves the problem, the you should post it as an answer. I'd be curious as to why you can't use a direct row index to result set index though – MadProgrammer May 09 '17 at 22:02
  • @MadProgrammer - do you see a reason why the data in the JTable would delete after `fireTableRowsDeleted`? I've tried several approaches to fix this including manually decrementing `numRowCount` by 1 but this just throws an `invalid cursor state: ResultSet is empty` error. – Probius May 11 '17 at 21:30
  • Sounds like your `ResultSet` might have been closed or there database has removed the reference to it for some reason – MadProgrammer May 11 '17 at 21:49
  • @MadProgrammer - I added a small check null check (if resultSet !=null) and the application came back saying it was still open...also in regards to the `attempt to assign to non-updatable column` error I was getting previously, it was because I had a calculated field in my Access database (had to do some research to figure that out) – Probius May 11 '17 at 22:07
  • Sorry I should clarify - it's not your `ResultSet` object that's been dereferenced, but the database resource which your `ResultSet` object represents, so when you ask it for information, it asks the database and the database says "I have no idea what you're talking about" - bang, problem - this is a guess – MadProgrammer May 11 '17 at 22:14
  • @MadProgrammer - you are correct. I added a few if statements (`if(!resultSet.next())`) to check: one at the beginning of the `removeRow` method and the second immediately after `resultSet.deleteRow();`. The second if confirms that the data set is empty or as you say the database has been 'dereferenced.' Honestly, I'm stumped as to how to proceed at this point... – Probius May 12 '17 at 14:49
  • @MadProgrammer - sorry it took me time to get back to this...I ended up changing my DB to Oracle as we're not really supposed to be using Access for projects like this. The delete works fine now except when I try to delete the last row in the table (I'll post a different question for that). In any case, this may be some bug in HSQLDB according to this [post](https://stackoverflow.com/questions/15159170/hsqldb-delete-rows-from-jdbc-resultset). – Probius May 31 '17 at 21:04

1 Answers1

0

I did not use a direct row index because that may change if the user sorted the data by any one of the columns. I felt that since employeeID was unique that it would be better to match on that:

//remove row in the ResultSet
 public void removeRow(String empID)
 {
  int rsRow = 0;
  try
  {
   //set cursor to beginning of data set (before first row)
   if(!resultSet.isBeforeFirst())
    resultSet.beforeFirst();
   
   //iterate through resultSet to find matching record with
   //correct employee ID. once found delete row
   while(resultSet.next())
   {
    if(resultSet.getString("EmployeeNo") == empID)
    {
     rsRow = resultSet.getRow();
     
     resultSet.deleteRow();
     System.out.println("User: " + empID + " was deleted from row: " + rsRow);
     break;
    }
   }
   
   resultSet.last();
   numRowCount = resultSet.getRow();
   
   fireTableRowsDeleted(rsRow, rsRow);

//   resultSet.absolute(rsRow);
//   resultSet.deleteRow();
  }
  catch (SQLException e)
  {
   e.printStackTrace();
  }
 }
 

Of course, now the data in the JTable is disappearing which I don't understand.

Probius
  • 79
  • 10