1

What is the proper relationship, in code, between a table model and the actual database queries?

Inside the addRow() method in the table model, should I place a further call to my database class, which in turn inserts the row into the database? I've illustrated this in the below code snippets.

public class MainPanel extends JPanel
{
    ...

    public MainPanel()
    {

        personTableModel = new PersonTableModel();

        personTable = new JTable(personTableModel);
        ...

        insertButton = new JButton("Insert");
        insertButton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e)
            {
                String name = nameTextBox.getText();
                String address = addressTextBox.getText();

                Object[] row = { name, address };

                personTableModel.addRow(row);  // <--- Add row to model
            }
        });    
    }
}

public class PersonTableModel extends AbstractTableModel
{
    private List<Object[]> data;
    private List<String> columnNames;

    PersonDB personDB = new PersonDB();
    ...

    public void addRow(Object[] row)
    {
        // insert row into 'data'

        personDB.addPerson(row);  // <---- Call the personDB database class
    }

    ...
}

public class PersonDB
{

    public PersonDB()
    {
        // establish database connection
    }

    public addPerson(Object[] row)
    {
        // code that creates a SQL statement based on row data
        // and inserts new row into database.
    }
    ...
}
user2864740
  • 60,010
  • 15
  • 145
  • 220
JDJ
  • 4,298
  • 3
  • 25
  • 44
  • I separate the concerns via "[DTOs](http://en.wikipedia.org/wiki/Data_transfer_object)" (or a BO/DAO variant) such that there is no such "relationship". Very generic table manipulators are good for database tools - but usually not good for applications. Generic and reflective auto-mapping tools can be used in place to minimize redundant code. – user2864740 Dec 29 '13 at 20:12
  • So since this is a desktop application where the database is stored locally, is the above code structure acceptable, except for the fact that in addRow() of the table model, I should be calling a generic DAO instead of directly calling the more specific database class that I called above? – JDJ Dec 29 '13 at 20:38

1 Answers1

0

Whether or not you should directly make an insert call depends on some aspects:

  • Do you want other processes to access the data immediately?
  • Do you fear that your program crashes and you lose important information?
  • Can you ensure that any data persisted during addRow is meaningful (the program could terminate directly after the insert)?

Than of course it may be a good idea to directly insert the data into the backing Database.

You should however watch out, that there are two variants of addRow and two variants of insertRow. DefaultTableModel directs calls internally through insertRow(int, Vector), which would probably be the only function to overwrite, if you want to immediately persist data.

If you like the proposed idea of DTOs the examples below may help you.

The Idea is to represent "Entities" or table rows as classes in Java. A DTO is the simplest representation and normally only contains fields with respective getter and setter.

Entities can generically be persisted and loaded through ORM libraries like EclipseLink or Hibernate. Additionally for this table-application the use of DTOs provide a way of storing data not shown to the user in a clean and typed way.

DTO:

public class PersonDto {
    private Long id;
    private String name;
    private String street;

    public PersonDto() {
    }

    public PersonDto(Long id, String name, String street) {
        this.id = id;
        this.name = name;
        this.street = street;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public static class NameColumn extends DtoTableModel.ColumnProvider<PersonDto, String> {

        public NameColumn() {
            super("Name", String.class);
        }

        @Override
        public String getValue(PersonDto dto) {
            return dto.getName();
        }

        @Override
        public void setValue(PersonDto dto, Object value) {
            dto.setName((String) value);
        }
    }

    public static class StreetColumn extends DtoTableModel.ColumnProvider<PersonDto, String> {

        public StreetColumn() {
            super("Street", String.class);
        }

        @Override
        public String getValue(PersonDto dto) {
            return dto.getStreet();
        }

        @Override
        public void setValue(PersonDto dto, Object value) {
            dto.setStreet((String) value);
        }
    }
}

DTO based TableModel:

import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;

public class DtoTableModel<T> extends AbstractTableModel {

    private final ArrayList<T>                    rows;
    private final ArrayList<ColumnProvider<T, ?>> columns;

    protected DtoTableModel() {
        rows = new ArrayList<T>();
        columns = new ArrayList<ColumnProvider<T, ?>>();
    }

    @Override
    public int getRowCount() {
        return rows.size();
    }

    @Override
    public int getColumnCount() {
        return columns.size();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return columns.get(columnIndex).getValue(rows.get(rowIndex));
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        final ColumnProvider<T, ?> column = columns.get(columnIndex);
        column.setValue(rows.get(rowIndex), aValue);
        this.fireTableCellUpdated(rowIndex, columnIndex);
    }

    @Override
    public String getColumnName(int column) {
        return columns.get(column).getTitle();
    }

    public void addColumn(ColumnProvider<T, ?> column) {
        this.columns.add(column);
        this.fireTableStructureChanged();
    }

    public void addRow(T row) {
        this.rows.add(row);
        this.fireTableRowsInserted(this.rows.size() - 1, this.rows.size() - 1);
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return this.columns.get(columnIndex).getValueClass();
    }

    public static abstract class ColumnProvider<T, V> {
        private       String   title;
        private final Class<V> valueClass;

        protected ColumnProvider(String title, Class<V> valueClass) {
            this.title = title;
            this.valueClass = valueClass;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public Class<V> getValueClass() {
            return valueClass;
        }

        public abstract V getValue(T dto);

        public abstract void setValue(T dto, Object value);
    }
}

Example-"Application":

import javax.swing.*;
import java.awt.*;

public class JTableTest extends JFrame {

    private final JTable jTable;

    public JTableTest() throws HeadlessException {
        super("JFrame test");
        this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);

        final GridBagLayout layout = new GridBagLayout();
        final Container contentPane = this.getContentPane();
        contentPane.setLayout(layout);

        final GridBagConstraints gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.fill = GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;

        final DtoTableModel<PersonDto> dm = new DtoTableModel<PersonDto>();
        jTable = new JTable(dm);
        dm.addColumn(new PersonDto.NameColumn());
        dm.addColumn(new PersonDto.StreetColumn());
        dm.addRow(new PersonDto(1L, "Paul", "Mayfairy Street"));
        dm.addRow(new PersonDto(2L, "Peter", "Ferdinand Street"));
        JScrollPane scrollpane = new JScrollPane(jTable);
        contentPane.add(scrollpane, gridBagConstraints);

        this.pack();
    }

    public static void main(String[] args) {
        final JTableTest jTableTest = new JTableTest();
        jTableTest.setVisible(true);
    }

}
TheConstructor
  • 4,285
  • 1
  • 31
  • 52
  • Thanks very much for your answer. I just have a quick question about the insertRow(int, Vector) that you mentioned. Would it be appropriate to use this method to actually replace the contents of the specified row? – JDJ Dec 30 '13 at 02:43
  • As you are extending AbstractTableModel and not DefaultTableModel (even though you could, if you just use a Object[] per row) you can implement insertRow yourself. For DefaultTableModel no row is replaced, but all rows starting with the index where the row is to be inserted are moved one index up, thereby working like [add(int, E)](http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html#add(int,%20E)) and not [set(int, E)](http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html#set(int,%20E)). – TheConstructor Dec 30 '13 at 16:58
  • 1
    beware: model's setValueAt must fire a cellUpdated! – kleopatra Jan 08 '14 at 16:20
  • Good catch. Did the coding from scratch and missed that event. Just added the call. – TheConstructor Jan 08 '14 at 20:33