3

I'm writing a program using jdbc that will be an interface to database(smth like CRUD aplication). I assume that I have to write a class(e.g. DBCLass) that will do all the operations with database(select, update, insert, delete and maybe some other logic that will be reduced to these operations). User interface consists of a set of tables and a few buttons. To use a Jtable I need to implement a class(e.g Model) which is a subclass of AbstractTableModel. So this class will display my data to the user. I need to implement such model for all tables in my database schema. I don't want to write the logic in that classes that display data to the user and I think it is not very good thing to write the logic code in such classes. But it is also incorrect to load all the data from the db table to memory(e.g. ArrayList) and then display it in Model. So, I want an advise which is the best way to solve such problem.

edit: A small example:

Statement stmt = ....;
ResaultSet rs = stmt.executeQuery("SELECT * FROM table1");

javadoc says that executeQuery method returns a ResultSet object that contains the data produced by the given query. So If we have a lot of data(which size is more than permited size to our virtual machine), our program will fail. So my question is still relevant

mKorbel
  • 109,525
  • 20
  • 134
  • 319
maks
  • 5,911
  • 17
  • 79
  • 123

6 Answers6

4

Download the source for SQuirreL SQl and have a look at the table implementation.

Some things to note:

Database tables aren't Java JTables. A table in a database is in fact a set (curse the fool who used the wrong term) with items and each item has properties (usually called "columns" which isn't a JColumn which explains why it's so hard to map the two).

A set can grow to any size. It has no intrinsic order. You can do lots of set operations on it like: union, difference, sub set.

Therefore, it's not a table, especially not a UI table.

There is no easy UI paradigm which maps "set" to "table". You can

  1. Load N records and page through the results.

  2. You can load more as the user scrolls down

  3. You can count the size of the set and adjust the scrollbar accordingly. As the user scrolls through the data, it is fetched from the DB and displayed.

Pros + cons:

Solution 1 is most simple to implement and the one which users hate most. Why do they need to wait to see data again when the go backwards? This is especially frustrating if each fetch takes 15 seconds. Page ... wait ... page ... oops! There it was! Damn! Wait wait wait ... back ... wait ... aaargh.

Also databases often have a hard time to page data. For some queries, performance can be disastrous.

Solution 2 is simple to implement, especially if you can keep the ResultSet open forever. But 100% of the time, you can't. It will start to fail if you keep it open for a couple of hours or a day. After that time, the DB will think "oh, it's dead, Jim" and close the connection and your user will get a nice error message and you will get an angry user.

So you need to page here, too, but not as often. On the positive side, users need not wait again for data they already have. One huge point: If the set contains millions of rows, users intuitively understand that they need to attack the problem from a different angle as they scroll down. Eventually, they will get tired and ask for a better solution (instead of being angry at you because your stupid program can't display 15 million rows in less than 0.0000000001s).

Solution 3 is worse than #2, again. If the table grows, the UI will become unusable: Even looking at the scroll know will move you to a random place in the table. So it makes your mouse useless. And your users angry.

So I usually try a solution which:

  1. Reads 1000 rows, max. Plus I stop after 100 rows (so the user has at least some data) if reading the rows takes more than 1 second. Usually, the query is slow and reading the result set takes virtually no time, but I like being defensive here.

  2. On top of each column is a filter and an "order by" which can be mapped directly to SQL. That way, I can make sure that if you want the data sorted by a column, it's sorted by all values (and not only those which you can see on the screen).

This allows users to chop huge amounts of data into meaningful sub sets.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • Thanks for answer, I've investigated different methods of mapping database data to Jtable and have prefered to use the next method: every table model has a method called getValueAt(int row, int column) which show the object, so I've written a method which return this object. It fetches the data from database using such query(select * from(select t.*, rownum r from (select * from test) t) where r = row) where a row is the current row which is rendered in JTable. As you can see this query always will return 1 object. How do you think, is this a good way to map the data? – maks Apr 04 '11 at 16:08
  • Yes, but it's slow if the query is more complex. I suggest to insert a cache between the table and the database which can remember, say, 100 rows. – Aaron Digulla Apr 05 '11 at 09:03
3

Table From Database has a couple of ideas.

camickr
  • 321,443
  • 19
  • 166
  • 288
1

Maks,

Here is another example on generic sql to table implementation:

http://www.oreillynet.com/pub/a/oreilly/java/news/javaex_1000.html

It may be a good place to look for your answer.

Also, this local question/answer may help you with ResultSet size: Java JDBC Lazy-Loaded ResultSet

Hope this helps,

Robert

Community
  • 1
  • 1
R. Simac
  • 737
  • 9
  • 9
0

This blog post explains how to lazy load data into a table model: JTable Bound to a Database With Lazy Loading

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
0

You want to use the DBTable component from the QuickTable project.

Check this SO answer with sample code usage.

Community
  • 1
  • 1
Stephan
  • 41,764
  • 65
  • 238
  • 329
-1

Here's a class that extract data rows and columns from database.

Look at table = new JTable(rows(), columns());

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.print.PrinterException;

import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import com.swtdesigner.SwingResourceManager;
import java.util.*;
import java.sql.*;
import javax.swing.*;
public class listing extends JDialog {


private JTable table;
public static Vector rows() {

    Vector data = new Vector();
    String sql= null;
    Connection C;


        try {

            DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
            C = (Connection) DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE",
                    "system", "manager");

            Statement stmt = C.createStatement();
            ResultSet rset = stmt.executeQuery("SELECT * FROM site ");
            ResultSetMetaData md = rset.getMetaData();
            int columns = md.getColumnCount();



            while (rset.next()) {
                Vector row = new Vector(columns);

                for (int i = 1; i <= columns; i++) {
                    row.addElement(rset.getObject(i));
                }

                data.addElement(row);

            }

            rset.close();
            stmt.close();
        } catch (Exception e) {
            System.out.println(e.getMessage());
            System.out.println(e.getStackTrace());
        }

        //System.out.println("lignes :  "+data);
        return data;
}


   public static Vector columns()
    {   Connection c ;
        Vector cols = new Vector ();
        String sql2=null;
        try { 
            DriverManager.registerDriver(new oracle.jdbc.OracleDriver());
            c = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:XE",
                    "system", "manager");

            sql2 = "select * from SITE";

            Statement stmt = c.createStatement();
            ResultSet rs = stmt.executeQuery(sql2);
            ResultSetMetaData md = rs.getMetaData();
            int columns = md.getColumnCount();

              //récupération des noms des colonnes dans la table site

            for (int i = 1; i <= columns; i++) {
            cols.addElement(md.getColumnName(i));
            }
           } catch (Exception e) {
            System.out.println(e.getMessage());
            System.out.println(e.getStackTrace());
        }
        //System.out.println("colonnes ::: "+cols);
        return cols;
    }

