package view;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.nio.file.*;
import java.sql.*;
import java.util.*;
import javax.sql.*;
import javax.sql.rowset.*;
import javax.swing.*;
/**
* This program uses metadata to display arbitrary tables in a database.
* @version 1.33 2016-04-27
* @author Cay Horstmann
*/
public class ViewDB
{
public static void main(String[] args)
{
EventQueue.invokeLater(() ->
{
JFrame frame = new ViewDBFrame();
frame.setTitle("ViewDB");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
});
}
}
/**
* The frame that holds the data panel and the navigation buttons.
*/
class ViewDBFrame extends JFrame
{
private JButton previousButton;
private JButton nextButton;
private JButton deleteButton;
private JButton saveButton;
private DataPanel dataPanel;
private Component scrollPane;
private JComboBox<String> tableNames;
private Properties props;
private CachedRowSet crs;
private Connection conn;
public ViewDBFrame()
{
tableNames = new JComboBox<String>();
try
{
readDatabaseProperties();
conn = getConnection();
DatabaseMetaData meta = conn.getMetaData();
try (ResultSet mrs = meta.getTables(null, null, null, new String[] { "TABLE" }))
{
while (mrs.next())
tableNames.addItem(mrs.getString(3));
}
}
catch (SQLException ex)
{
for (Throwable t : ex)
t.printStackTrace();
}
catch (IOException ex)
{
ex.printStackTrace();
}
tableNames.addActionListener(
event -> showTable((String) tableNames.getSelectedItem(), conn));
add(tableNames, BorderLayout.NORTH);
addWindowListener(new WindowAdapter()
{
public void windowClosing(WindowEvent event)
{
try
{
if (conn != null) conn.close();
}
catch (SQLException ex)
{
for (Throwable t : ex)
t.printStackTrace();
}
}
});
JPanel buttonPanel = new JPanel();
add(buttonPanel, BorderLayout.SOUTH);
previousButton = new JButton("Previous");
previousButton.addActionListener(event -> showPreviousRow());
buttonPanel.add(previousButton);
nextButton = new JButton("Next");
nextButton.addActionListener(event -> showNextRow());
buttonPanel.add(nextButton);
deleteButton = new JButton("Delete");
deleteButton.addActionListener(event -> deleteRow());
buttonPanel.add(deleteButton);
saveButton = new JButton("Save");
saveButton.addActionListener(event -> saveChanges());
buttonPanel.add(saveButton);
if (tableNames.getItemCount() > 0)
showTable(tableNames.getItemAt(0), conn);
}
/**
* Prepares the text fields for showing a new table, and shows the first row.
* @param tableName the name of the table to display
* @param conn the database connection
*/
public void showTable(String tableName, Connection conn)
{
try (Statement stat = conn.createStatement();
ResultSet result = stat.executeQuery("SELECT * FROM " + tableName))
{
// get result set
// copy into cached row set
RowSetFactory factory = RowSetProvider.newFactory();
crs = factory.createCachedRowSet();
crs.setTableName(tableName);
crs.populate(result);
if (scrollPane != null) remove(scrollPane);
dataPanel = new DataPanel(crs);
scrollPane = new JScrollPane(dataPanel);
add(scrollPane, BorderLayout.CENTER);
pack();
showNextRow();
}
catch (SQLException ex)
{
for (Throwable t : ex)
t.printStackTrace();
}
}
/**
* Moves to the previous table row.
*/
public void showPreviousRow()
{
try
{
if (crs == null || crs.isFirst()) return;
crs.previous();
dataPanel.showRow(crs);
}
catch (SQLException ex)
{
for (Throwable t : ex)
t.printStackTrace();
}
}
/**
* Moves to the next table row.
*/
public void showNextRow()
{
try
{
if (crs == null || crs.isLast()) return;
crs.next();
dataPanel.showRow(crs);
}
catch (SQLException ex)
{
for (Throwable t : ex)
t.printStackTrace();
}
}
/**
* Deletes current table row.
*/
public void deleteRow()
{
if (crs == null) return;
new SwingWorker<Void, Void>()
{
public Void doInBackground() throws SQLException
{
crs.deleteRow();
crs.acceptChanges(conn);
if (crs.isAfterLast())
if (!crs.last()) crs = null;
return null;
}
public void done()
{
dataPanel.showRow(crs);
}
}.execute();
}
/**
* Saves all changes.
*/
public void saveChanges()
{
if (crs == null) return;
new SwingWorker<Void, Void>()
{
public Void doInBackground() throws SQLException
{
dataPanel.setRow(crs);
crs.acceptChanges(conn);
return null;
}
}.execute();
}
private void readDatabaseProperties() throws IOException
{
props = new Properties();
try (InputStream in = Files.newInputStream(Paths.get("database.properties")))
{
props.load(in);
}
String drivers = props.getProperty("jdbc.drivers");
if (drivers != null) System.setProperty("jdbc.drivers", drivers);
}
/**
* Gets a connection from the properties specified in the file database.properties.
* @return the database connection
*/
private Connection getConnection() throws SQLException
{
String url = props.getProperty("jdbc.url");
String username = props.getProperty("jdbc.username");
String password = props.getProperty("jdbc.password");
return DriverManager.getConnection(url, username, password);
}
}
/**
* This panel displays the contents of a result set.
*/
class DataPanel extends JPanel
{
private java.util.List<JTextField> fields;
/**
* Constructs the data panel.
* @param rs the result set whose contents this panel displays
*/
public DataPanel(RowSet rs) throws SQLException
{
fields = new ArrayList<>();
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = 1;
gbc.gridheight = 1;
ResultSetMetaData rsmd = rs.getMetaData();
for (int i = 1; i <= rsmd.getColumnCount(); i++)
{
gbc.gridy = i - 1;
String columnName = rsmd.getColumnLabel(i);
gbc.gridx = 0;
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel(columnName), gbc);
int columnWidth = rsmd.getColumnDisplaySize(i);
JTextField tb = new JTextField(columnWidth);
if (!rsmd.getColumnClassName(i).equals("java.lang.String"))
tb.setEditable(false);
fields.add(tb);
gbc.gridx = 1;
gbc.anchor = GridBagConstraints.WEST;
add(tb, gbc);
}
}
/**
* Shows a database row by populating all text fields with the column values.
*/
public void showRow(ResultSet rs)
{
try
{
if (rs == null) return;
for (int i = 1; i <= fields.size(); i++)
{
String field = rs == null ? "" : rs.getString(i);
JTextField tb = fields.get(i - 1);
tb.setText(field);
}
}
catch (SQLException ex)
{
for (Throwable t : ex)
t.printStackTrace();
}
}
/**
* Updates changed data into the current row of the row set.
*/
public void setRow(RowSet rs) throws SQLException
{
for (int i = 1; i <= fields.size(); i++)
{
String field = rs.getString(i);
JTextField tb = fields.get(i - 1);
if (!field.equals(tb.getText()))
rs.updateString(i, tb.getText());
}
rs.updateRow();
}
}
构造函数的解释
readDatabaseProperties()函数的主要作用是给props对象赋初值,然后将jdbc.drivers设置为相应的值(即加载jdbc驱动),getConnection函数建立一个到数据库的连接。
首先,是对主程序的解释,用一个Lambda表达式来作为EventQueue.invokeLater(Runnable runnable)的参数,即重载了该接口的run()函数,该方法的主要目的是创建一个Frame。该Frame的名称为ViewDB,该Frame的默认关闭操作为:JFame.EXIT_ON_CLOSE。然后设置该Frame可见。
接着是对ViewDBFrame的介绍,有4个JButton,为previousButton、nextButton、deleteButton、saveButton。一个私有变量DataPanel(extends JPanel),一个Component scrollPane,一个JComboBox<String> tableNames, 一个Properties props,一个CachedRowSet crs,一个Connection conn。
该ViewDBFrame的构造函数主要目的就是给各个参数赋值,
从元数据对象meta中返回 类型为“Table”的表的相关信息(返回结果的类型为ResultSet),将得到的相关表的信息加载到JcomboBox<E>中,作为这个下拉列表的一个Item。
给下拉列表tableNames加一个事件侦听器,这个事件侦听器的目的是为了展示选中的表,然后将这个下拉列表放在Frame的NORTH位置。
添加一个WindowListener对象,用于定义关闭时的操作。
添加一个JPanel,并将它放在Frame的SOUTH位置。
创建一个标签为“Previous”的按钮,加的侦听器的作用是展示前一行,同理,还有相应按钮的作用是展示下一行,删除行,保存更改。
如果下拉列表中包含的项数不止一项,那么就展示第一行的存储的表的相关信息。
非构造函数函数
public void showTable(String tableName, Connection conn)
准备文本字段来展示一个新的表,或者显示第一行。
参数——要显示的表的名称,以及已经与数据库建立连接的对象
首先是根据表创建一个Statement,然后执行 “SELECT * FROM” + tableName, 从数据库中选出这个表的信息,
将结果复制进一个CachedRowSet,(CachedRowSet 创建的步骤,首先建立一个RowSetFactory,根据RowSetFactory的createCachedRowSet方法来创建)。
根据CachedRowSet创建一个DataPanel
用DataPanel创建一个JScrollPane,将ScrollPane放到Frame的中间
使用函数pack()使此窗口的大小适合其子组件的首选大小和布局。
然后调用showNextRow();
然后是函数showPreviousRow:
public void showPreviousRow()
首先,检测crs是否为null或者游标是否只在第一行,如果是,就返回,
不是,则调用previous()函数,将游标移动到前一行,再调用dataPanel的showRow()方法。
showNextRow()函数的实质与上面的函数相同,只不过是显示下一行
deleteRow()函数:
public void deleteRow()
首先检查crs是否为null,是的或,就直接返回,
否则,就调用SwingWorker<Void, Void>(首先创建一个SwingWorker的具体实现,即重写doInBackground()函数,),然后调用excute()函数开始执行。
doInBackground()函数中,调用CachedRowSet类的deleteRow()方法,
然后使用CachedRowSet的acceptChanges(Connection)方法来将更改反映到底层数据库。
接着检测删除行后游标是否位于最后一行之后,
是的话再检测能否将游标移到最后一行,不能的话就将crs设置为null。
此函数最终返回null
done函数中:
public void done()
调用DataPanel的showRow函数。
public void saveChanges()
该函数的基本结构与上面的函数相同。
doInBackground()函数调用了DataOanel的setRow()函数,然后调用acceptChanges()函数。
readDatabaseProperties()函数根据一个.properties文件设置一个Properties对象,然后注册数据库的驱动器。
(感觉这里不是很合理,将注册驱动器的相关代码放到getConnection函数中会更好)
getConnection()函数将得到的url、username、password用于DriverManager的getConnection连接。
接下来是建立的一个DataPanel类(extends JPanel)
这个面板呈现了结果集的内容,
该类的字段为:
List<JTextField> fields
首先是DataPanel的构造函数,
该构造函数的参数为一个RowSet,
将fields设置为一个ArrayList<>,
设置宽度为1,高度为1。
从参数ResultSet得到一个ResultSetMetaData,
接着是一个for循环{
该循环首先设置gridy的值,
使用rs来存储该列的列名。将gridx设置为0;
将anchor设置为GridBagConstraints.EAST,
将一个带有列名的JLacel放在gbc中
创建一个与列最大宽度同宽的JTextField,
如果某一列的类的类型为“java.lang.String”,就将这个JTextField设置为不可编辑。
将这个tb放进fields中,
将gridx设置为1,将anchor设置为WEST,
将tb放进gbc
}
接下来是一个函数showRow
通过用列值填充所有文本字段来显示数据库行
该函数获取rs在第i列的值,采用i-1列tb因为ArrayList的列是从0开始的,而列的值是从1开始的。
最后是一个函数setRow
将更改后的数据更新为行集的当前行
首先检测从行集中得到的值与JTextField是否相等,不相等,则更新为JTextField中的值。
最后调用的是RowSet的updateRowset方法,使用此ResultSet对象的当前行的新内容更新底层数据库
对所有行做检验,但是只会更新当前行
最后来做一个总结,主函数创建一个ViewDBFrame,frame的标题为ViewDB,设置默认的关闭操作是调用System.exit()方法,设置该Frame为visible。
这个Frame上面的功能有查看前一行、查看下一行、删除、保存。