How to provide pagination support to a JTable in Swing? - java

I have created one GUI in Swing Java in which I have used JTable.Now I want to display next page information into it by using pagination. How should I do that ?

Another option to implement this is to use a scrollbar-less scrollpane and a couple nav buttons to effect the control. The buttons that have been added are normal JButtons for the prototype.
A quick prototype is added below. It makes a couple assumptions, one of which is that the table model has all of its data. Work could be done to ensure that rows end up flush at the top of the view upon navigation.
private void buildFrame() {
frame = new JFrame("Demo");
frame.setSize(300, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addStuffToFrame();
frame.setVisible(true);
}
private void addStuffToFrame() {
final JTable table = getTable();
final JScrollPane scrollPane = new JScrollPane(table);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
final JButton next = new JButton("next");
final JButton prev = new JButton("prev");
ActionListener al = new ActionListener(){
public void actionPerformed(ActionEvent e) {
Rectangle rect = scrollPane.getVisibleRect();
JScrollBar bar = scrollPane.getVerticalScrollBar();
int blockIncr = scrollPane.getViewport().getViewRect().height;
if (e.getSource() == next) {
bar.setValue(bar.getValue() + blockIncr);
} else if (e.getSource() == prev) {
bar.setValue(bar.getValue() - blockIncr);
}
scrollPane.scrollRectToVisible(rect);
}
};
next.addActionListener(al);
prev.addActionListener(al);
JPanel panel = new JPanel(new BorderLayout());
JPanel buttonPanel = new JPanel();
buttonPanel.add(prev);
buttonPanel.add(next);
panel.add(buttonPanel, BorderLayout.NORTH);
panel.add(scrollPane, BorderLayout.CENTER);
frame.getContentPane().add(panel);
}
private JTable getTable() {
String[] colNames = new String[]{
"col 0", "col 1", "col 2", "col 3"
};
String[][] data = new String[100][4];
for (int i = 0; i < 100; i++) {
for (int j = 0; j < 4; j++) {
data[i][j] = "r:" + i + " c:" + j;
}
}
return new JTable(data,colNames);
}
alt text http://img7.imageshack.us/img7/4205/picture4qv.png

Paging in a Swing JTable looks like a nice article.
Here is an excerpt:
As far as I remember the solution for this problem lies in the concept of paging: just retrieve the data that the user wants to see and nothing more. This also means you have to sometimes get extra data from the db server (or appserver) if your user scrolls down the list.
Big was my surprise that there wasn't really an out-of-the-box solution (not even a copy- paste solution) for this problem. Anyone that knows one, please don't hesitate to extend my (rather limited) knowledge of the J2EE platform.
So we dug in, and tried to build a solution ourselves.
What we eventually came up with was an adapted TableModel class to takes care of the paging.

You can try with 2 query, first query is to count total rows in DB and second query is for the real data :) And for the UI side, you can try like this:
public class MainForm extends javax.swing.JFrame {
private void initDefaultValue() {
rowsPerPage = Integer.valueOf(cmbPageSize.getSelectedItem().toString());
totalRows = Main.getTablePagingService().countComments();
Double dblTotPage = Math.ceil(totalRows.doubleValue()/rowsPerPage.doubleValue());
totalPage = dblTotPage.intValue();
if (pageNumber == 1) {
btnFirst.setEnabled(false);
btnPrevious.setEnabled(false);
} else {
btnFirst.setEnabled(true);
btnPrevious.setEnabled(true);
}
if (pageNumber.equals(totalPage)) {
btnNext.setEnabled(false);
btnLast.setEnabled(false);
} else {
btnNext.setEnabled(true);
btnLast.setEnabled(true);
}
txtPageNumber.setText(String.valueOf(pageNumber));
lblPageOf.setText(" of " + totalPage + " ");
lblTotalRecord.setText("Total Record " + totalRows + " rows.");
List wPComments = Main.getTablePagingService().findAllComment(pageNumber, rowsPerPage);
jTable1.setModel(new CommentTableModel(wPComments));
autoResizeColumn(jTable1);
}
private void btnFirstActionPerformed(ActionEvent evt) {
pageNumber = 1; initDefaultValue();
}
private void btnPreviousActionPerformed(ActionEvent evt) {
if (pageNumber > 1) {
pageNumber -= 1; initDefaultValue();
}
}
private void btnNextActionPerformed(ActionEvent evt) {
if (pageNumber
And in service layer, you just need use limit function like this :
public List findAllComment(Integer pageNumber, Integer rowsPerPage) {
try {
List listWP = new ArrayList();
preparedFindAll.setInt(1, (rowsPerPage*(pageNumber-1)));
preparedFindAll.setInt(2, rowsPerPage);
ResultSet rs = preparedFindAll.executeQuery();
while (rs.next()) {
WPComment comment = new WPComment();
comment.setCommentID(rs.getInt("comment_ID"));
comment.setCommentAuthor(rs.getString("comment_author"));
comment.setCommentDate(rs.getDate("comment_date"));
comment.setCommentContent(rs.getString("comment_content"));
listWP.add(comment);
}
return listWP;
} catch (SQLException ex) {
Logger.getLogger(TablePagingServiceJDBC.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
public Integer countComments() {
try {
Integer totalRows = 0;
ResultSet rs = preparedCount.executeQuery();
while (rs.next()) {
totalRows = rs.getInt("count(*)");
}
return totalRows;
} catch (SQLException ex) {
Logger.getLogger(TablePagingServiceJDBC.class.getName()).log(Level.SEVERE, null, ex);
}
return 0;
}
Or you can fork me on github at Project Page Table Paging on Swing :)

I have written a Java pagination tool dataj. It uses JQuery dataTables plug-in pagination metadata to build up the result page.
I have also added some client classes for Java Swing including a TableRowSorter that calls the (server side) sorting instead of sorting inside the table model.
Feel free to download it and contact me if you have any questions. It's under Apache 2 license.

Alternatively, you can make use of the QuickTable project.
Screenshot
Here is the DBTable component in action:
The DBTable component is embedded in a traditionnal JFrame.
Sample code
The following sample code produces the window shown in the previous screenshot:
import javax.swing.JFrame;
import javax.swing.UIManager;
import quick.dbtable.DBTable;
public class QuickTableFrame extends JFrame {
private static final long serialVersionUID = -631092023960707898L;
public QuickTableFrame() {
try {
// Use system look and feel
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
// set Frame properties
setSize(300, 200);
setVisible(true);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
// create a new quicktable
DBTable dBTable1 = new DBTable();
// add to frame
getContentPane().add(dBTable1);
// set the database driver to be used, we are using jdbc-odbc driver
dBTable1.setDatabaseDriver("org.h2.Driver");
/*
* set the jdbc url,"quicktabledemo" is the data source we have
* created for the database
*/
dBTable1.setJdbcUrl("jdbc:h2:mem:test;INIT=create table employee as select * from CSVREAD('test.csv');");
// set the select statement which should be used by the table
dBTable1.setSelectSql("select * from employee");
// to create the navigation bars for the table
dBTable1.createControlPanel();
// connect to database & create a connection
dBTable1.connectDatabase();
// fetch the data from database to fill the table
dBTable1.refresh();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// create a new table frame
QuickTableFrame myframe = new QuickTableFrame();
}
}
Resources and dependencies
test.csv
empid,emp_name,emp_dept,emp_salary
1,Azalia,ornare,114918
2,Jade,tristique,152878
3,Willa,In scelerisque scelerisque,166733
...
H2
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.187</version>
</dependency>
References
QuickTable basic tutorial
QuickTable official tutorials
Download latest jar
h2 database

Related

How to refresh JTable after inserting data to database?

I'm populating JTable from access database. when running the code for the first time, table loads perfectly. Then adding new records to database from JDialog. What I was trying to do is to call loadData() method when JDialog is closed, but table is not updating.
This is my loadData() method:
private void loadData() {
System.out.println("sssss");
final String [] columnNames={"Seq", "First Name", "Last Name","Num1","Num2","Num3"};
connectDb();
data = new Object[rows][columns];
int row = 0;
try {
while(rs.next()){
for(int col = 0 ; col<columns; col++ ){
if(col==0)
data[row][col]=rs.getString("contact_seq");
if(col==1)
data[row][col]=rs.getString("contact_fname");
if(col==2)
data[row][col]=rs.getString("contact_lname");
if(col==3)
data[row][col]=rs.getString("contact_num1");
if(col==4)
data[row][col]=rs.getString("contact_num2");
if(col==5)
data[row][col]=rs.getString("contact_num3");
}
row++;
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
model = new DefaultTableModel(data, columnNames){
/**
*
*/
private static final long serialVersionUID = 1L;
public boolean isCellEditable(int row, int column)
{
return false;
}
};
table = new JTable(model);
}`
this how I call loadData method when closing the JDialog.
JMenuItem mntmNew = new JMenuItem("New Contact");
mntmNew.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addData gui = new addData(viewData.this,rs);
gui.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
gui.setVisible(true);
gui.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e){
loadData();
}
});
}
});
mnFile.add(mntmNew);
My database is updated when adding the records but Jtable is not refreshed.
Here:
private void loadData() {
...
table = new JTable(model); // don't re-create the table here
}
Don't re-create the table but update its model instead, either by setting a new table model or by clearing and re-filling the current one:
private void loadData() {
...
table.setModel(model);
// Of course, table should have been initialized
// and placed first, and only once!
}
See examples here (includes SwingWorker to make database calls in a background thread), here and here. Please have a look to those answers, there are explanations to make the point clear.
This worked for me:
if (model.getRowCount() > 0) {
for (int i = model.getRowCount() - 1; i > -1; i--) {
model.removeRow(i);
}
}
setTablevalue();
I removed all the rows from the JTable and again called the setTableValue method to re-populate the table.
This is a shot in the dark, but maybe this will work?:
public void windowClosed(WindowEvent e) {
loadData();
// Add this line:
table.repaint();
}
If I understand what is going on, the underlying database is getting updated but the JTable component is not showing the updates. My guess is that you just have to call the repaint() method so that the JTable gets updated as well.

Making items in jcombobox visible

I am trying to make a combo box which populates the item inside according to my database. at the moment the database contains 5 records which are test1,test2,test3,test4,test5. I've made a loop which is meant to populate it with each record.
private void selectschoolActionPerformed(java.awt.event.ActionEvent evt) {
ResultSet rs = null;
selectschool.setVisible(true);
try{
rs = DAO.getAllSchool();
ArrayList allSchoolName = new ArrayList();
int count = 0;
while(rs.next()){
allSchoolName.add(rs.getString("SchoolName"));
count++;
}
Object ob[] = new Object[count];
for (int i = 0; i < count; i++){
ob[i] = allSchoolName.get(i);
selectschool.addItem(ob[i]);
}
}catch (Exception e){
System.out.println("Exception: " + e);
}
CreateTimeTable ctt = new CreateTimeTable();
ctt.setVisible(true);
String sch = selectschool.getSelectedItem().toString();
ctt.jTextArea1.setText(sch);
I have tested in the for loop and it loops the right amount of times to fill the array will all the records no more no less. but in the GUI when I drop down the combo box, it is empty. But when I click and hold the mouse down, drag it below to the empty field below, it will select test1 and only test 1. How could I make the rest of the values show up? I've tested it and the ob[] holds the right records which need to be entered into the combo box. I think it might be the selectschool.addItem() which isn't working right.
Much appreciated, Thanks.
(DAO.getAllSchool(); just retrieves the database records from sql)
You need to create the JComboBox and add an ActionListener to it. Not create the ActionPerformed and inside that set the JComboBox.
Try it like this:
// fill with values from ResultSet rs (however you obtain it)
ArrayList<String> schools = new ArrayList<String>();
while(rs.next())
schools.add(rs.getString("SchoolName"));
// create the JComboBox
final JComboBox<String> selectSchool = new JComboBox<String>(schools.toArray(new String[]{}));
// add an actionlistener
selectSchool.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// do something with e.g. printing the selection
String school = (String) selectSchool.getSelectedItem();
System.out.println("You selected " + school);
}
});
edit: Or in a complete, very simple but working example:
package sto;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JComboBox;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
// create school list (in your example here would be the ResultSet)
ArrayList<String> schools = new ArrayList<String>();
schools.add("School 1");
schools.add("School 2");
schools.add("School 3");
schools.add("School 4");
// create the JComboBox
final JComboBox<String> selectSchool = new JComboBox<String>(schools.toArray(new String[]{}));
// add an actionlistener
selectSchool.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// do something with e.g. printing the selection
String school = (String) selectSchool.getSelectedItem();
System.out.println("You selected " + school);
}
});
// create a JFrame and add the JComboBox
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(selectSchool);
frame.pack();
frame.setVisible(true);
}
}

JTable (BeanTableModel) not updating/refreshing - Java Swing

I've 2 panels. One where the user inserts the data and the other with a JTable where the results will be shown.
My problem is when the user press's the ok (in my case apply) JButton the data is computed but is not shown on the JTable, nothing changes in the JTable.
For my JTable I'm using the Bean Table Model from tips4Java (http://tips4java.wordpress.com/2008/11/27/bean-table-model/).
One weird thing is if I send data (lets call this data 'A') to the table when the program starts it is shown on the table and if later on I try to update the table, the table does not update. But when I don't send data to the table at start up but try to update/send the table with data 'A' it does not update.
So my question is, why is not the JTable showing whatever data I send to?
Here's my code:
JButton listenere that starts the processing and sends the data to the table:
crashForm.setFormListener(new FormListener() {
#Override
public void formEvent(OptionsFormEvent oe) {
String readTable = oe.getReadFromTable();
int access = oe.getAccess();
int transition = oe.getTransition();
boolean smooth = oe.isTrainCrash();
ArrayList<String> allTrains = new ArrayList<>();
List crashedTrainList = new ArrayList<>();
try {
allTrains = controller.getUniqueTrains(controller.connectServer(), readTable, "trainid");
} catch (Exception ex) {
JOptionPane.showMessageDialog(null, "An error occured while getting trains data\n"
+ "Error description: " + ex.getMessage(), "Error",
JOptionPane.ERROR_MESSAGE);
}
try {
for (int i = 0; i < allTrains.size(); i++)
{
ArrayList<Train> trainDataList = new ArrayList<>();
ArrayList<Train> crashedProccessedData = new ArrayList<>();
String query = "the sql query...";
trainDataList = controller.getTrainData(controller.connectServer(), readTable, query);
crashedProccessedData = controller.detectCrash(access, transition, trainDataList);
crashedTrainList.addAll(crashedProccessedData);
}
} catch (Exception ex) {
JOptionPane.showMessageDialog(null, "An error occured while detecting a crash.\n"
+ "Error description: " + ex.getMessage(), "Error",
JOptionPane.ERROR_MESSAGE);
}
System.out.println("Total crashes detected:" + crashedTrainList.size());
tablePanel.createTable(Train.class, crashedTrainList, true, false, tableLabels);
tablePanel.fireTableDataChanged();
}
});
}
And here's my tablePanel class:
public TablePanel() {
}
public void createTable(Class c, List data, boolean toolBarUp,
boolean toolBarBottom, ArrayList<String> labelsCheckBox) {
beanTableModel = new BeanTableModel(c, data);
columnModel = new XTableColumnModel();
table = new JTable(beanTableModel);
table.setColumnModel(columnModel);
table.createDefaultColumnsFromModel();
if(toolBarUp == true)
{
final JToolBar toolBarTop = new JToolBar();
// Create the Show ALL
JButton reset = new JButton("Reset");
reset.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for(Component c : toolBarTop.getComponents()){
if(c instanceof JCheckBox){
JCheckBox checkBox = (JCheckBox) c;
checkBox.setSelected(false);
columnModel.setAllColumnsVisible();
}
}
int numberOfColumn = columnModel.getColumnCount();
for(int aux = 0; aux < numberOfColumn; aux++)
{
int num = columnModel.getColumnCount();
TableColumn column = columnModel.getColumnByModelIndex(aux);
columnModel.setColumnVisible(column, true);
}
}
});
toolBarTop.add(reset);
// Create a JCheckBox for each column
for(int i = 0; i < labelsCheckBox.size(); i++)
{
final int index = i;
toolBarTop.add(new JCheckBox(new AbstractAction(labelsCheckBox.get(i)) {
#Override
public void actionPerformed(ActionEvent e) {
TableColumn column = columnModel.getColumnByModelIndex(index);
boolean visible = columnModel.isColumnVisible(column);
columnModel.setColumnVisible(column, !visible);
}
}));
}
setLayout(new BorderLayout());
add(toolBarTop, BorderLayout.NORTH);
add(new JScrollPane(table), BorderLayout.CENTER);
// add(toolBarDown, BorderLayout.SOUTH);
}
final JToolBar toolBarDown = new JToolBar();
toolBarDown.add(new JButton(new AbstractAction("Save Table") {
#Override
public void actionPerformed(ActionEvent e) {
throw new UnsupportedOperationException("Not supported yet.");
}
}));
}
public void createTable(Class c, Class cAncestor) {
beanTableModel = new BeanTableModel(c, cAncestor);
table = new JTable(beanTableModel);
setLayout(new BorderLayout());
add(new JScrollPane(table), BorderLayout.CENTER);
}
public void createTable(Class c, Class cAncestor, List data) {
beanTableModel = new BeanTableModel(c, cAncestor, data);
table = new JTable(beanTableModel);
setLayout(new BorderLayout());
add(new JScrollPane(table), BorderLayout.CENTER);
beanTableModel.fireTableDataChanged();
}
public void createTable(Class c) {
beanTableModel = new BeanTableModel(c);
table = new JTable(beanTableModel);
setLayout(new BorderLayout());
add(new JScrollPane(table), BorderLayout.CENTER);
}
public void createTable(Class c, List data)
{
beanTableModel = new BeanTableModel(c, data);
table = new JTable(beanTableModel);
setLayout(new BorderLayout());
add(new JScrollPane(table), BorderLayout.CENTER);
}
//to refresh the table everytime there is an update
public void fireTableDataChanged()
{
beanTableModel.fireTableDataChanged();
}
So again, my question is: Isn't my JTable not updated with the new results every time I send new data to it?
You should never invoke fireTableDataChanged manually. Only the TableModel is responsible for invoking this event.
From your code it looks like you are creating a new TableModel, JTable and JScrollPane every time you make a change. Any time you add a component to a visible GUI the basic code should be:
panel.add(....);
panel.revalidate();
panel.repaint();
By default all components have a size of zero so since you don't invoke revalidate() then never get a proper size and there is nothing to paint. Also, it is never a good idea to simply keep adding components to the CENTER of your panel since the old component are still part of the container.
However, there is a better solution. There is no need to keep creating new components. All you need to do is create an new TableModel and then use:
table.setModel( newlyCreatedModel );
and the model will be added to the table and the table will repaint itself automatically.

Remove JTable row that read file records

I am New in java, I have a JTable that can read records from a txt file and show they perfectly.
I want to add a new book to my JFrame that when user select a row on table and clicked the "delete" button, that row should delete and that deleted row records must delete from txt file,too.
my code is this, but it has errors and not seen JTable! :
public class CopyOfAllUserTable extends AbstractTableModel {
Vector data;
Vector column;
public static void main(String[] args){
new CopyOfAllUserTable();
}
public CopyOfAllUserTable() {
String line;
data = new Vector();
column = new Vector();
try {
FileInputStream fis = new FileInputStream("D:\\AllUserRecords.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(fis));
StringTokenizer st1 = new StringTokenizer(br.readLine(), " ");
while (st1.hasMoreTokens())
column.addElement(st1.nextToken());
while ((line = br.readLine()) != null) {
StringTokenizer st2 = new StringTokenizer(line, " ");
while (st2.hasMoreTokens())
data.addElement(st2.nextToken());
}
br.close();
} catch (Exception e) {
e.printStackTrace();
}
final JFrame frame1=new JFrame();
JTable table=new JTable(data,column);
JButton button1=new JButton("Delete");
button1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DefaultTableModel model=new DefaultTableModel(data, column);
JTable table=new JTable(model);
}
});
JPanel panel=new JPanel();
panel.add(table);
panel.add(button1);
frame1.add(panel);
frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame1.setBounds(200, 80, 600, 500);
frame1.setVisible(true);
frame1.setEnabled(true);
}
public int getRowCount() {
return data.size() / getColumnCount();
}
public int getColumnCount() {
return column.size();
}
public Object getValueAt(int rowIndex, int columnIndex) {
return (String) data.elementAt((rowIndex * getColumnCount())
+ columnIndex);
}
}
My problem is in delete row, and read records from file to jtable are perfectly successful.
Firstly you're not adding your JTable to the content of the frame.
For containers like: frame.getContentPane() and JPanel you should add the child components by using their #add(...) method.
For example:
final JPanel panel=new JPanel(new BorderLayout());
button1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
DefaultTableModel model=new DefaultTableModel(data, column);
JTable table=new JTable(model);
panel.add(new JScrollPane(table));
panel.revalidate();
}
});
Note that JPanel default layout is FlowLayout. Second thing - if you want to have headers and scrolling in your JTable you need to wrap it with JScrollPane.
Next - you should revalidate the panel after adding/removing/etc.
The second issue is removing rows from JTable. I usually write a method to handle it:
protected void removeRows(final int[] rows) {
int modelRows[] = new int[rows.length];
for(int i = 0; i < rows.length; ++i) {
modelRows[i] = table.convertRowIndexToModel(rows[i]);
}
Arrays.sort(modelRows);
for(int i = modelRows.length - 1; i >= 0; --i) {
int row = modelRows[i];
model.removeRow(row);
}
model.fireTableDataChanged();
}
The convertRowIndexToModel method converts index returned by JTable#getSelectedRows() or JTable#getSelectedRow() (which are the visible indices) to the model indices. If you set RowSorter for your JTable or you leave it to standard implementation:
table.setAutoCreateRowSorter(true);
You are adding table directly to the panel with out using the JScrollPane. Your table header will not be visible if you do like this,
So instead of this,
JPanel panel=new JPanel();
panel.add(table);
Do this,
JPanel panel=new JPanel();
panel.add(new JScrollPane(table));
Why to use JScrollPane? Read this.
When user selects a row and clicks on delete, then get the selected row and remove it from the table list. As you are using AbstractTableModel then you have to write your custom removeRow(..) method to perform this.
Example:
private boolean removeSelectedRow(int row) {
// Remove the row from the list that the table is using.
dataList.remove(row);
// You need to call fireXXX method to refresh the table model.
fireTableDataChanged();
return true;
// If fail return false;
}
If delete is the action then first get the selected row and then call removeSelectedRow(int) like the following,
private void deleteRow() {
int selectedRow = table.getSelectedRow();
boolean deleteStatus = removeSelectedRow(selectedRow);
// Only if the deletion is success then delete from the file.
if(deleteStatus) {
// Delete it from the file too.
}
}
first you have to make sure that something has been selected: when there is something selected than enable the delete button. please look up the JTable java source code #
http://developer.classpath.org/doc/javax/swing/JTable-source.html
and the following code:
1331: /**
1332: * Receives notification when the row selection changes and fires
1333: * appropriate property change events.
1334: *
1335: * #param event the list selection event
1336: */
1337: public void valueChanged(ListSelectionEvent event)
1338: {
1339: firePropertyChange(AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY,
1340: Boolean.FALSE, Boolean.TRUE);
1341: int r = getSelectedRow();
1342: int c = getSelectedColumn();
1343: if (r != lastSelectedRow || c != lastSelectedColumn)
1344: {
1345: Accessible o = getAccessibleAt(lastSelectedRow,lastSelectedColumn);
1347: Accessible n = getAccessibleAt(r, c);
1348: firePropertyChange(AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY, o, n);
1350: lastSelectedRow = r;
1351: lastSelectedColumn = c;
1352: }
1353: }
You need to register for the last event to be notified when the selected rows have been changed. Add your own listener to enable the deletebutton based on whether or not a row has been selected which is as you can see in the event itself.
Please use to start with the DefaultTableModel because it will work in 90% of the cases.
And any change is applied to the tabledatamodel which will automatically propogate to the JTable View: normally you never change the view because all selection and scroll information is lost which is something you don't want.
When the delete button is fired the approach is straight forward: there is a row selected, otherwise it is impossible to click it: remove that selected row number from the defaultTableModel, and last but not least I would write simply the entire contents of the datamodel model to the designated file because the table's model hold the actual rows that are indeed displayed in the View.
So please think in terms of models models and models: Views are instantiated only once, packed scrolled etc and than you leave them as is. Models are normally also never changed: you change the contents of the models by adding and or deleting rows. One other tip: use always renderers: those that don't don't, in my humble opinion, don't understand how to work with JTables.
And yes you can leave out the first part to listen for selection changes: sure and pop up a warning to indicate the problem. And in a later stage add the functionality that listens for selection changes to enable and or disable the JButton delete row.
Hope this helps.

How to properly fetch a huge ResultSet using JDBC

I'm facing problems when fetching and processing a huge ResultSet from a database using JDBC (a few million rows), in this case MySQL's Connector/J. One of those problems is that even though I'm using a SwingWorker and taking measures not to perform any long processing on the Event Dispatch thread, the UI still freezes occasionally. This only happens with huge queries; the approach I'm using works for small ones.
Is there something that can be done to remedy this? Am I improperly handling such large ResultSet?
Note: I'm using the Employees sample database, more specifically the Salaries table.
Sample code
public class TestFrame extends javax.swing.JFrame {
static final String URL = "jdbc:mysql://localhost:3306/employees";
static final String USERNAME = "root";
static final String PASSWORD = "";
private Connection conn;
public TestFrame() {
initComponents();
initConnection();
}
public void initConnection() {
try {
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
System.out.println("Connected.");
} catch (SQLException e) {
e.printStackTrace();
}
}
#SuppressWarnings("unchecked")
private void initComponents() {
java.awt.GridBagConstraints gridBagConstraints;
jScrollPane1 = new javax.swing.JScrollPane();
uiTable = new javax.swing.JTable();
btnRun = new javax.swing.JButton();
txtQuery = new javax.swing.JTextField();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
getContentPane().setLayout(new java.awt.GridBagLayout());
jScrollPane1.setViewportView(uiTable);
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
gridBagConstraints.ipadx = 643;
gridBagConstraints.ipady = 374;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.weightx = 1.0;
gridBagConstraints.weighty = 1.0;
gridBagConstraints.insets = new java.awt.Insets(6, 10, 0, 10);
getContentPane().add(jScrollPane1, gridBagConstraints);
btnRun.setText("Run");
btnRun.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
btnRunActionPerformed(evt);
}
});
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 2;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.insets = new java.awt.Insets(6, 625, 11, 10);
getContentPane().add(btnRun, gridBagConstraints);
txtQuery.setText("SELECT * FROM Salaries");
gridBagConstraints = new java.awt.GridBagConstraints();
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.ipadx = 660;
gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
gridBagConstraints.insets = new java.awt.Insets(11, 10, 0, 10);
getContentPane().add(txtQuery, gridBagConstraints);
pack();
}
private void btnRunActionPerformed(java.awt.event.ActionEvent evt) {
String query = txtQuery.getText();
SwingWorker<DefaultTableModel, Object> worker = new SwingWorker<DefaultTableModel, Object>() {
#Override
protected DefaultTableModel doInBackground() {
DefaultTableModel tableModel = null;
try (Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery(query);
ResultSetMetaData rsmd = rs.getMetaData();
String[] columnNames = new String[rsmd.getColumnCount()];
for (int i = 1; i <= columnNames.length; i++) {
columnNames[i - 1] = rsmd.getColumnName(i);
}
tableModel = new DefaultTableModel(columnNames, 0);
while (rs.next()) {
Vector row = new Vector();
for (int i = 1; i <= columnNames.length; i++) {
row.add(rs.getString(i));
}
tableModel.addRow(row);
}
} catch (Exception e) {
e.printStackTrace();
}
return tableModel;
}
#Override
protected void done() {
try {
DefaultTableModel tableModel;
if ((tableModel = get()) != null) {
uiTable.setModel(tableModel);
}
} catch (InterruptedException | ExecutionException ex) {
}
}
};
worker.execute();
}
public static void main(String args[]) {
try {
javax.swing.UIManager.setLookAndFeel("javax.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | javax.swing.UnsupportedLookAndFeelException ex) {
}
java.awt.EventQueue.invokeLater(() -> {
new TestFrame().setVisible(true);
});
}
private javax.swing.JButton btnRun;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JTextField txtQuery;
private javax.swing.JTable uiTable;
}
It's also worth mentioning that the UI freezes even before I start to populate the table model.
The problem is that you shouldn't read the million records. Doesn't matter how you read the result set, if you read one million things it takes time... And if you put all the records in memory it takes a bunch of memory too.
The usual way to do that is reading some records, let's say ¿200?, presenting the 200 records and adding buttons to go forward and read another 200, and backward, to read the previous 200 records, so the user can navigate through the data without moving one million records between the server and yout client.
Just need to add some conditions in your query WHERE clausule and keep record of the last read record, so when you make a query you start the cursor from the last record:
select * from ***
where ****
AND ID > 'myStoredLastReadID'
Or something like that. Then, change the while ( rs.next()) for a
int ix = 0;
while ( rs.next() && (ix++) < 200 ) {
And add the buttons to your GUI. You will need something more, for example to go backward, but I think you can catch the idea.
If you really need to show one million records to your user... well, it will be slow whatever you do because you are going to move one million things from the server to your client, store one million things in memory, calculate which part of that million things the GUI must paint in the screen...
Use an AbstractTableModel on your own page caching, and just keep reading some pages when not in the cache. Keep the cache limited.
With COUNT(*). and LIMIT you can fetch the pages you are displaying, a kind of caching. This becomes even a bit harder with a faster paging technique, using index fields: say the ID. Then on a sorted table one could page by storing page border IDs.
You probably need to keep the connection open, reopen it on failure. Make a page cache.
Also create a Vector with an initial capacity of the correct size.
new Vector(columnNames.length);
This is an (quick and very dirty) example of how to call something slow from a button without freezing the main Swing thread.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class TestUI extends JFrame implements ActionListener {
JButton but;
public TestUI() {
super( "Test...");
but = new JButton( "Test");
but.setActionCommand( "BUTTON");
but.addActionListener( this);
getContentPane().add( but, BorderLayout.CENTER);
pack();
setVisible( true);
}
public void actionPerformed( ActionEvent ev) {
if ( ev.getActionCommand().equals( "BUTTON") ) {
SlowClass sc = new SlowClass( TestUI.this);
sc.start();
/** THIS FREEEZES
try {
sc.join();
}
catch ( InterruptedException e) {
}
*/
}
else if ( ev.getActionCommand().equals( "QUERY_END") ) {
System.out.println( "END");
but.setText( "Query over");
}
}
public class SlowClass extends Thread {
ActionListener listener;
SlowClass( ActionListener listener) {
this.listener = listener;
}
public void run() {
try {
Thread.sleep( 10000);
listener.actionPerformed( new ActionEvent( this, 0, "QUERY_END"));
}
catch ( Exception ex) {
ex.printStackTrace();
}
}
}
public static void main( String args[]) throws Exception {
TestUI tui = new TestUI();
}
}
Create a new thread, start the thread from the event hadler and, using a listener, wait to be notified when the work is done. If you wait in the same invocation, in the commented lines, the main Swing thread freezes waiting for the end, so the listener is important.
This example starts a thread that doesn't consume CPU, so it works because nobady is fighting for the machine resources, you can resize the freame while the thread is working because Swing is free to work. But this is not your case, you are doing a lot of things, so we don't know if Swing hangs because there's something wrong in the code or because there isn't enough CPU and memory for Swing and the query.
Try something like this (cleaner, please, handling the listener correctly, not adding it in the constructor...) to see what happens. Or change the superquery for a sleep(20000) in your code to see if it hangs to get a hint of what happens.

Categories