public static void main(String args[]) {
    try {
        listing dialog = new listing();
        dialog.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        dialog.setVisible(true);
    } catch (Exception e) {
        e.printStackTrace();
    }
}


public listing() {
    super();
    getContentPane().setLayout(null);
    setTitle("Listing de la table \"SITE\"");
    setBounds(100, 100, 500, 363);
    setResizable(false);

    final JLabel laTablesiteLabel = new JLabel();
    laTablesiteLabel.setText("La table \"SITE\" contient . . . ");
    laTablesiteLabel.setBounds(10, 34, 274, 16);
    getContentPane().add(laTablesiteLabel);

    final JLabel label = new JLabel();
    label.setIcon(SwingResourceManager.getIcon(listing.class, "/pictures/130.png"));
    label.setBounds(402, 18, 49, 48);
    getContentPane().add(label);

    final JButton okButton = new JButton();
    okButton.addActionListener(new ActionListener() {
        public void actionPerformed(final ActionEvent arg0) {
            dispose();
            setVisible(false);
        }


    });
    okButton.setIcon(SwingResourceManager.getIcon(listing.class, "/pictures/33-32x32.png"));
    okButton.setText("Ok");
    okButton.setBounds(10, 291, 148, 32);
    getContentPane().add(okButton);

    final JButton refeshButton_1 = new JButton();
    refeshButton_1.setIcon(SwingResourceManager.getIcon(listing.class, "/pictures/48-32x32.png"));
    refeshButton_1.setText("Actualiser");
    refeshButton_1.setBounds(171, 291, 148, 32);
    getContentPane().add(refeshButton_1);

    final JScrollPane scrollPane = new JScrollPane();
    scrollPane.setBounds(10, 85, 474, 187);
    getContentPane().add(scrollPane);

    table = new JTable(rows(), columns()); // chargement de JTable 
    scrollPane.setViewportView(table);

    final JButton printButton_1_1 = new JButton();
    printButton_1_1.addActionListener(new ActionListener() {
        public void actionPerformed(final ActionEvent arg0) {
            try {
                table.print();
            } catch (PrinterException e) {

                e.printStackTrace();
            }
        }
    });
    printButton_1_1.setIcon(SwingResourceManager.getIcon(listing.class, "/pictures/Printer_h.png"));
    printButton_1_1.setText("Imprimer");
    printButton_1_1.setBounds(336, 291, 148, 32);
    getContentPane().add(printButton_1_1);




    }

}
Ali Ben Messaoud
  • 11,690
  • 8
  • 54
  • 87
  • 1
    -1, Why would you execute the SQL query twice? That doesn't seem very efficient to me. Not only that you need to code the SQL code in two different places, which means you also need to maintain the code in two places. – camickr Mar 19 '11 at 01:00
  • You load hole data from database table to memory. If you have 1 million records in your table, your program will not work – maks Mar 19 '11 at 11:03