I created a java swing application, which takes data from a database, and displays it in a JTable. I'm currently just trying to view all the data in my database. But when I click the button to send the query to the database and view the obtained data on the jtable, the table does not update. This is my code:
TableData.java
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
public class TableData extends JPanel {
private JTable table;
private DefaultTableModel model;
private final Object Column[] = {"id", "titolo", "autore", "prezzo"};
TableData(){
model = new DefaultTableModel();
table = new JTable(model);
model.setColumnIdentifiers(Column);
setLayout(new BorderLayout());
add(new JScrollPane(table), BorderLayout.CENTER);
}
public void updateTable(Object row[]){
model.addRow(row);
}
}
ConnectionDB.java
import java.sql.Statement;
import java.sql.DriverManager;
import java.sql.ResultSet;
public class ConnectionDB {
private String url = "jdbc:mysql://localhost:4444/database_name";
private String user = "root";
private String password = "password";
TableData tableData = new TableData();
//id titolo autore prezzo
public void upload_data(String titolo, String autore, int prezzo){
try{
Connection con = DriverManager.getConnection(url, user, password);
Statement statement = con.createStatement();
String query = "insert into libri(titolo, autore, prezzo)" +
"values ('"+titolo+"','"+autore+"','"+prezzo+"')";
statement.executeUpdate(query);
System.out.println("success");
}
catch(Exception e){
e.printStackTrace();
}
}
public void get_data(){
Object row[] = new Object[4];
try{
Connection conn = DriverManager.getConnection(url, user, password);
Statement statement = conn.createStatement();
ResultSet resultSet = statement.executeQuery("select * from libri");
while(resultSet.next()){
for(int i = 1; i<=4; i++){
row[i-1] = resultSet.getString(i);
}
tableData.updateTable(row);
}
}
catch(Exception e){
e.printStackTrace();
}
}
}
PanelForm.java
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.jar.JarEntry;
public class PanelForm extends JPanel {
....somebutton.....
ConnectionDB conn = new ConnectionDB();
....some board....
buttonVisualizza.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
conn.get_data();
}
});
}
}
This is my HomePage.java
import javax.swing.text.AbstractDocument;
import java.awt.*;
public class HomePage extends JFrame {
private PanelForm panelForm;
private TableData tableData;
HomePage(){
super("Home");
setSize(800,500);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocation(200,200);
setLayout(new BorderLayout());
panelForm = new PanelForm();
tableData = new TableData();
add(panelForm, BorderLayout.LINE_START);
add(tableData, BorderLayout.CENTER);
setVisible(true);
}
}
I have already tried to reload the model, and this command: model.fireTableDataChanged();
So immediately, you're creating multiple instances of TableData
....
public class ConnectionDB {
//...
TableData tableData = new TableData();
public class HomePage extends JFrame {
private PanelForm panelForm;
private TableData tableData;
HomePage(){
//..
tableData = new TableData();
Neither of these two instances have anything to do with each other, so updating one will not update the other.
This suggests that you need to spend some more time researching concepts like
Let's start with, you're using an Object Oriented language, this means, you should be trying to describe your data more as "objects" rather then a lose connection of elements in an array.
Let's start with a "book" concept...
public interface Book {
public String getTitle();
public String getAuthor();
public int getPrice();
}
I like interface
s they free your code from changes which might occur at an implementation level, should you need to shift your data source from say something like a local database to a web API.
Having said that, we're still going to need a concrete implement...
public class DefaultBook implements Book {
private String title;
private String author;
private int price;
public DefaultBook(String title, String author, int price) {
this.title = title;
this.author = author;
this.price = price;
}
@Override
public String getTitle() {
return title;
}
@Override
public String getAuthor() {
return author;
}
@Override
public int getPrice() {
return price;
}
}
I might be tempted to also inject that the records database key into an implementation, but that's details you can address later.
Next, lets take a look at the ConnectionDB
...
public class ConnectionDB {
//...
public void upload_data(String titolo, String autore, int prezzo){
try{
Connection con = DriverManager.getConnection(url, user, password);
Statement statement = con.createStatement();
String query = "insert into libri(titolo, autore, prezzo)" +
"values ('"+titolo+"','"+autore+"','"+prezzo+"')";
statement.executeUpdate(query);
System.out.println("success");
}
catch(Exception e){
e.printStackTrace();
}
}
This is not only inefficient and slow, it's also dangerous.
Create a new instance of the Connection
each time is slow and you're leaving yourself open to sql injection attacks. You're also not closing any of the resources, which could mean that the connection will keep them open, degrading the performance of your database.
Start by taking a look at The try-with-resources Statement and Using Prepared Statements.
Instead, you should be making use of dependency injection to pass in a reference to the Connection
. This resolves the class of the responsibility of creating the connection, allow you to create what ever connection you need to what ever database you want.
public class ConnectionDB {
private Connection connection;
public ConnectionDB(Connection connction) {
this.connection = connction;
}
protected Connection getConnection() {
return connection;
}
public void uploadData(Book book) throws SQLException {
try(PreparedStatement statement = getConnection().prepareStatement("insert into libri(titolo, autore, prezzo) values (?, ?, ?)")) {
statement.setString(1, book.getTitle());
statement.setString(2, book.getAuthor());
statement.setInt(3, book.getPrice());
statement.executeUpdate();
System.out.println("success");
}
}
public List<Book> getData() throws SQLException {
List<Book> books = new ArrayList<>(32);
try (Statement statement = getConnection().createStatement()) {
ResultSet resultSet = statement.executeQuery("select titolo, autore, prezzo from libri");
while (resultSet.next()) {
String title = resultSet.getString(1);
String author = resultSet.getString(2);
int price = resultSet.getInt(3);
books.add(new DefaultBook(title, author, price));
}
}
return books;
}
}
You will note that getData
returns a List
of Book
s, this is important, as it removes any implementation details that previously existed. The ConnectionDB
doesn't care how you might use the data, only that when requested, it will return a List
of data for you.
Also note, I'm not consuming the errors. Error management (beyond cleaning up the resources) isn't this classes responsibility, the caller will want to know when something goes wrong.
Okay, let's move onto the UI by stating with how you would model the data from ConnectionDB
for the UI.
I would, personally, create a custom TableModel
which could take a List
of Book
s and model it as I needed, for example...
public class BookTableModel extends AbstractTableModel {
private List<Book> books;
private String[] columnNames = new String[] {
"Title", "Author", "Price"
};
public BookTableModel(List<Book> books) {
this.books = books;
}
public List<Book> getBooks() {
return books;
}
@Override
public int getRowCount() {
return getBooks().size();
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public String getColumnName(int column) {
return columnNames[column];
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
Book book = getBooks().get(rowIndex);
switch (columnIndex) {
case 0: return book.getTitle();
case 1: return book.getAuthor();
case 2: return book.getPrice();
}
return null;
}
@Override
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case 0: return String.class;
case 1: return String.class;
case 2: return Integer.class;
}
return Object.class;
}
}
This is important, because I could use a ListModel
instead if I wanted to! Same data, different presentation!
public class BookTable extends JPanel {
private JTable table;
public BookTable(BookTableModel model) {
setLayout(new BorderLayout());
table = new JTable(model);
add(new JScrollPane(table));
}
protected JTable getTable() {
return table;
}
public BookTableModel getModel() {
return (BookTableModel) getTable().getModel();
}
public void setModel(BookTableModel model) {
getTable().setModel(model);
}
}
Now, unless my UI was suitably complex, I might not seperate the table management to a seperate class, instead, I might just do something like...
public class BookPane extends JPanel {
private ConnectionDB connectionDB;
private JTable bookTable;
public BookPane(ConnectionDB connectionDB) {
this.connectionDB = connectionDB;
setLayout(new BorderLayout());
// You could add a empty constructor to the
// BookTableModel to make this a bit easier
bookTable = new JTable(new BookTableModel(new ArrayList<>()));
add(new JScrollPane(bookTable));
JButton loadButton = new JButton("Load");
loadButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
List<Book> data = getConnectionDB().getData();
// You could just replace the `List` managed by
// BookTableModel, but this is just simpler
getBookTable().setModel(new BookTableModel(data));
} catch (SQLException ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(BookPane.this, "Failed to load data", "Error", JOptionPane.ERROR_MESSAGE);
}
}
});
}
public ConnectionDB getConnectionDB() {
return connectionDB;
}
protected JTable getBookTable() {
return bookTable;
}
}
So, how does this all tie together? Well, maybe something like...
Connection connection = ...; // Create an instance of a Connection for your database
ConnectionDB connectionDB = new ConnectionDB(connection);
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new BookPane(connectionDB));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});