主页
软件技术
返回
MySQL管理器

当前IT行业流行的关系型数据库有Oracle、SQL Server、DB2和MySQL等。其中MySQL是一个小型的关系型数据库,开发者是瑞典的MySQL AB公司,于2008年6月1日被Sun公司收购。MySQL拥有体积小、速度快等优点,被广泛的应用在中小型企业的IT系统中,更重要的一点是,它是开源的。由于MySQL应用广泛,因此涌现出许多MySQL的客户端,例如MySQL Front、Navicat与MySQL自带的MySQL Administrator等,这些都是我们平时在开发MySQL数据库应用时十分常用的MySQL图形管理工具。这些优秀的工具为我们提供了十分方便的功能去管理MySQL数据库,例如提供浏览数据的图形界面、操作数据的界面、操作各个数据库元素(表、视图、存储过程等)的界面,这些功能为我们带来了极大的方便,可以在一个完全图形化的界面进行数据库处理。使用这些工具,你可以不懂如何编写SQL语句,只需要通过操作图形界面,就可以达到操作数据库的目的。

在本章中,我们将自己开发一个简单的MySQL管理器。在开发的过程中,让大家了解前面所讲到的那些优秀工具的实现原理。在本章开头已经提到,这些管理工具,提供了各种的图形界面让我们去进行各种的操作,因此,开发一个MySQL管理器,除了需要编写一些操作数据库的SQL以外,还需要注意的是图形界面的处理。这些管理工具,实现的原理并无太大差别,但是哪个工具更能得到多数使用者的青睐,更多的就是取决于这些工具给用户带来的使用体验及方便性。

本章所开发的MySQL管理器是基于MySQL5.0开发的,因此如果要得到最佳的运行效果,请使用MySQL5.0。由于MySQL各个版本间都存在差别,例如笔者在开发这个管理器的时候,就遇到MySQL5.0与MySQL5.1之间的微小差别,这些差别对我们开发所产生的影响,将在下面的章节中详细介绍。

1 MySQL管理器原理

MySQL管理器,主要功能是让用户可以轻松进行各种的MySQL操作,包括连接管理、数据库管理、表管理、视图管理、存储过程和函数管理,这些功能点我们都可以使用JDBC实现,例如表管理中包括创建表、修改表等功能,我们可以使用JDBC直接执行SQL语句中的CREATE TABLE和ALTER TABLE来达到目的。除了这些功能外,还需要对数据库中的数据进行导出和导与的操作,进行这些操作,我们可以编写程序来实现,但是,更好办法就是使用MySQL的命令(mysql或者mysqldump)来解决,这样可以轻松解决数据的导出与导入,但是,前提就是使用的客户端必须安装MySQL数据库,并且要告诉我们这个管理器,MySQL的具体目录,我们可以使用程序去调用这些MySQL的命令。下面,我们就开始实现这些所定义的功能。

2 建立界面

在编写程序前,我们需要准备各个界面,包括连接管理界面、表管理界面、视图管理界面、存储过程(函数)管理界面与查看数据界面等。表管理、视图管理、存储过程和函数管理我们可以建立一个主界面,根据不同的情况显示不同的菜单,而连接管理我们可以使用一棵树来进行管理,可以同时存在多个连接,这些连接下面的子节点就是该连接下面的数据库。

2.1 MySQL安装目录选择界面

当进入管理器时,我们就需要让用户去选择MySQL的安装目录,原因就是因为我们需要MySQL的内置命令,因此需要指定MySQL的安装目录。图1是安装目录选择界面。

图1 MySQL安装目录选择界面

让用户选择MySQL安装目录十分简单,只提供一个目录选择安装以及显示目录路径的JTextFeild,并且加入一个确定与取消按钮。当用户选择了MySQL的安装目录,点击了确定时,就显示我们的主界面,这里需要注意的是,我们在实现的时候,需要判断用户所选择的MySQL[安装目录是否正确,由于mysql与mysqldump等命令是存在于MySQL安装目录下的bin目录的,因此判断用户所选择的目录是否正确,可以判断在bin目录下是否存在相应的命令,这些将在下面的章节中描述。MySQL安装目录在本章代码中对应的是ConfigFrame类。

2.2 主界面

主界面提供各种功能的入口,可以让用户在该界面中使用或者进入各个功能,除了需要提供这些入口外,还需要提供一棵树,更直观的展示当前所使用的连接,以及该连接下面所有的数据库。主界面如图2所示。

图2 主界面

主界面由一个工具栏,一棵树以及一个JList组成,其中工具栏中包括的操作如下:

      添加连接:可以让用户添加一个连接。

      查看表:查看当前数据库中所有的表。

      查看视图:查看当前数据库中所有的视图。

      查看存储过程(函数):查看数据库中所有的存储过程与函数。

      打开执行SQL的界面:打开一个执行SQL语句的界面。

在主界面的左边部分,提供一棵树让用户十分直观的看到连接的相关信息,这棵树可以隐藏根节点,第一层节点就是连接,第二层节点就是该连接下所对应的所有的数据库,每一个数据库节点下面可以有三个子节点:表、视图和存储过程,当然,我们在平时使用其他管理工具的时候,还可以有触发器等内容,我们在本章的项目中不提供这些功能。

这里需要注意的是,我们更换了树的各个节点图片,因此需要为JTree添加一个DefaultTreeCellRenderer来设置各个节点的图片以及文字,当然,还需要将各个节点抽象成不同的对象,新建各个视图对象的接口ViewObject,该接口将是所有视图对象的父类,这些视图对象包括树的各个节点,主界面右边列表所显示的各个元素等。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectViewObject.java

public interface ViewObject {

         //返回显示的图片

         Icon getIcon();

}

该接口只有一个getIcon方法,返回一个Icon对象,表示这些界面所对应的图片,另外,树上的各个节点对象,可以有两种形式,第一种就是需要带连接的节点,例如数据库连接节点和数据库节点,第二种就是不需要带有连接的节点,因此我们可以将带有连接的节点抽象成一个父类,让连接节点和数据库节点去继承。另外,还需要提供一个connect的抽象方法,需要让子类去实现。

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeConnectionNode.java

public abstract class ConnectionNode implements ViewObject {

         //JDBC的Connection对象

         protected Connection connection;

         //连接方法,由子类去实现

         public abstract Connection connect();

         //省略setter和getter方法

}

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeServerConnection.java

public class ServerConnection extends ConnectionNode {

         private final static String DRIVER = "com.mysql.jdbc.Driver";//MySQL驱动

         private String connectionName; //MySQL驱动

         private String username; //用户名

         private String password; //密码

         private String host; //连接ip

         private String port; //连接端口

         //省略setter和getter方法

         //实现接口ViewObject的方法, 根据不同的连接状态显示不同的图片

         public Icon getIcon() {

                   if (super.connection == null) return ImageUtil.CONNECTION_CLOSE;

                   else return ImageUtil.CONNECTION_OPEN;

         }

         //重写toString方法, 返回连接名称

         public String toString() {

                   return this.connectionName;

         }

         //实现父类的connect方法

         public Connection connect() {

         }

}

一个ServerConnection对象表示一个连接节点,一个连接节点当然需要包括一些连接的相关信息,包括连接名称、MySQL用户名、密码、连接的IP与端口等信息。该对象实现了ViewObject的getIcon方法,判断父类ConnectionNode的connection属性是否为空来显示不同的图片,还需要重写toString方法,返回连接的名称。

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java

public class Database extends ConnectionNode {

         private String databaseName; //数据库名字

         private ServerConnection serverConnection; //数据库所属的服务器连接

         //需要使用数据库名称与服务器连接对象构造

         public Database(String databaseName, ServerConnection serverConnection) {

                   this.databaseName = databaseName;

                   this.serverConnection = serverConnection;

         }

         //实现接口的方法, 判断该数据库是否连接, 再返回不同的图片

         public Icon getIcon() {

                   if (this.connection == null) return ImageUtil.DATABASE_CLOSE;

                   return ImageUtil.DATABASE_OPEN;

         }

         //重写toString方法

         public String toString() {

                   return this.databaseName;

         }

         //实现父类的connect方法

         public Connection connect() {

         }

}

Database节点对象包括数据库的名字,另外还需要一个ServerConnection对象,由于每个数据库都是某个连接节点下面的子节点,因此需要记录它的父节点,当然,并不是简单的进行记录,还可以让它们共享一些不会经常创建的实例,例如Connection。另外,需要注意的是,无论ServerConnection或者Database对象,都需要实现ViewObject的getIcon方法,当连接节点或者数据库节点被打开时,都需要改变它们的图片,而显示何种图片,由getIcon方法决定。

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeTableNode.java

public class TableNode implements ViewObject {

         private Database database; //所属的数据库节点

         //返回表的树节点图片

         public Icon getIcon() {

                   return ImageUtil.TABLE_TREE_ICON;

         }

         //重写toString方法

         public String toString() {

                   return "表";

         }

}

一个TableNode对象代表一个表的节点,需要提供一个Database属性来表示这个对象是属于哪个数据库下面的子节点。如图2所示,我们可以看树中每个数据库节点的表节点都是一致的(每个数据库里面都有表),可以将这个表节点理解成是导航栏的某一组成部分,当用户点击了这个节点后,就可以在右边的列表中显示对应数据库的表。

与TableNode一样,另外再次创建两个对象:ViewNode和ProcedureNode,分别代表数据库节点下面的视图节点和存储过程节点,实现方法与TableNode类似。下面为树节点添加一个DefaultTreeCellRenderer类,让其得到这些节点对象,并设置相应的文字和图片。

代码清单:codemysql-managersrcorgcrazyitmysqlui reeTreeCellRenderer.java

public class TreeCellRenderer extends DefaultTreeCellRenderer {

         public Component getTreeCellRendererComponent(JTree tree, Object value,

                            boolean sel, boolean expanded, boolean leaf, int row,

                            boolean hasFocus) {

                   DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;

                   //获得每个节点的ViewObject

                   ViewObject obj = (ViewObject)node.getUserObject();

                   if (obj == null) return this;

                   this.setText(obj.toString());//设置文字

                   this.setIcon(obj.getIcon());//设置图片

                   if (sel) this.setForeground(Color.blue); //判断是否选来设置字体颜色

                   else this.setForeground(getTextNonSelectionColor());

                   return this;

         }

}

在节点处理类TreeCellRenderer类,得到每个节点的ViewObject后,就可以为节点设置文字和图片,我们的ViewObject接口提供了getIcon方法,所以我们就可以在节点处理类中得到每个节点所对应的图片与文字(从toString方法获得)。

树的相关处理就完成了,主界面的右边是一个列表,对应的是一个JList对象,JList里面的每一个元素,都是ViewObject的实现类,只需要实现getIcon方法与重写toString方法即可。每个列表的元素对象都可以将它们的name属性抽象到一个父类中,各个对象去继承它即可,在本例中,我们所涉及有三种数据类型:表、视图和存储过程(函数),我们需要建立三个对象,分别代表这三种数据类型。

在本章的代码中,我们创建了TableData、ViewData和ProcedureData三个类分别代表表数据、视图数据和存储过程数据,这三个对象都需要实现ViewObject接口,具体的实现与三个节点的实现类似,都需要实现getIcon方法并重写toString。表、视图和存储过程都是某一个数据库下面的元素,因此这三个数据对象都需要保存一个Database属性,表示该数据所属于的数据库。与树一样,还需要提供一个元素处理类,来指定显示的数据图片。

代码清单:codemysql-managersrcorgcrazyitmysqluilistListCellRenderer.java

public class ListCellRenderer extends DefaultListCellRenderer {

         public Component getListCellRendererComponent(JList list, Object value,

                            int index, boolean isSelected, boolean cellHasFocus) {

                   JLabel label = (JLabel)super.getListCellRendererComponent(list,

                                     value, index, isSelected, cellHasFocus);

                   ViewObject vd = (ViewObject)value; //得到ViewObject对象

                   label.setIcon(vd.getIcon());//设置图片

                   label.setToolTipText(vd.toString());

                   //设置选中时的字体颜色

                   if (isSelected) {

                            setBackground(Color.blue);

                            setForeground(Color.white);

                   }

                   return this;

         }

}

到这里,主界面的各个对象都创建好了,本章中对应的主界面对象是MainFrame类,可以在该类中创建对应的树与列表。这里需要注意的是,当创建列表(JList)的时候,可以将JList设置为横向滚动,调用以下代码即可实现:

dataList.setLayoutOrientation(JList.VERTICAL_WRAP); //dataList是界面中的JList对象

创建主界面后,我们可以在创建树与创建列表的时候加入一些模拟数据来查看效果,具体的效果如图3所示:

图3 主界面效果

2.3 数据显示界面

在整个管理器中,我们需要一个数据显示的界面,而且只有一个。打开数据显示界面的途径有两种,一种是双击一个表查看数据的时候,另外一种就是执行SQL的时候(执行查询的SQL),就会打开数据显示界面,将用户感兴趣的数据显示出来。由于一般会存在打开多个表或者多次执行SQL的情况,因此我们在编写打开数据显示界面的代码的时候,每次都需要去创建这个界面对象的实例。在本章中,界面显示对象对应的类是DataFrame,数据显示界面如图4所示。

图4 数据显示界面

界面比较简单,一个工具条加一个表格即可,工具条中包括的功能有:

      刷新:刷新当前界面的数据。

      降序:当用户选择了某一列并点击该图标的时候,就对该列所对应的字段进行降序排序。

      升序:操作与降序一样,但是对所选字段进行升序排序。

在这个界面中,需要注意的是,这个列表对应的JTable对象并不像其他JTable一样,拥有固定的列,由于我们不可能知道用户将要打开的表有多少列,因此只能在用户打开表的时候,得到该表的信息再动态的生成列与数据。除了这里之外,我们还需要为这个JTable对象进行一些额外的处理,例如我们需要让这个JTable对象可以整列选择,就需要自己编写一个类去继承JTable。

代码清单:codemysql-managersrcorgcrazyitmysqlui ableDataTable.java

         //当点击表头时, 表示当前所选择的列

         private int selectColumn = -1;

         public DataTable(DefaultTableModel model) {

                   //为表头添加鼠标事件监听器

                   header.addMouseListener(new MouseAdapter() {

                            public void mouseClicked(MouseEvent e) {

                                     header.getTable().clearSelection();

                                     int tableColumn = header.columnAtPoint(e.getPoint());

                                     selectColumn = tableColumn;

                            }

                   });

                   //为JTable添加鼠标监听器

                   this.addMouseListener(new MouseAdapter() {

                            public void mouseClicked(MouseEvent e) {

                                     selectColumn = -1;

                                     updateUI();

                            }

                   });

         }

注意以上代码中的类属性selectColumn,当我们用鼠标点击了表头的时候,就将该值设为当前选择的列的索引,当在JTable的其他地方点击了鼠标时,就设置该值为-1,表示没有选择表头。那么我们就需要重写JTable的isCellSelected方法,如果selectColumn不是-1,那么就需要将用户所选择的列整列设为选中状态,以下是isCellSelected方法的实现:

         //判断一个单元格是否被选中, 重写JTable的方法

         public boolean isCellSelected(int row, int column) {

                   if (this.selectColumn == column) return true; //如果列数与当前选择的列相同,返回true

                   return super.isCellSelected(row, column);

         }

另外,我们还需要提供一个返回selecColumn值的public的方法。做完这些后,可以点击一列,看到效果如图5所示。

图5 数据显示界面选择整列

2.4 创建连接界面

连接是整个工具的最基础部分,没有连接,其他任何操作都不能进行,因此使用这个MySQL管理工具,就需要提供一个新增连接的界面,让用户去创建各个连接,界面如图6所示。

图6 新建连接界面

图6中新建连接的界面比较简单,普通的一个表单,界面中包括的元素如下:

      连接名称:该名称在管理器的树中显示,并且该名称不可以重复。

      连接IP:需要连接到的MySQL服务器IP。

      端口:MySQL的端口,默认为3306。

      用户名:连接MySQL的用户名,例如root。

      密码:连接MySQL的密码。

      测试连接:测试输入的信息是否可以连接到MySQL服务器中,当然,如果测试不能连接,也可以添加这个连接。

      确定和取消:点击确定添加连接并关闭该窗口,点击取消不保存连接并关闭窗口。

2.5 创建表界面

当用户需要创建一个表的时候,就需要提供一个界面让用户去输入表的各种数据,包括字段名称、类型、是否允许空和主键等信息。创建表界面是本章中最为复杂的界面,用户可以随意的在表中进行操作,最后执行保存,表界面如图7所示。

图7 创建表界面

界面如图7所示,该界面较为复杂,分成上下两个表格,上面的表格主要处理表的字段信息,包括字段名、类型、是否允许空和主键,在该表格下面,有一个输入默认值的文本框,并提供一个表示字段是否自动增长的多选框。当我们在表格中选中某行数据(字段)的时候,默认值就需要发生相应的改变,自动增长的多选框也要随着改变。在本章中表界面对应的是TableFrame类。

字段表格需要进行特别处理的是允许空和主键的单元格,这两个单元格都需要使用图片来显示。我们编写一个FieldTable类来表示字段表格,并为这个FieldTable提供一个DefaultTableCellRenderer的子类来对单元格进行处理。

代码清单:codemysql-managersrcorgcrazyitmysqlui ableFieldTableIconCellRenderer.java

public class FieldTableIconCellRenderer extends DefaultTableCellRenderer {

         public Component getTableCellRendererComponent(JTable table, Object value,

                            boolean isSelected, boolean hasFocus, int row, int column) {

                   //判断单元格的值类型,分别调用setIcon与setText方法

                   if (value instanceof Icon) this.setIcon((Icon)value);

                   else this.setText((String)value);

                   this.setHorizontalAlignment(CENTER);

                   return this;

         }

}

FieldTableIconCellRenderer的实现十分简单,只是判断单格的值再进行处理。在FieldTable使用以下代码即可实现显示图片。

                   this.getColumn(ALLOW_NULL).setCellRenderer(this.cellRenderer);

                   this.getColumn(PRIMARY_KEY).setCellRenderer(this.cellRenderer);

以上代码先得到允许空和主键的列后再设置单元格处理类。重新运行程序时,就可以看到效果如图7所示,但是否需要对FieldTable加入鼠标事件处理,当点击了允许空和主键的列单元格时,就需要改变它们图片。为FieldTable加入鼠标监听器。

代码清单:codemysql-managersrcorgcrazyitmysqlui ableFieldTable.java

         //鼠标在JTable中点击的时候触发该方法

         private void selectCell() {

                   int column = this.getSelectedColumn();

                   int row = this.getSelectedRow();

                   if (column == -1 || row == -1) return;

                   //修改图片列

                   selectAllowNullColumn(row, column);

                   selectPrimaryKeyColumn(row, column);

         }

         //点击的单元格位于允许空列

         private void selectAllowNullColumn(int row, int column) {

                   //得到需要更改图片的列(允许空列)

                   TableColumn tc = this.getColumn(ALLOW_NULL);

                   if (tc.getModelIndex() == column) {

                            Icon currentIcon = (Icon)this.getValueAt(row, column);

                            //根据当前选中的图片来更改允许空的图片

                            if (ImageUtil.CHECKED_ICON.equals(currentIcon)) {

                                     this.setValueAt(ImageUtil.UN_CHECKED_ICON, row, column);

                            } else {

                                     this.setValueAt(ImageUtil.CHECKED_ICON, row, column);

                            }

                   }

         }

         //如果鼠标点击的列是"主键列",去掉或者加上图标

         private void selectPrimaryKeyColumn(int row, int column) {

                   //得到需要更改图片的列(主键列)

                   TableColumn tc = this.getColumn(PRIMARY_KEY);

                   if (tc.getModelIndex() == column) {

                            Object obj = this.getValueAt(row, column);

                            if (ImageUtil.PRIMARY_KEY_BLANK.equals(obj)) {

                                     this.setValueAt(ImageUtil.PRIMARY_KEY, row, column);

                            } else {

                                     this.setValueAt(ImageUtil.PRIMARY_KEY_BLANK, row, column);

                            }

                   }

         }

只需要在创建FieldTableIconCellRenderer的时候为表格加入鼠标监听器,该监听器调用以上代码的selectCell方法即可,selectCell方法再去调用点击允许空和主键单元格的方法,即以上的selectAllowNullColumn和selectPrimaryKeyColumn方法,这两个方法中判断用户所选择的列,是否为需要进行图片处理的列(允许空和主键),再对单元格的值(图片)进行修改,就可以达到点击单元格就显示不同图片的效果。另外,当我们点击了某行数据(字段)的时候,还需要处理默认值与自动增长,我们在下面章节将会实现。

实现了字段列表后,还需要注意的是该列表下面的三个按钮,分别是新字段、插入字段和删除字段,新字段与插入字段的区别是,新字段在列表的最后加入一行数据,插入字段在用户所选择的行的前面插入一行数据。

TableFrame下面的外键列表与字段列表不同的是,外键列表不需要进行图片处理,但是每个单元格都需要使用下拉框来代替普通的文字。与字段列表一样,新建一个ForeignTable的类来表示一个外键列表,外键列表有5列,而且每一列中的每个单元格都是下拉框,因此我们需要在ForeignTable中创建5个下拉框(JComboBox)以及5个单元格编辑器对象。

5个单元格编辑器对象,以下是ForeignTable的实现。

代码清单:codemysql-managersrcorgcrazyitmysqlui ableForeignTable.java

         private DefaultCellEditor fieldNameEditor; //字段名称编辑器对象

         private DefaultCellEditor referenceTableEditor; //约束表

         private DefaultCellEditor referenceFieldEditor; //约束字段

         private DefaultCellEditor onDeleteEditor; //级联删除

         private DefaultCellEditor onUpdateEditor; //级联更新

那么在创建这些单元格编辑器对象的时候,就分别以各个下拉框的对象作为构造参数:

         this.fieldNameEditor = new DefaultCellEditor(this.fieldNameComboBox);

接下来,得到相应的列,再设置编辑器对象即可:

         this.getColumn(FIELD_NAME).setCellEditor(this.fieldNameEditor);

做完这些工作后,外键列表中所有的单元格都变成可以使下拉来设定值,我们在开发界面的时候,由于缺乏真实的数据,因此我们可以提供一些模拟的数据来实现效果,到需要实现的时候,就可以替换上真实的数据。新增表与修改表的界面可以共用一个界面,但是同时需要做新增与修改操作的时候,就需要做多一些额外的判断,本章中新增表与修改表为同一个界面(TableFrame)。

2.6 视图界面

当用户需要编写一个视图的时候,我们可以提供一个视图界面。视图界面实现十分简单,只有一个JTextArea即可,并附带有保存操作。这里需要注意的是,用户点击保存的时候,需要将视图通过SQL的CREATE VIEW来创建,那么用户查看视图的时候,与查看表一样,都是需要打开数据浏览界面。图8是视图界面。

图8 视图界面

在本章中,创建表的界面一样,无论新增视图或者修改视图,都使用相同的一个界面,对应的是ViewFrame。

2.7 存储过程界面

用户需要新建一个存储过程或者函数的时候,可以提供一个新建存储过程界面让用户去操作。存储界面在本章中对应的类是ProcedureFrame。存储过程界面如图9所示。

图9 存储过程界面

界面元素说明:

      输入方法体的JTextArea:用户可以在此输入存储过程或者函数的方法体。

      参数JTextField:输入存储过程或者函数的参数。

      返回值JTextField:可以输入函数的返回值,因为函数才有返回值。如果选择的类型为存储过程,则该JTextField不可用。

      类型下拉框:可以选择编写的类型,是存储过程还是函数。

2.8 查询界面

当用户需要执行一些SQL的时候,可以提供一个查询界面让用户去输入,该界面提供执行SQL与保存SQL的功能,执行SQL的时候,如果是普通的INSERT、UPDATE或者其他无需浏览数据的SQL语句,则可以直接操作。如果执行的是查询、调用存储过程或者函数的语句,那么就需要将结果显示到数据界面,即2.3的界面。本章对应的查询界面类是QueryFrame,查询界面如图10所示。

图10 查询界面

2.9 树节点右键菜单

在主界面的连接树中,当我们点击了树的某个节点的时候,可以提供一些右键菜单来执行一些相关的操作,例如点击了连接节点,就可以提供关闭连接、删除连接等右键菜单,如果点击了数据库节点,就可以提供关闭数据库或者删除数据库等右键菜单。

点击连接节点的右键菜单如图11所示。

图11 连接节点菜单

点击数据库节点的右键菜单如图12所示。

图12 数据库节点右键菜单

由于我们对连接节点或者数据库节点进行选择的时候,就可以打开连接或者数据库,因此并不需要提供打开的菜单,本章中使用JPopupMenu来实现鼠标右键菜单,MainFrame中提供一个JPopupMenu对象来存放各个菜单当点击了连接节点的时候JPopupMenu删除所有的子菜单,再加入连接节点的菜单(JMenuItem),数据库节点的实现方式与之相同。

2.10 数据列表右键菜单

主界面中除了连接树外,还有一个数据列表,当用户在树中点击了表节点、视图节点或者存储过程节点的时候,数据列表中就显示不同的数据,我们可以根据当前所显示的数据来创建不同的鼠标右键菜单。图13是数据列表显示表数据的时候的右键菜单。

图13 表数据菜单

表数据鼠标右键菜单说明:

      新建表:打开创建表的界面,即2.5中的界面。

      编辑表:修改一个表,与新建表使用同一个界面。

      删除表:删除列表中选择数据。

      导出表:将一个表的数据导出。

视图数据的鼠标右键菜单如图14所示。

图14 视图数据菜单

视图数据鼠标右键菜单说明:

      新建视图:打开2.6中的视图界面,用于创建视图。

      编辑视图:修改所选择的视图,与新建视图使用同一个界面。

      删除视图:删除所选择的视图。

存储过程鼠标右键菜单如图15所示。

图15 存储过程数据菜单

存储过程鼠标右键菜单说明:

      新建存储过程:打开2.7中的存储过程界面,创建存储过程。

      编辑存储过程:修改选择的存储过程,与新建存储过程使用相同的界面。

      删除存储过程:删除所选择的存储过程或者函数。

以上为三种数据的右键菜单,实现方式与树节点的右键菜单一样,当界面的数据发生改变时,就相应的去删除JPopupMenu所有的子菜单,再添加相应的菜单(JMenuItem)即可。

以上的菜单均在主界面(MainFrame)中创建,程序并不知道当前显示的是哪种数据,因此我们需要在MainFrame中提供一个ViewObject的类来标识当前显示的类型,ViewObject是所有界面元素都需要实现的接口,表数据是TableData类,视图数据是ViewData类,存储过程数据是ProcedureData类,详细请看2.2中的各个界面对象。当用户点击了工具栏或者树上的某个节点时,就相应的改变MainFrame中的ViewObject即可。

到此,管理器的所有界面都创建完毕,接下来就可以实现相关的功能。

3 实现MySQL安装目录选择功能

实现MySQL安装目录选择功能,我们使用2.1的界面。当用户进入管理器的时候,就让用户选择本地的MySQL安装目录,由于我们需要使用MySQL的一些内置命令,因此选择MySQL的安装目录是一个必要的操作,得到MySQL安装目录后,我们就可以找到bin目录下面的命令。因此用户选择了安装目录后,我们的程序就需要对所选择目录进行验证,判断能否找到bin目录。

3.1 实现目录选择

选择目录实现十分简单,只需要提供一个文件选择器即可,而且这个文件选择器只可以选择目录,当用户选择了对应的目录后,就可以将其选择的目录显示到2.1界面的JTextField中。文件选择器的代码如下。

代码清单:codemysql-managersrcorgcrazyitmysqluiConfigFrame.java

         private JTextField field;

         public FileChooser(JTextField field) {

                   this.field = field;

                   //设置只可以选择目录

                   this.setFileSelectionMode(FileChooser.DIRECTORIES_ONLY);

         }

         //重写JFileChooser的方法

         public void approveSelection() {

                   //设置JTextField的值

                   this.field.setText(this.getSelectedFile().getAbsolutePath());

                   super.approveSelection();

         }

        

用户选择目录后,就将其所选的目录的绝对路径显示到JTextField中,当点击确定的时候,就可以进行判断,以下代码为点击确定所执行的代码。

代码清单:codemysql-managersrcorgcrazyitmysqluiConfigFrame.java

                   //取得用户输入值

                   String mysqlHome = this.mysqlHomeField.getText();

                   //寻找用户选择的目录,判断是否可以找到MySQL安装目录下的bin目录

                   File file = new File(mysqlHome + MySQLUtil.MYSQL_HOME_BIN);

                   //找不到MySQL的安装目录,提示

                   if (!file.exists()) {

                            showMessage("请选择正确MySQL安装目录", "错误");

                            return;

                   }

以上代码的黑体部分,需要去判断MySQL安装目录下的bin目录是否存在,如果没有存该目录,则表示用户所选择的目录是错误的,弹出提示并返回。如果用户选择的目录是正确的话,就需要去读取管理器的配置文件。

3.2 读取和保存安装目录路径

用户选择了MySQL的安装目录后,我们需要将目录的绝对路径保存到一份配置文件中,这样做的话,就可以不必每一次都去进行目录选择。提供一份mysql.properties的配置文件,以下为该配置文件的读取代码。

代码清单:codemysql-managersrcorgcrazyitmysqlutilFileUtil.java

         //返回配置文件的MYSQL_HOME配置

         public static String getMySQLHome() {

                   File configFile = new File(MYSQL_PROPERTIES_FILE);

                   Properties props = getProperties(configFile);

                   return props.getProperty(MYSQL_HOME);

         }

以上代码中的MYSQL_PROPERTIES_FILE就是mysql.properties配置文件的相对路径,找到该文件后,就读取它的mysql.home属性。那么用户在进入MySQL安装目录选择界面的时候,就可以调用以上的方法去获得MySQL安装目录的值。

接下来实现保存安装目录的功能,在这之前,新建一个GlobalContext的类,用于保存管理器全局的一些信息,例如这里的mysql.home属性。以下代码实现保存配置的功能。

代码清单:codemysql-managersrcorgcrazyitmysqluiConfigFrame.java

                   //省略其他代码...

                   //如果配置文件的值与用户输入的值不相等,则重新写入配置文件中

                   if (!mysqlHome.equals(FileUtil.getMySQLHome())) {

                            FileUtil.saveMysqlHome(this.mysqlHomeField.getText());

                   }

                   GlobalContext ctx = new GlobalContext(mysqlHome);

                   this.mainFrame = new MainFrame(ctx);

                   this.mainFrame.setVisible(true);

                   this.setVisible(false);

注意以上代码的判断,如果用户前一次所选择的MySQL安装目录与这一次所选择的目录不一致,则需要重新将新的目录信息保存到mysql.properties文件中。这些做的话,就不需要每一次进入系统都去修改配置文件。

3.3 读取连接信息

在得到MySQL安装目录,进入主界面时,还需要得到用户所有的连接信息,这些信息用来初始化主界面左边的树,管理器是针对MySQL数据库的,但是这些连接信息可以不记录到数据库,与保存MySQL安装目录一样,可以提供一些properties文件来保存,每一个连接作为一份properties文件。保存连接的信息我们在下面的章节中实现,这里主要实现读取的实现。

新建一个PropertiesHandler的接口,专门用于处理连接属性文件。该接口提供一个读取数据库连接配置文件的方法,并返回ServerConnection集合,ServerConnection代表一个连接节点,并保存有一些数据库连接的信息,详细请看2.2中的ServerConnection类。以下代码读取一份properties,并返回一个Properties对象。

代码清单:codemysql-managersrcorgcrazyitmysqlutilFileUtil.java

         //根据文件得到对应的properties文件

         public static Properties getProperties(File propertyFile) throws IOException {

                   Properties prop = new Properties();

                   FileInputStream fis = new FileInputStream(propertyFile);

                   prop.load(fis);

                   fis.close();

                   return prop;

         }

那么在PropertiesHandler实现类中,就可以读取相应目录下的所有properties文件。

代码清单:codemysql-managersrcorgcrazyitmysqlsystemPropertiesHandlerImpl.java

         //得到所有的连接信息

         public List<ServerConnection> getServerConnections() {

                   File[] propertyFiles = getPropertyFiles();

                   List<ServerConnection> result = new ArrayList<ServerConnection>();

                   for (File file : propertyFiles) {

                            ServerConnection conn = createServerConnection(file);

                            result.add(conn);

                   }

                   return result;

         }

         //将一份properties文件封装成ServerConnection对象

         private ServerConnection createServerConnection(File file) {

                   Properties prop = FileUtil.getProperties(file);

                   ServerConnection conn = new ServerConnection(FileUtil.getFileName(file),

                                     prop.getProperty(FileUtil.USERNAME),

                                     prop.getProperty(FileUtil.PASSWORD),

                                     prop.getProperty(FileUtil.HOST),

                                     prop.getProperty(FileUtil.PORT));

                   return conn;

         }

得到所有的连接信息后,先不需要初始化树,需要将这些信息存放到一个对象中,因为在下面的实现中,这些类或者连接信息需要经常使用到。在3.2中提供了一个GlobalContext的类来表示管理器的上下文,可以将这些连接信息放到该类中。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectGlobalContext.java

         //存放所有服务器连接的集合

         private Map<String, ServerConnection> connections = new HashMap<String, ServerConnection>();

         //添加一个连接到Map中

         public void addConnection(ServerConnection connection) {

                   this.connections.put(connection.getConnectionName(), connection);

         }

在GlobalContext中建立一个Map来保存这些连接信息,并提供add方法,由于这个Map是使用连接的名称作为key的,所以就决定了在管理器中不允许出现重名的连接。那么在用户选择MySQL安装目录,点击确定后,就可以将连接加入到GlobalContext中,用户点击确定按钮执行的部分代码。

代码清单:codemysql-managersrcorgcrazyitmysqluiConfigFrame.java

                   //读取全部的服务器连接配置

                   List<ServerConnection> conns = ctx.getPropertiesHandler().getServerConnections();

                   for (ServerConnection conn : conns) ctx.addConnection(conn);

到此,MySQL安装目录的功能已经实现,得到用户的各个连接信息后,就可以根据这些连接实现创建树的功能。

4 连接管理

进入主界面后,我们需要将各个连接信息创建一棵树,用户往后的各个操作,都与这些棵树息息相关。树的第一层节点是管理器中的各个连接,只需要得到各个连接后,以这些连接对象创建第一层节点即可。本小节将实现连接相关的功能,这些功能包括创建连接节点、打开连接、删除连接等。

4.1 创建连接节点

进入主界面时,我们已经可以得到GlobalContext对象,各个连接信息都保存在该对象中,因此可以根据这些连接信息来创建树。在创建树的时候,需要注意的是,我们只需要根据这些连接信息来创建第一层节点,而不需要再去创建下面的几层节点,当用户点击第一层节点(连接节点)的时候,再去访问该连接下面的数据库信息,正常得到这些数据库后,再创建数据库节点。

MainFrame中创建连接节点的方法。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //创建树中服务器连接的节点

         private void createNodes(DefaultMutableTreeNode root) {

                   Map<String, ServerConnection> conns = this.ctx.getConnections();

                   for (String key : conns.keySet()) {

                            ServerConnection conn = conns.get(key);

                            //创建连接节点

                            DefaultMutableTreeNode conntionNode = new DefaultMutableTreeNode(conn);

                            root.add(conntionNode);

                   }

         }

MainFrame中创建树的方法。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //创建树

         private void createTree() {

                   DefaultMutableTreeNode root = new DefaultMutableTreeNode(new RootNode());

                   //创建连接节点

                   createNodes(root);

                   this.treeModel = new DefaultTreeModel(root);

                   //构造树

                   JTree tree = new JTree(this.treeModel);

                   //设置节点处理类

                   TreeCellRenderer cr = new TreeCellRenderer();

                   tree.setCellRenderer(cr);

                   //设置监听器类

                   tree.addMouseListener(new TreeListener(this));

                   tree.setRootVisible(false);

                   //添加右键菜单

                   tree.add(this.treeMenu);

                   this.tree = tree;

         }

以上代码中的TreeCellRenderer为节点的处理类,具体的实现请看2.2中TreeCellRenderer类的实现。TreeListener是一个鼠标事件监听器,当用户点击了树的连接节点后,需要建立连接,我们在下面的章节中实现。

4.2 打开连接

当用户点击连接节点后,就需要立即打开这个连接,我们使用了一个ServerConnection对象来保存连接的信息,并使用该对象来创建树中的连接节点,打开连接的时候,就可以根据连接的信息去尝试进行服务器连接,使用JDBC进行连接即可。如果成功进行连接,就马上创建该连接节点的子节点(数据库节点),如果不能成功连接,则弹出提示。ServerConnection继承了ConnectionNode这个抽象类,ConnectionNode中保存了一个JDBC的Connection对象,ServerConnection中判断是否连接的标准是判断ConnectionNode的Connection对象是否为空,而且ServerConnection中需要实现父类(ConnectionNode)的connect方法,下面代码是ServerConnection对ConnectionNode的connect方法的实现。

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeServerConnection.java

         //实现父类的方法

         public Connection connect() {

                   //Connection在本类中只有一个实例

                   if (super.connection != null) return super.connection;

                   Class.forName(DRIVER);

                   Connection conn = createConnection("");

                   super.connection = conn;

                   return super.connection;

         }

         //创建连接, 参数是数据库名称

         public Connection createConnection(String database) throws Exception {

                   Class.forName(DRIVER);

                   Connection conn = DriverManager.getConnection(getConnectUrl() + database,

                                     this.username, this.password);

                   return conn;

         }

以上的代码中先判断ServerConnection的connection属性是否为空,如果该属性为空(没有连接)则进行创建。注意createConnection方法,该方法声明为public,可以让外部去使用。下面实现树的节点监听器,当节点被选中后,就可以执行connect方法,但是需要注意的是,并不是每个节点都相同,只是连接节点被点击的时候才去进行连接。

代码清单:codemysql-managersrcorgcrazyitmysqlui reeTreeListener.java

         public void mousePressed(MouseEvent e) {

                   if (e.getModifiers() == MouseEvent.BUTTON1_MASK) {

                            //左键点击,查看树的节点,调用MainFrame的打开节点方法

                            this.mainFrame.viewTreeDatas();

                   }

         }

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //点击树节点的操作

         public void viewTreeDatas() {

                   //获得选中的节点

                   DefaultMutableTreeNode selectNode = getSelectNode();

                   if (selectNode == null) return;

                   //判断点击节点的类型

                   if (selectNode.getUserObject() instanceof ServerConnection) {

                            clickServerNode(selectNode);//服务器连接节点

                   }

         }

         //点击服务器节点

         public void clickServerNode(DefaultMutableTreeNode selectNode) {

                   //暂时不实现

         }

连接节点被点击后,就会执行clickServerNode方法,该方法需要做的是先去验证被选中的节点是否可以进行连接,再创建该连接节点的子节点(数据库节点)。要创建数据库节点,就要得到该连接下面所有的数据库,执行MySQL的一句show databases就可以得到所有的数据库。

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeServerConnection.java

         //获得一个服务器连接下面所有的数据库

         public List<Database> getDatabases() {

                   List<Database> result = new ArrayList<Database>();

                   try {

                            //获得一个连接下面所有的数据库

                            ResultSet rs = query("show databases");

                            while (rs.next()) {

                                     String databaseName = rs.getString("Database");

                                     Database db = new Database(databaseName, this);

                                     result.add(db);

                            }

                            rs.close();

                            return result;

                   } catch (Exception e) {

                            return result;

                   }

         }

         //查询并返回ResultSet对象

         public ResultSet query(String sql) throws Exception {

                   Statement stmt = getStatement();

                   return stmt.executeQuery(sql);

         }

使用Statement执行show databases就可以得到所有的数据库ResultSet对象,这里需要注意的是,我们需要得到Statement对象,直接使用ConnectionNode的connection属性去创建Statement对象即可,并不需要再去重新创建JDBC的Connection对象。查询到数据库的ResultSet对象后,就将Database列的值封装成一个Database对象,加入到结果集中即可。在本章中,一个Database对象代表一个数据库节点。那么现在就可以实现clickServerNode方法,点击了连接节点后,就会执行该方法。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //点击服务器节点

         public void clickServerNode(DefaultMutableTreeNode selectNode) {

                   ServerConnection server = (ServerConnection)selectNode.getUserObject();

                   //验证是否可以进行连接

                   validateConnect(selectNode, server);

                   //创建服务器子节点

                   buildServerChild(server, selectNode);

         }

         //创建数据库一层的节点(树的第二层)

         public void buildServerChild(ServerConnection server,

                            DefaultMutableTreeNode conntionNode) {

                   //如果有子节点,则不再创建

                   if (conntionNode.getChildCount() != 0) return;

                   List<Database> databases = server.getDatabases();

                   //再创建连接节点下面的数据节点

                   for (Database database : databases) {

                            DefaultMutableTreeNode databaseNode = new DefaultMutableTreeNode(database);

                            //将数据库节点加入到连接节点中

                            this.treeModel.insertNodeInto(databaseNode, conntionNode, conntionNode.getChildCount());

                   }

         }

         //判断连接是否出错,适用于服务器节点和数据库节点

         private void validateConnect(DefaultMutableTreeNode selectNode, ConnectionNode node) {

                   //进行连接

                   node.connect();

         }

打开连接的功能已经实现,可以运行程序查看效果。总的来说,打开一个连接需要做的是:验证连接和创建数据库节点。

4.3 新建连接

在2.4中,我们已经提供了一个创建连接的界面,实现新建连接,只需要将用户输入的连接信息保存到一份properties文件中,再向树中添加一个连接节点即可。我们为接口PropertiesHandler添加一个saveServerConnection的方法,PropertiesHandler是用于处理properties文件的接口,在3.3中已经创建。

PropertiesHandler实现类对saveServerConnection的实现。

代码清单:codemysql-managersrcorgcrazyitmysqlsystemPropertiesHandlerImpl.java

         public void saveServerConnection(ServerConnection conn) {

                   //得到配置文件名, 这些properties文件存放于connections目录下

                   String configFileName = FileUtil.CONNECTIONS_FOLDER +

                            conn.getConnectionName() + ".properties";

                   //创建properties文件

                   File connConfigFile = new File(configFileName);

                   //创建文件

                   FileUtil.createNewFile(connConfigFile);

                   Properties props = new Properties();

                   props.setProperty(FileUtil.HOST, conn.getHost());

                   props.setProperty(FileUtil.PORT, conn.getPort());

                   props.setProperty(FileUtil.USERNAME, conn.getUsername());

                   props.setProperty(FileUtil.PASSWORD, conn.getPassword());

                   //将属性写入配置文件

                   FileUtil.saveProperties(connConfigFile, props, "Connection " +

                                     conn.getConnectionName() + " config.");

         }

saveServerConnection实现简单,只需要将ServerConnection对象中的各个属性写到properties文件中即可。那么在ConnectionFrame(新建连接界面)中,当用户输入各个信息点击确定后,就可以对这些连接进行保存,以下为点击确定执行的方法。

代码清单:codemysql-managersrcorgcrazyitmysqluiConnectionFrame.java

         //保存连接

         private void saveConnection() {

                   //得到用户输入的信息并返回一个ServerConnection对象

                   ServerConnection conn = getDataConnectionFromView();

                   //判断连接名称是否重复

                   if (this.ctx.getConnection(conn.getConnectionName()) != null) {

                            showMessage("已经存在相同名字的连接", "错误");

                            return;

                   }

                   //直接保存, 不需要创建任何的连接, 添加到GlobalContext的连接Map中

                   this.ctx.addConnection(conn);

                   //保存到属性文件

                   this.ctx.getPropertiesHandler().saveServerConnection(conn);

                   this.mainFrame.addConnection(conn);

                   this.setVisible(false);

         }

注意以上代码的黑体部分,需要进连接的名字进行判断,先去GlobalContext的连接Map中获取ServerConnection对象,如果能得到,则表示已经存在相同名字的连接。保存到属性文件后,就调用MainFrame的addConnection方法,以下是addConnection方法的实现。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //在添加连接界面添加了一个连接后执行的方法, 向树中添加一个连接

         public void addConnection(ServerConnection sc) {

                   //得到要节点

                   DefaultMutableTreeNode root = (DefaultMutableTreeNode)this.treeModel.getRoot();

                   DefaultMutableTreeNode newChild = new DefaultMutableTreeNode(sc);

                   //向要节点添加连接节点

                   this.treeModel.insertNodeInto(newChild, root, root.getChildCount());

                   if (root.getChildCount() == 1) this.tree.updateUI();             

         }

addConnection方法直接使用DefaultTreeModel的insertNodeInto方法向树添加一个ServerConnection节点。运行程序并进行添加一个连接,可以看到具体的效果。除了添加连接的功能外,界面中还有一个测试连接的功能,在添加连接前,可以先测试一下服务器是否可以连接。以下是点击测试连接按钮触发的方法。

代码清单:codemysql-managersrcorgcrazyitmysqluiConnectionFrame.java

         //测试连接

         private void checkConnection() {

                   //从界面中得到连接信息

                   ServerConnection conn = getDataConnectionFromView();

                   try {

                            conn.connect();

                            showMessage("成功连接", "成功");

                   } catch (Exception e) {

                            showMessage(e.getMessage(), "警告");

                   }

         }

与打开连接一样,都是使用ServerConnection的connect方法进行连接,再捕获异常。保存的时候,我们会再去从界面获取一个ServerConnection对象,因此测试连接的ServerConnection对象与保存时候的ServerConnection是两个对象。

4.4 删除连接

用户选择了一个连接需要删除的时候,就需要提供一个删除连接的功能,删除连接的功能我们在右键菜单中提供,当用户选择了某个连接节点的时候,就弹出该菜单,该菜单已经在2.9中实现,下面实现删除连接的功能。首先我们需要明白的是,删除一个连接,就是从管理器中彻底删除这个连接信息,再从树中删除这个连接节点,最后还需要从GlobalContext的连接Map中删除该连接。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //删除一个连接

         private void removeConnection() {

                   DefaultMutableTreeNode selectNode = getSelectNode();

                   ServerConnection conn = (ServerConnection)selectNode.getUserObject();

                   //从上下文件中删除

                   this.ctx.removeConnection(conn);

                   //从树节点中删除

                   this.treeModel.removeNodeFromParent(selectNode);

         }

当用户选择了某个连接节点的时候,选择右键菜单中的删除连接,就会触发上面的removeConnection方法,只需要为菜单对象添加ActionListener即可。先调用GlobalContext的删除连接方法将ServerConnection从全局上下文中删除,再使用DefaultTreeModel将该节点从树上删除。以下是GlobalContext中删除ServerConnection的方法(以上代码的黑体部分)。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectGlobalContext.java

         //从Map中删除一个连接

         public void removeConnection(ServerConnection connection) {

                   //删除该连接的配置文件

                   File configFile = new File(FileUtil.CONNECTIONS_FOLDER +

                                     connection.getConnectionName() + ".properties");

                   configFile.delete();

                   this.connections.remove(connection.getConnectionName());

         }

GlobalContext中的removeConnection方法,先删除properties文件,再从Map中删除该ServerConnection对象。

4.5 关闭连接

关闭一个服务器的连接,需要将ServerConnection对象的connection属性设置为true,connection属性保存在ServerConnection的父类ConnectionNode中,设置该属性为null后,连接节点的图标就自然会变成关闭的图标,因为ServerConnection中实现了ViewObject的getIcon方法。另外,还需要帮ServerConnection节点删除它的全部子节点。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //删除一个节点的所有子节点

         private void removeNodeChildren(DefaultMutableTreeNode node) {

                   //获取节点数量

                   int childCount = this.treeModel.getChildCount(node);

                   for (int i = 0; i < childCount; i++) {

                            //从最后一个开始删除

                            this.treeModel.removeNodeFromParent((DefaultMutableTreeNode)node.getLastChild());

                   }

         }

         //关闭服务器连接

         private void closeConnection() {

                   DefaultMutableTreeNode selectNode = getSelectNode();

                   ServerConnection sc = (ServerConnection)selectNode.getUserObject();

                   //将ServerConnection的连接对象设为null

                   sc.setConnection(null);

                   //删除所有的子节点

                   removeNodeChildren(selectNode);

                   //设置树不选中

                   this.tree.setSelectionPath(null);

         }

以上代码的removeNodeChildren方法,从树中删除一个节点的所有子节点,用户选择了某个节点再进行删除节点后就会触发closeConnection方法。

5 数据库管理

数据库管理功能不多,包括打开数据库、关闭数据库和删除数据库,这三个功能与连接管理中的功能类似,例如打开连连与打开数据库,都需要创建子节点,但是每个数据库的子节点都只有三个,分别是表节点、视图节点和存储过程节点,而连接则是根据数据库来创建子节点的。在本章中,我们使用一个Database对象来代表一个数据库节点,具体请看2.2中的Database类。Database对象与ServerConnection对象都是继承于ConnectionNode的,因此都需要去实现connect方法,并都有一个属性自己的Connection对象。实现打开数据库或者关闭数据库功能时,都与ServerConnection的实现类似。

5.1 打开数据库

当数据库节点被点击后,就可以进行打开操作,在4.2中,当树的某个节点被点击后,就会调用MainFrame的viewTreeDatas方法,在4.2中,我们只判断了用户点击服务器节点的情况,下面再帮该方法加入判断数据库节点的情况,当然,该方法还需要加入表节点、视图节点和存储过程节点的点击判断,判断用户点击的是哪种类型节点,再进行处理。

MainFrame的viewTreeDatas方法。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

                   //判断点击节点的类型

                   if (selectNode.getUserObject() instanceof ServerConnection) {

                            clickServerNode(selectNode);//服务器连接节点,在4.2中已经实现

                   } else if (selectNode.getUserObject() instanceof Database) {

                            clickDatabaseNode(selectNode);//数据库连接节点

                   }

以上的代码判断了用户点击节点的类型,以下是上面代码中clickDatabaseNode的实现,点击数据库节点后,需要进行数据库连接,再为数据库节点添加三个子节点(表、视图和存储过程)。

MainFrame的clickDatabaseNode方法。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //创建数据库节点子节点

         private void buildDatabaseChild(Database database,

                            DefaultMutableTreeNode databaseNode) {

                   //判断如果已经连接,则不创建节点

                   if (databaseNode.getChildCount() != 0) return;

                   //创建三个子节点(表、视图、存储过程)

                   DefaultMutableTreeNode tableNode = new DefaultMutableTreeNode(new TableNode(database));

                   DefaultMutableTreeNode viewNode = new DefaultMutableTreeNode(new ViewNode(database));

                   ProcedureNode pNode = new ProcedureNode(database);

                   DefaultMutableTreeNode procedureNode = new DefaultMutableTreeNode(pNode);

                   //插入树中

                   this.treeModel.insertNodeInto(tableNode, databaseNode, databaseNode.getChildCount());

                   this.treeModel.insertNodeInto(viewNode, databaseNode, databaseNode.getChildCount());

                  this.treeModel.insertNodeInto(procedureNode, databaseNode, databaseNode.getChildCount());

         }

         //点击数据库节点

         public void clickDatabaseNode(DefaultMutableTreeNode selectNode) {

                   //获取点击树节点的对象

                   Database database = (Database)selectNode.getUserObject();

                   validateConnect(selectNode, database);

                   //创建节点

                   buildDatabaseChild(database, selectNode);

         }

点击数据库节点与点击连接节点的实现类似,都是先进行验证连接,验证都是调用ConnectionNode的connect方法进行,而这个方法都由ServerConnection和Database分别进行实现。验证了连接后,再进行创建节点,数据库节点的子节点只有三个:表、视图和存储过程,以上代码的黑体部分创建这三个子节点。以下是Database对父类的connect方法的实现。

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java

         //创建本类的连接对象

         public Connection connect() {

                   //如果已经连接, 则返回

                   if (super.connection != null) return super.connection;

                   //创建数据库连接

                   super.connection = this.serverConnection.createConnection(this.databaseName);

                   return super.connection;

         }

我们在2.2中创建Database对象时,为该对象指定了一个构造器,构造Database对象必须要一个ServerConnection对象,表明一个Database所属的服务器连接,因为我们可以直接使用ServerConnection的createConnection方法去创建Connection连接,createConnection方法在4.2中已经实现。

5.2 新建数据库

新建一个数据库,使用JDBC执行CREATE DATABASE即可实现,当用户选择了一个连接节点的时候,就可以选择弹出的右键菜单来创建数据库,如图11所示,接下来显示数据库创建界面,该界面只有一个JTextField,给用户去输入数据库名称,在本章对应的是DatabaseFrame类。为Database对象新建一个create方法,该方法用于创建数据库。

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java

         //创建数据库

         public void create() {

                   Statement stmt = this.serverConnection.getStatement();

                   stmt.execute("create database " + this.databaseName);

         }

为DatabaseFrame的确定按钮加入监听器并调用Database的create方法即可,需要注意的是,显示DatabaseFrame的时候,需要将当前选择的连接节点对象(ServerConnection)也传递到DatabaseFrame中。创建了数据库后,以Database对象来创建一个树节点,添加到相应的连接节点下。

5.3 删除数据库

删除数据库,只需要使用JDBC执行DROP DATABASE语句即可实现,以下是删除数据库的实现。

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java

         //删除一个数据库

         public void remove() {

                   Statement stmt = this.serverConnection.getStatement();

                   stmt.execute("drop database " + this.databaseName);

         }

删除数据库后,还需要将该节点从树上删除。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //删除一个数据库

         private void removeDatabase() {

                   //得到选择中的节点

                   DefaultMutableTreeNode selectNode = getSelectNode();

                   Database db = (Database)selectNode.getUserObject();

                   db.remove();

                   this.treeModel.removeNodeFromParent(selectNode);

         }

5.4 关闭数据库

与4.5中关闭连接一样,都是将本类中的connection属性设置为null,再将子节点全部删除。以下是关闭数据库的实现。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //关闭数据库连接

         private void closeDatabase() {

                   DefaultMutableTreeNode selectNode = getSelectNode();

                   Database db = (Database)selectNode.getUserObject();

                   db.setConnection(null);

                   //删除所有的子节点

                   removeNodeChildren(selectNode);

                   //设置树不选中

                   this.tree.setSelectionPath(null);

         }

以上代码的黑体部分已经在4.5中实现。到此,数据库的相关管理功能已经实现,数据库的功能相对比较简单,只需要使用CREATE DATABASE和DROP DATABASE即可实现,如果需要修改数据库,可以使用ALTER DATABASE实现。

6 视图管理

视图管理主要包括读取视图、新建视图、修改视图和查询视图。当用户选择了某个数据库,并点工具栏的视图菜单或者点击视图节点,就可以查询全部的视图,再选择某个具体的视图,点击鼠相当规模右键,就弹出相关的右键菜单,具体的菜单在2.10中已经提供(图14)。

6.1 读取视图列表

用户选择了某个数据库节点后,就可以打开这个数据库的连接,再点击这个数据库节点下面的视图节点,就可以查询这个数据库中所有的视图。在4.2中,当点击了树中的某个节点时,我们就会执行一个viewTreeDatas方法,该方法在5.1中也实现了数据库节点的点击,现在再为该方法加入点击视图节点的实现。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //点击树节点的操作

         public void viewTreeDatas() {

                   //获得选中的节点

                   DefaultMutableTreeNode selectNode = getSelectNode();

                   if (selectNode == null) return;

                   //清空列表数据

                   this.dataList.setListData(this.emptyData);

                   //判断点击节点的类型

                   if (selectNode.getUserObject() instanceof ServerConnection) {

                            clickServerNode(selectNode);//服务器连接节点, 在4.2中实现

                   } else if (selectNode.getUserObject() instanceof Database) {

                            clickDatabaseNode(selectNode);//数据库连接节点, 在5.1中实现

                   } else if (selectNode.getUserObject() instanceof ViewNode) {

                            Database db = getDatabase(selectNode);

                            clickViewNode(db);//视图节点

                   }

         }

在本章中,数据列表由一个JList实现,由于点击了视图节点会在JList中显示视图数据,因此我们需要将JList中原有的数据清空(以上代码的黑体部分)。当用户选择的是一个视图节点,那么就会执行clickViewNode方法(该方法在下面实现),该方法主要去读取数据库中的所有视图,再将数据放入JList中,我们将读取数据库视图的方法写在Database中。要查询所有的视图,我们需要到MySQL内置的数据库information_schema中的VIEWS表查询,该表保存了MySQL中所有的视图,因此查询的时候,我们需要加入数据库名称作为查询条件。

Database中查询视图代码。

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java

         //返回数据为中所有的视图

         private ResultSet getViewsResultSet() throws Exception {

                   Statement stmt = getStatement();

                   //到information_schema数据库中的VIEWS表查询

                   String sql = "SELECT * FROM information_schema.VIEWS sc WHERE " +

                                     "sc.TABLE_SCHEMA='" + this.databaseName + "'";

                   ResultSet rs = stmt.executeQuery(sql);

                   return rs;

         }

以上代码执行一句查询的SQL并返回ResultSet对象,但是这样并不满足要求,我们需要将ResultSet对象转换成界面显示的数据格式。在列表中,我们使用一个ViewData代表一个视图,ViewData对象在2.2中已经创建,该对象包含一个database和一个content(String类型)属性,database属性代表这个ViewData对象所属的数据库,content属性表示这个视图的内容,以下代码将ResultSet对象封装成ViewData集合。

代码清单:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java

         //返回这个数据库里的所有视图

         public List<ViewData> getViews() {

                   List<ViewData> result = new ArrayList<ViewData>();

                   ResultSet rs = getViewsResultSet();

                   while (rs.next()) {

                            //得到视图的定义内容

                            String content = rs.getString("VIEW_DEFINITION");

                            ViewData td = new ViewData(this, content);

                            //得到视图名称

                            td.setName(rs.getString(TABLE_NAME));

                            result.add(td);

                   }

                   rs.close();

                   return result;

         }

得到了视图对象的集合后,我们就可以实现点击视图节点的方法(本小节前面的clickViewNode方法),将得到的视图集合放入JList中,并创建相应的右键菜单(图14)。

代码清单:codemysql-managersrcorgcrazyitmysqluiMainFrame.java

         //点击视图节点,查找全部的视图

         private void clickViewNode(Database db) {

                   List<ViewData> datas = db.getViews();

                   this.dataList.setListData(datas.toArray());

                   //显示视图后,创建右键菜单

                   createViewMenu();

                   //设置当前显示的数据类型为视图

                   this.currentView = new ViewData(db, null);

         }

注意最后还需要将当前的ViewObject设置为视图对象,用于标识当前所浏览的数据类型。实现效果如图16所示。

图16 视图列表

6.2 新建视图

创建视图使用JDBC执行CREATE VIEW语句即可实现,视图界面只提供一个保存功能,由于我们创建视图与修改视图都是使用同一个界面,因此在执行保存的时候,就需要判断新增还是修改。当得到用户在视图界面输入的视图定义后,就可以执行CREATE VIEW语句进行创建视图。为ViewData对象加入一个创建视图的方法。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistViewData.java

         //创建视图

         public void createView() {

                   //拼装CREATE VIEW语句

                   String sql = MySQLUtil.CREATE_VIEW + name + " " +

                   MySQLUtil.AS + " " + content;

                   database.getStatement().execute(sql);

         }

用户点击保存,弹出另外一个窗口让用户输入视图名称,最后调用上面的createView方法,即可以创建视图,

6.3 修改视图与删除视图

与创建视图一样,使用同样的界面,只是执行不同的SQL语句,修改视图可以使用ALTER VIEW即可,为ViewData对象加入修改视图的方法。当修改完视图后,调用修改方法即可。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistViewData.java

         //修改视图

         public void alterView() {

                   String sql = MySQLUtil.ALTER_VIEW + name + " " + MySQLUtil.AS

                            + " " + content;

                   database.getStatement().execute(sql);

         }

同样地,删除视图使用JDBC执行DROP VIEW即可实现。

ViewData:

         //删除视图

         public void dropView() {

                   String sql = MySQLUtil.DROP_VIEW + this.name;

                   database.getStatement().execute(sql);

         }

到此,查询全部视图、创建视图、修改视图和删除视图功能已经实现,但是,还缺少一个最重要的功能,就是查看视图。当我们选择了某个视图进行双击操作的时候,就需要浏览该视图的数据,这一个功能我们将在下面的章节中实现。

7 存储过程与函数管理

存储过程与函数管理使用相同的界面,因此我们可以一起实现,它们的区别在于是否有返回值,通过一些界面判断即可实现。与视图管理一样,都是有新增、修改和删除功能。

7.1 新增存储过程和函数

存储过程和函数的界面已经在2.7中创建,得到存储过程或者函数的定义、参数、返回值(函数)与名称后,就可以使用命令去创建存储过程或者函数。创建存储过程使用CREATE PROCEDURE,创建函数使用CREATE FUNCTION。在本章中,视图数据使用的是一个ViewData对象(6章节),在2.2中也创建了一个ProcedureData对象来表示一个存储过程的数据对象,因此将创建存储过程或者函数加入到该类中即可。

ProcedureData创建存储过程方法。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistProcedureData.java

         //创建存储过程

         public void createProcedure() {

                   String sql = MySQLUtil.CREATE_PROCEDURE + this.name +

                   " (" + this.arg + ") " + this.content;

                   this.database.getStatement().execute(sql);

         }

ProcedureData中创建函数方法。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistProcedureData.java

         //创建函数

         public void createFunction() {

                   String sql = MySQLUtil.CREATE_FUNCTION + this.name +

                   " (" + this.arg + ") returns " + this.returnString + " " + this.content;

                   this.database.getStatement().execute(sql);

         }

存储过程对象(ProcedureData)与视图对象(ViewData)一样,都是属于某个数据库的,因此这两个对象都会保存一个数据库的属性,直接就可以通过数据库对象(Database)的getStatement方法得到Statement对象,再执行SQL语句。

7.2修改存储过程与函数

修改存储过程(函数)与新增存储过程(函数)使用的是相同的界面,因此在保存的时候需要作出判断。与修改视图不同的是,修改存储过程或者函数,不使用ALTER PROCEDURE(ALTER FUNCTION)来实现,这是由于MySQL中的ALTER PROCEDURE和ALTER FUNCTION并不能修改存储过程或者函数的方法体与参数,因此,实现时需要将原来的存储过程或者函数先删除,再重新创建。

ProcedureData中修改存储过程。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistProcedureData.java

         //修改存储过程

         public void updateProcedure() {

                   //修改存储过程需要先把原来的先删除

                   //删除语句

                   String dropSQL = MySQLUtil.DROP_PROCEDURE + this.name;

                   this.database.getStatement().execute(dropSQL);

                   //创建语句

                   String createSQL = MySQLUtil.CREATE_PROCEDURE + this.name +

                   " (" + this.arg + ") " + this.content;

                   this.database.getStatement().execute(createSQL);

         }

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistProcedureData.java

         //修改函数

         public void updateFunction() {

                   //修改需要先把原来的先删除

                  String dropSQL = MySQLUtil.DROP_FUNCTION + this.name;

                   this.database.getStatement().execute(dropSQL);

                   String createSQL = MySQLUtil.CREATE_FUNCTION + this.name +

                   " (" + this.arg + ") returns " + this.returnString + " " + this.content;

                   this.database.getStatement().execute(createSQL);

         }

以上两个方法就可以修改存储过程和函数,但是如果一旦存储过程或者函数编写有误,那么就会将原来的存储过程或者函数删除,为了解决这个问题,可以将原来的存储过程改名,再创建一个修改后的存储过程,如果创建失败,就将改名后的旧的存储过程改回来,这样就可以确保错误发生后无法恢复原来的存储过程。修改存储过程或者函数的名称使用ALTER PROCEDURE或者ALTER FUNCTION即可实现。

在修改存储过程与函数的时候,我们就使用了DROP PROCEDURE和DROP FUNCTION来删除一个存储过程和函数,删除存储过程和函数不再详细描述。存储过程或者函数的调用可以使用CALL来调用,在实现了SQL查询功能后,就可以执行一句CALL的SQL来调用查看效果。

8 表管理

表管理在本章中相对较难,我们需要从界面中得到创建表的信息,例如字段信息、外键字段信息等。在修改表的时候,用户在界面的表格中会进行各种操作,操作完后进行保存,就需要收集这些被操作过的数据,再进表的修改。

在2.5中,我们已经创建的表管理界面(如2.5中的图7所示),对应的是TableFrame类,界面中存的有两个列表对象,分别是字段列表和外键字段列表,对应的类是FieldTable与ForeignTable,它们都继承于JTable。字段列表有以下操作:

      新字段:向字段列表的尾部追加一个新的字段行。

      插入字段:在所选择的行前面插入一个字段行。

      删除字段:删除所选的行。

      设置默认值:在文本框中输入该字段的默认值。

      设置自动增长:设置字段是否可以自动增长。

外键字段有以下操作:

      新外键:向表外尾部追加一个新的外键。

      删除外键:删除一个选择的外键。

8.1 新字段

为了能体现一个字段,我们新建一个字段对象Field,该对象保存一个字段的所有信息,包括名称,长度等一系列的字段信息。

代码清单:codemysql-managersrcorgcrazyitmysql ableobjectField.java

         private String fieldName; //字段名

         private String type; //字段类型

         private boolean allowNull = true; //允许空,默认允许为空

         private boolean isPrimaryKey = false; //是否主键,是主键为true,否则为false

         private String defaultValue; //默认值

         private boolean autoIncrement = false; //是否自动增长

         private TableData table; //该字段所属的表         

         private String uuid; //标识这个字段的uuid

         //省略setter和getter方法

接下来,在TableFrame(表管理界面)中,创建一个字段集合用来保存当前界面所显示的字段,那么如果进行新建字段操作,就可以对该集合进行操作了。下面实现刷新字段列表的方法,由于在加入新字段、修改字段或者删除字段后,都需要将列表进行一次刷新。

代码清单:codemysql-managersrcorgcrazyitmysqluiTableFrame.java

         //刷新字段列表

         public void refreshFieldTable() {

                   DefaultTableModel tableModel = (DefaultTableModel)this.fieldTable.getModel();

                   //设置数据

                   tableModel.setDataVector(getFieldDatas(), this.fieldTable.getFieldTableColumn());

                   //设置列表样式,包括行高、列宽等

                   this.fieldTable.setTableFace();

         }

         //得到字段列表数据

         public Vector getFieldDatas() {

                   Vector datas = new Vector();

                   for (int i = 0; i < this.fields.size(); i++) {

                            Field field = this.fields.get(i);

                            Vector data = new Vector();

                            data.add(field.getFieldName());//字段名称

                            data.add(field.getType());//字段类型

                            data.add(getNullIcon(field));//获得是否允许空的图片

                            data.add(getPrimaryKeyIcon(field));//获得主键图片

                            datas.add(data);

                   }

                   return datas;

         }

以上代码中的黑体部分,就是当前界面中的字段集合。在新建一个字段后,就可以使用refreshFieldTable方法对字段列表进行刷新。

代码清单:codemysql-managersrcorgcrazyitmysqluiTableFrame.java

         //加入新字段

         private void newField() {

                   Field field = new Field();

                   this.fields.add(field);

                   //刷新字段列表

                   refreshFieldTable();

                   //如果是修改状态,则添加addFields集合中

                   if (this.table.getName() != null) this.addFields.add(field);

         }

以上的黑体代码,是在用户是行修改表的时候才需要,我们创建一个addFields集合,用来保存用户添加过的字段(修改的时候)。该集合的作用,我们将修改表的时候详细描述。

8.2 插入字段与删除字段

插入新字段,只需要得到用户当前所选择的行,并在该行的前面加入一个数据行即可,需要注意的是,当用户没有选择任意一行的时候,就可以调用新字段的方法,即8.1中创建新字段的方法。

代码清单:codemysql-managersrcorgcrazyitmysqluiTableFrame.java

         //插入新字段

         private void insertField() {

                   //得到选择中的行索引

                   int selectRow = this.fieldTable.getSelectedRow();

                   if (selectRow == -1) {

                            //没有选中,调用加新字段方法,加入新字段

                            newField();

                            return;

                   }

                   Field field = new Field();

                   this.fields.add(selectRow, field);

                   //刷新字段列表

                   refreshFieldTable();

                   //如果是修改状态,则添加addFields集合中

                   if (this.table.getName() != null) this.addFields.add(field);

         }

删除字段实现与插入字段一样,只需要将字段从集合中删除并刷新列表即可,另外,如果是修改表的话,就需要加入另外的操作,在下面修改表的章节中描述。

代码清单:codemysql-managersrcorgcrazyitmysqluiTableFrame.java

         //删除字段

         private void deleteField() {

                   //得到选中的行

                   int selectRow = this.fieldTable.getSelectedRow();

                  if (selectRow == -1) return;

                   //得到用户所选择的Field对象

                   Field field = this.fields.get(selectRow);

                   if (field == null) return;

                   //从字段集合中删除

                   this.fields.remove(field);

                   //刷新列表

                   refreshFieldTable();

         }

8.3 编辑字段

当用户在字段列表中对字段的信息进行修改时,就需要得到相应的Field对象,并设置新的信息。当用户对列表停止编辑的时候,就可以触发相应的方法。这里需要注意的是,当停止编辑的时候,需要修改对应的Field对象,只需要修改该对象的字段名称与字段类型,因为这两个属性才可以输入,其他两个属性(是否允许空和主键)进行选择才会发生值的改变。

代码清单:codemysql-managersrcorgcrazyitmysqlui ableFieldTable.java

         //重写JTable的方法, 列表停止编辑的时候触发该方法

         public void editingStopped(ChangeEvent e) {

                   int column = this.getEditingColumn();

                   int row = this.getEditingRow();

                   super.editingStopped(e);

                   //获得当前编辑的列名

                   DefaultTableModel model = (DefaultTableModel)this.getModel();

                   String columnName = model.getColumnName(column);

                   //得到编辑后的单元格的值

                  String value = (String)this.getValueAt(row, column);

                   if (columnName.equals(FIELD_NAME)) {

                            //更改字段名称

                            this.tableFrame.changeFieldName(row, value);

                   } else if (columnName.equals(FIELD_TYPE)) {

                            //更改字段类型

                            this.tableFrame.changeFieldType(row, value);

                   }

         }

在列表停止编辑的时候,得到用户所编辑的单元格的行索引和编辑后的值,再调用TableFrame的方法进行修改字段名和字段类型。

代码清单:codemysql-managersrcorgcrazyitmysqluiTableFrame.java

         //字段列表的字段名称,同步去修改字段集合中的字段名称值

         public void changeFieldName(int row, String value) {

                   //得到相应的Field对象

                   Field field = this.fields.get(row);

                   if (field == null) return;

                   field.setFieldName(value);

         }

         //字段列表的字段类型,同步去修改字段集合中的字段类型值

         public void changeFieldType(int row, String value) {

                   //得到相应的Field对象

                   Field field = this.fields.get(row);

                   if (field == null) return;

                   field.setType(value);

         }

那么用户对列表进行修改后,就可以同步的去修改TableFrame中的相应对象的字段名和字段类型。另外,如果用户点击了“允许空”和“主键”列,就需要同步去修改集合中的Field对象。修改FieldTable的selectCell方法,该方法在2.5中已经实现了部分,当用户选择了列表的时候,就会触发该方法。

代码清单:codemysql-managersrcorgcrazyitmysqlui ableFieldTable.java

         //鼠标在JTable中点击的时候触发该方法

         private void selectCell() {

                   int column = this.getSelectedColumn();

                   int row = this.getSelectedRow();

                   if (column == -1 || row == -1) return;

                   //修改图片列

                   selectAllowNullColumn(row, column);

                   selectPrimaryKeyColumn(row, column);

                   //设置点击后会改变的值,注意是点击,并不是输入,因此只会更改允许空和主键

                   changeClickValue(row, column);

         }

以上的黑体代码为新加的代码。其中的changeClickValue为改变“允许空”与“主键”这两列的值,当用户点击了这两列的时候,就需要同步修改TableFrame的fields集合。

代码清单:codemysql-managersrcorgcrazyitmysqlui ableFieldTable.java

         //当发生鼠标点击单元格事件的时候,改变值,一般只改变允许空和主键列

         private void changeClickValue(int row, int column) {

                   //得到主键列

                   TableColumn primaryColumn = this.getColumn(PRIMARY_KEY);

                   if (primaryColumn.getModelIndex() == column) {

                            this.tableFrame.changePrimaryKeyValue(row);

                   }

                   //得到允许空列

                   TableColumn allowNullColumn = this.getColumn(ALLOW_NULL);

                   if (allowNullColumn.getModelIndex() == column) {

                            this.tableFrame.changeAllowNullValue(row);

                   }

         }

判断用户所点击的单元格所属的列,如果是“允许空”或者“主键”列,就可以调用TableFrame中的方法去修对应Field对象的属性值。

8.4 设置默认值与自动增长

界面中提供了一个文本框,可以设置某个字段的默认值,提供了一个多选框,可以设置字段是否可以自动增长。我们可以为文本框加入按键事件,当用户在文本框中进行输入时,就可以改变该字段的默认值。

代码清单:codemysql-managersrcorgcrazyitmysqluiTableFrame.java

         //改变字段的默认值

         public void changeDefaultValue() {

                   //得到选中的行

                   int selectRow = this.fieldTable.getSelectedRow();

                   if (selectRow == -1) return;

                   //取得默认值

                   String defaultValue = this.defaultField.getText();

                   //取得当前编辑的Field对象

                   Field field = this.fields.get(selectRow);

                   //设置字段默认值

                   field.setDefaultValue(defaultValue);

         }

同样地,与设置默认值一样,也可以为多选框加入监听器,如果发生点击事件时,就执行某个方法。

代码清单:codemysql-managersrcorgcrazyitmysqluiTableFrame.java

         //点击自动增长checkBox的方法

         private void clickIsAutoIncrementBox() {

                   //得到字段列表中所选中的行索引

                   int row = this.fieldTable.getSelectedRow();

                   if (row == -1) return;

                   //得到当前所选择了Field对象

                   Field field = this.fields.get(row);

                   //设置Field对象中的自动增长属性

                   if (this.isAutoIncrementBox.isSelected()) field.setAutoIncrement(true);

                   else field.setAutoIncrement(false);

         }

那么用户在设置默认值或者自动增长的时候,就可以同步将默认值与自动增长的标识保存到TableFrame的fields集合中。但是,还需要去修改点击字段列表的方法,将默认值与自动增长的值展现到界面的元素中。修改FieldTable的selectCell方法,列表被点击时,会触发该方法。

代码清单:codemysql-managersrcorgcrazyitmysqlui ableFieldTable.java

         //鼠标在JTable中点击的时候触发该方法

         private void selectCell() {

                   //省略其他代码

                   //修改默认值

                   this.tableFrame.setDefaultValue(row);

                   //修改是否自动增长的checkbox

                   this.tableFrame.setIsAutoIncrement(row);

         }

点击了列表某行的时候,就可以得到相应的Field对象,再设置界面的文本框和多选框即可。

8.5 新外键

与实现新字段一样,新建一个FoeignField对象来代表一个外键,并在TableFrame中创建一个集合来保存当前界面中的外键。

代码清单:codemysql-managersrcorgcrazyitmysql ableobjectForeignField.java

         private String constraintName; //约束的名称

         private Field field; //被约束的字段,根据该字段可以找出该外键对象所属于的表

         private Field referenceField; //外键的字段,可以根据此属性找出该关系中的外键表

         private String onDelete; //级联删除策略

         private String onUpdate; //级联更新策略

         private String referenceTableName; //约束表的名称

         private String referenceFieldName; //约束字段的名称

         private String uuid; //字段的uuid

         //省略setter和getter方法

在2.5中已经创建了外键列表对象(ForeignTable),新建刷新外键列表的方法。

代码清单:codemysql-managersrcorgcrazyitmysqluiTableFrame.java

         //刷新外键字段列表

         public void refreshForeignFieldTable() {

                   //设置外键数据

                   DefaultTableModel tableModel = (DefaultTableModel)this.foreignTable.getModel();

                   tableModel.setDataVector(getForeignDatas(), this.foreignTable.getForeignColumns());

                   //设置外键列表的样式

                   this.foreignTable.setTableFace();

         }

加入一个外键的时候,就可以调用refreshForeignFieldTable方法刷新外键列表。

代码清单:codemysql-managersrcorgcrazyitmysqluiTableFrame.java

         //新增一个外键字段

         private void newForeignField() {

                   ForeignField foreignField = new ForeignField();

                   this.foreignFields.add(foreignField);

                   //设置该外键的constraintName,用UUID设置

                   foreignField.setConstraintName(UUID.randomUUID().toString());

                   //刷新外键列表

                   refreshForeignFieldTable();

                   //如果是修改状态,加到添加的外键集合中

                   if (this.table.getName() != null) this.addForeignFields.add(foreignField);

         }

以上的黑体代码,与8.1中新字段一样,都是在修改表的时候需要使用到的代码,将在下面修改表的章节中加以描述。这里需要注意的是,为了标识新加的“外键”在数据库中的唯一性,因为需要将ForeignField的constraintName属性设置为唯一的。

8.6 删除一个外键

删除外键实现比较简单,只需要从TableFrame的“外键”集合中删除即可。

代码清单:codemysql-managersrcorgcrazyitmysqluiTableFrame.java

         //删除一个字段

         private void deleteForeignField() {

                   //得到选中的行

                   int selectRow = this.foreignTable.getSelectedRow();

                   if (selectRow == -1) return;

                   //得到选中的外键对象

                   ForeignField field = this.foreignFields.get(selectRow);

                   if (field == null) return;

                   //从字段集合中删除

                   this.foreignFields.remove(field);

         }

前面几个小节中,我都讲解了如何实现表管理中的一些界面操作,接下来实现具体的表管理,包括查询表信息、保存表和修改表。

8.7 查询字段信息

查询一个表的信息需要到MySQL的系统表中查询,这些信息包括字段信息与外键信息等。由于我们在TableFrame中建立了一个字段集合来保存当前界面中的字段信息,因此,只需要从数据库中查询所有的表字段并封装成一个Field集合即可。在本章中,一个表由一个TableData对象来代表,TableData中包含了一个Database对象,Database对象可以取到数据库的连接信息,可以将查询字段的方法写到TableData中。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistTableData.java

         //获得查询字段的SQL

         private String getFieldSQL() {

                   StringBuffer sql = new StringBuffer();

                   sql.append("SELECT * FROM information_schema.COLUMNS sc")

                   .append(" WHERE sc.TABLE_SCHEMA='")

                   .append(this.database.getDatabaseName() + "' ")

                   .append(" AND sc.TABLE_NAME='")

                   .append(this.name + "' ")

                   .append("ORDER BY sc.ORDINAL_POSITION");

                   return sql.toString();

         }

getFieldSQL方法是返回一句查询的SQL,到系统表中查询一个表的所有字段,使用JDBC执行getFieldSQL方法返回的SQL,就可以得到ResultSet对象,再得到各个列的值。

我们需要从查询到的ResultSet中得到以下值:

      COLUMN_NAME:字段名。

      COLUMN_TYPE:字段类型。

      IS_NULLABLE:是否允许空。

      COLUMN_KEY:如果是主键,那么该值为“PRI”。

      COLUMN_DEFAULT:该字段的默认值。

      EXTRA:如果该值为auto_increment,则表示是自动增长。

得到以上的值,就可以封装成一个字段对象,并加到结果集合中,那么TableFrame就可以根据这个集合来显示字段数据。

8.8 查询外键信息

查询外键信息与查询字段信息一样,都是到MySQL的系统表(KEY_COLUMN_USAGE)中进行查询,但是,如果使用的MySQL5.0,则不能到系统表中查询到一个外键的ON DELETE和ON UPDATE的值。如果使用的是MySQL5.1,就可以到系统表中查询到一个字段的这两个值。本章使用的MySQL版本是5.0,因此要得到ON DELETE和ON UPDATE的值,就需要得到建表时的SQL,并对该句SQL进行分析,得到外键的ON DELETE和ON UPDATE,这就是本章开头所讲的MySQL5.0与MySQL5.1的差别对我们开发这个管理器所产生的影响。

执行下面的SQL就可以返回外键字段信息的ResultSet:

SELECT * FROM information_schema.KEY_COLUMN_USAGE sc WHERE sc.TABLE_SCHEMA='数据库名' AND sc.TABLE_NAME='表名' AND sc.REFERENCED_COLUMN_NAME <> '' ORDER BY sc.COLUMN_NAME

得到的ResultSet里面包含有如下字段:

      COLUMN_NAME:外键列名。

      CONSTRAINT_NAME:约束的名称。

      REFERENCED_TABLE_NAME:约束的表名。

      REFERENCED_COLUMN_NAME:约束的字段名。

得到约束的字段名后,就可以再次到系统表(COLUMNS)中查询约束的字段。得到这些信息后,就可以创建一个ForeignField对象,但是,ForeignField中还包含有onDelete和onUpdate两个属性,为了得到这个属性,我们需要得到创建表的SQL,并对该句SQL进行分析。如果你使用的是MySQL5.1,就可以直接到系统表中查询。得到创建表的SQL,可以使用JDBC执行SHOW CREATE TABLE来实现。

以下方法分析创建表的SQL,并得到ON DELETE和ON UPDATE信息。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistTableData.java

         //返回ON DELETE或者ON UPDATE的值

         private String getOnValue(String createSQL, ForeignField foreignField,

                            String on) {

                   String constraintName = foreignField.getConstraintName();

                   //以逗号将其分隔

                   String[] temp = createSQL.split(",");

                   for (int i = 0; i < temp.length; i++) {

                            String tempString = temp[i];

                            //如果遇到外键的字符串,则进行处理

                            if (tempString.indexOf("CONSTRAINT `" + constraintName + "`") != -1) {

                                     //如果遇到ON DELETE或者ON UPDATE,则进行处理,返回ON DELETE或者ON UPDATE的值

                                     if (tempString.indexOf(on) != -1) {

                                               //得到ON DELETE或者ON UPDAT的位置

                                               int onIndex = tempString.indexOf(on) + on.length() + 1;

                                               String value = tempString.substring(onIndex, onIndex + 7);

                                               if (value.indexOf("NO ACTI") != -1) return "NO ACTION";

                                               else if (value.indexOf("RESTRIC") != -1)return "RESTRICT";

                                               else if (value.indexOf("CASCADE") != -1) return "CASCADE";

                                               else if (value.indexOf("SET NUL") != -1)return "SET NULL";

                                     }

                            }

                   }

                   return null;

         }

得到各个信息并封装成ForeignField的集合后,就可以将这个集合放到TableFrame中。TableFrame中保存了一个外键字段的集合,在8.5新外键中创建该集合。得到字段与外键字段的集合后,就可以在TableFrame中初始化列表,使用DefaultTableModel的setDataVector方法即可初始列表数据。

8.9 新建表

我们可以TableFrame(表管理界面)中得到字段的集合,集合中保存了一系列的Field对象,那么我们在创建表的时候,就可以根据这些Field对象的属性来拼装CREATE TABLE的SQL语句,然后使用JDBC执行即可。

编写拼装SQL的程序,最终得出的SQL语句如下:

CREATE TABLE IF NOT EXISTS `table3` (`field1` int(10)AUTO_INCREMENT ,`field2` varchar(10)DEFAULT 'test' ,`field3` int(10),FOREIGN KEY (`field3`) REFERENCES `table_2` (`field_3`) ON DELETE CASCADE ON UPDATE CASCADE ,PRIMARY KEY(`field1`))

具体实现在TableData中的addTable方法,拼装SQL语句的方法是TableData中的getTableSQL方法,getTableSQL方法先拼装CREATE TABLE命令,再依次拼装字段的SQL、外键字段的SQL和创建主键的SQL。

拼装字段SQL的方法。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistTableData.java

         //根据字段创建SQL

         private void createField(StringBuffer sql, List<Field> fields) {

                   for (Field f : fields) {

                            sql.append("`" + f.getFieldName() + "` ")

                            .append(f.getType());

                            //自增长加入AUTO_INCREMENT

                            if (f.isAutoIncrement()) sql.append(AUTO_INCREMENT + " ");

                            //该字段不允许为空, 加入NOT NULL

                            if (!f.isAllowNull()) sql.append(NOT_NULL + " ");

                            //该字段有默认值,并且不是自动增长

                            if (!f.isAutoIncrement()) {

                                     if (f.getDefaultValue() != null) sql.append(DEFAULT + " '" + f.getDefaultValue() + "' ");

                            }

                            sql.append(",");

                   }

         }

拼装外键字段的SQL。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistTableData.java

         //创建外键SQL

         private void createForeignFields(StringBuffer sql,

                            List<ForeignField> foreignFields) {

                   for (ForeignField f : foreignFields) {

                            sql.append(FOREIGN_KEY)

                            .append(" (`" + f.getField().getFieldName() + "`) ")

                            .append(REFERENCES)

                            .append(" `" + f.getReferenceField().getTable().getName() + "` ")

                            .append("(`" + f.getReferenceField().getFieldName() + "`) ");

                            if (f.getOnDelete() != null) {

                                     sql.append(ON_DELETE + " " + f.getOnDelete() + " ");

                            }

                            if (f.getOnUpdate() != null) {

                                     sql.append(ON_UPDATE + " " + f.getOnUpdate() + " ");

                            }

                            sql.append(",");

                   }

         }

拼装主键的SQL。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistTableData.java

         //创建主键SQL

         private void createPrimary(StringBuffer sql, List<Field> fields) {

                   for (Field f : fields) {

                            if (f.isPrimaryKey()) {

                                     sql.append(PRIMARY_KEY).append("(`" + f.getFieldName() + "`)").append(",");

                            }

                   }

         }

得到创建表的SQL后,使用JDBC执行即可。如果创建失败,可以将异常信息显示到界面中。

8.10 修改表

实现修改表的功能较为复杂,由于保存在界面中可能涉及的操作包括添加字段、修改字段、删除字段、添加外键字段、修改外键字段和删除外键字段,因此修改表的话,我们需要知道用户添加、修改、删除了哪些字段与外键字段。也就是说,用户每次进行添加、修改、删除字段与外键字段的时候,我们都需要对用户所操作的数据进行保存,最后,得到这些数据后,就可以使用ALTER TALBE来进行表的修改。在8.1新字段中,创建一个字段的时候,另外建立了一个集合来保存用户所添加的字段,因此还需要在修改和删除操作的时候,保存用户所操作的数据。

得到用户操作过的数据集合后,我们就可以为TableData加入一个修改表的方法,将这些集合里面的对象都转换成SQL,然后使用JDBC执行。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistTableData.java

         /**

          * 修改一个表

          * @param addFields 需要添加的字段

          * @param updateFields 修改的字段

          * @param dropFields 删除的字段

          * @param addFF 添加的外键

          * @param updateFF 修改的外键

          * @param dropFF 删除的外键

          */

         public void updateTable(List<Field> addFields, List<UpdateField> updateFields,

                            List<Field> dropFields, List<ForeignField> addFF,

                            List<UpdateForeignField> updateFF, List<ForeignField> dropFF) {

                   //得到添加字段的SQL

                   List<String> addFieldSQL = getAlterAddFieldSQL(addFields);

                   //得到修改字段的SQL

                   List<String> updateFieldSQL = getAlterUpdateFieldSQL(updateFields);

                   //得到删除字段的SQL

                   List<String> dropFieldSQL = getAlterDropFieldSQL(dropFields);

                   //得到添加外键的SQL

                   List<String> addFFSQL = getAlterAddForeignFieldSQL(addFF);

                   //得到修改外键的SQL

                   List<String> updateFFSQL = getAlterUpdateForeignFieldSQL(updateFF);

                   //得到删除外键的SQL

                   List<String> dropFFSQL = getAlterDropForeignFieldSQL(dropFF);

                   try {

                            Statement stmt = database.getStatement();

                            for (String s : addFieldSQL) stmt.addBatch(s);

                            for (String s : updateFieldSQL) stmt.addBatch(s);

                            for (String s : dropFieldSQL) stmt.addBatch(s);

                            for (String s : addFFSQL) stmt.addBatch(s);

                            for (String s : updateFFSQL) stmt.addBatch(s);

                            for (String s : dropFFSQL) stmt.addBatch(s);

                            stmt.executeBatch();

                   } catch (Exception e) {

                            throw new QueryException("更改表错误:" + e.getMessage());

                   }

         }

以上的代码将用户操作过的对象转换成SQL,例如,用户为表添加了若干个字段,那么就需要这样将Field对象转换成修改的SQL。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistTableData.java

         //返回全部的ALTER TABLE ADD字段语句,参数为用户添加的字段

         private List<String> getAlterAddFieldSQL(List<Field> addFields) {

                   List<String> result = new ArrayList<String>();

                   for (Field f : addFields) {

                            StringBuffer sql = new StringBuffer();

                            sql.append("ALTER TABLE " + this.name).append(" ADD " + f.getFieldName())

                            .append(" " + f.getType());

                            if (!f.isAllowNull()) sql.append(" NOT NULL");

                            if (f.getDefaultValue() != null) sql.append(" DEFAULT '" + f.getDefaultValue() + "'");

                            if (f.isAutoIncrement()) sql.append(" AUTO_INCREMENT");

                            if (f.isPrimaryKey()) sql.append(", ADD PRIMARY KEY (" + f.getFieldName() + ")");

                            result.add(sql.toString());

                   }

                   return result;

         }

使用ALTER TABLE ADD来添加字段,那么如果修改了字段,就使用ALTER TABLE CHANGE,删除字段使用ALTER TALBE DROP COLUMN。如果添加外键,需要先为约束字段加索引(ALTER TABLE ADD INDEX),再使用ALTER TABLE ADD FOREIGN KEY。如果是修改外键,就需要先将原来的外键删除,再重新建一次外键,重新建外键的方法与添加外键的顺序一致。删除外键使用ALTER TABLE DROP FOREIGN KEY。

修改表无论从界面操作到拼装SQL都比较复杂,实现起来比较麻烦。在实现的过程中可以小步快跑,慢慢地一个一个来实现。

8.11 删除表

删除表实现十分简单,只需要执行DROP TABLE即可实现,在TableData中加入一个dropTable方法。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistTableData.java

         //删除一个本类对应的表

         public void dropTable() {

                   StringBuffer sql = new StringBuffer();

                   sql.append("DROP TABLE IF EXISTS " + this.name);

                   Statement stmt = database.getStatement();

                   stmt.execute(sql.toString());

         }

表管理的大部分功能都已经实现,以上实现的这些功能中,大部分实现原理都十分简单,只是使用JDBC来执行一些SQL语句即可,但是界面的交互比较复杂,在实现的时候需要特别注意。本章中还有打开表、导出表数据的功能,将在下面的章节中描述。

9 数据浏览

在6中,我们实现了视图管理功能,在8中实现了表管理功能,在用户的实际操作中,当双击一个视图或者一个表的时候,我们需要将视图或者表对应的数据展现给用户,并在界面中显示,数据显示数据的界面已经在2.3中创建(DataFrame类),如2.3中的图4所示。该界面包括刷新、降序和升序功能。

9.1 浏览数据

可以打开数据显示界面的地方有多个,包括打开视图、打开表、执行查询,视图在本章中使用一个ViewData对象来表示,表使用一个TableData对象表示,但是这些查询都是共一个界面,因此我们新建一个QueryObject的接口,让ViewData(视图对象)和TableData去实现这个接口,该接口定义如下方法。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectQueryObject.java

public interface QueryObject {

         //得到数据

         ResultSet getDatas(String orderString);

         //得到查询的名称

         String getQueryName();

         //返回查询的SQL

         String getQuerySQL(String orderString);  

}

除了ViewData与TableData外,当执行一些SQL语句的时候,也需要将数据显示到DataFrame中,因此还需要建立一个QueryData的对象,表示一次查询所显示的数据对象,同样去实现QueryObject接口。QueryObject接口只需要实现得到数据的方法,在ViewData和TableData,只需要使用SELECT语句就可以从视图和表中得到相应的ResultSet对象。在本章中,ViewData和TableData都保存一个Database对象,因此可以直接使用JDBC去执行,但是新建的QueryData对象就不属于任何的数据库,因此需要为该类提供构造器。

代码清单:codemysql-managersrcorgcrazyitmysqlobjectlistQueryData.java

         //此次查询的SQL语句

         private String sql;

         //对应的数据库对象

         private Database database;

         public QueryData(Database database, String sql) {

                   this.sql = sql;

                   this.database = database;

         }

编写完ViewData、TableData和QueryData对接口QueryObject的方法后,就可以将数据显示到DataFrame中。

代码清单:codemysql-managersrcorgcrazyitmysqluiDataFrame.java

         public DataFrame(QueryObject queryObject) {

                   this.queryObject = queryObject;

                   //省略其他代码

         }

为DataFrame提供一个构造器,参数是接口QueryObject,那么DataFrame就可以不用关心数据来源,只需要负责数据显示就可以了,这些数据来源只封装在一个QueryObject中,有可能是表的数据,也有可能是视图的数据。DataFrame中处理数据的时候需要注意的是,JTable的列也是动态的,它的列数由ResultSet来决定的,得到一个ResultSet的列数,可以使用ResultSetMetaData对象的getColumnCount方法来得到列数。

本章使用一个DataTable对象表示一个数据列表,该对象继承JTable,在2.3中已经创建。新建一个DataColumn对象,表示一个数据列,新建一个DataCell对象,表示一个数据单元格对象。

DataColumn包括如下属性。

         //该列在JTable中的索引

         private int index;

         //该列的名称

         private String text;

DataCell包括如下属性:

         //该单元格所在的行

         private int row;

         //该单元格所在的列

         private DataColumn column;

         //该单元格的值

         private String value;

另外,需要为DataCell对象重写toString方法,返回该对象的value值。那么我们可以使用以下代码来创建列表的数据。

代码清单:codemysql-managersrcorgcrazyitmysqluiDataFrame.java

         while (this.rs.next()) {

                   DataCell[] data = new DataCell[columnCount];

                   //遍历列, 创建每一个单元格对象

                   for (int j = 0; j < columnCount; j++) {

                            //得到具体的某一列

                            DataColumn column = this.columns.get(j);

                            //创建单元格对象

                            DataCell dc = new DataCell(i, column,

                                               this.rs.getString(column.getText()));

                            data[j] = dc;

                   }

                   datas.add(data);

         }

遍历一个ResultSet对象,遍历所有的列,最后构造一个DataCell对象,并存放到结果的集合中,注意结果集合是一个List对象,List对象里面的元素是DataCell数组。最后在设置列表数据时候,可以DefaultTableModel的setDataVector方法将数据与列的信息设置到列表中。这里需要注意的是,不管以什么方式进入数据显示界面,都需要重新创建一个DataFrame对象。

9.2 刷新数据

在DataFrame中,已经保存有一个QueryObject的对象,如果需要对界面中的数据进行刷新,只需要重新读取一次数据,即执行QueryObject的getDatas方法,再重新将数据设置到列表中即可。

代码清单:codemysql-managersrcorgcrazyitmysqluiDataFrame.java

         //刷新数据

         private void refresh() {

                   //更新数据

                   this.rs = this.queryObject.getDatas(this.orderString);

                   //得到全部列

                   this.columns = getColumns(this.rs);

                   //设置数据到列表中

                   this.model.setDataVector(getDatas(), this.columns.toArray());

                   //设置每一列的宽

                   setTableColumn(this.table);

         }

除了刷新数据外,还有降序与升序的功能,当用户对数据进行了降序或者升序操作时,就可以调用refresh方法对列表进行刷新。

9.3 数据排序

我们在2.3中,已经实现了DataTable的整列选择(具体请看2.3章节),实现整列选择的时候,DataTable中保存一个selectColumn的值,表示用户当前所选择的列索引,当用户选择了整列的时候,selectColumn就等于具体的某列索引,因此就可以得到这一列的名称(数据库的字段名),然后再使用SQL中的ORDER BY语句对数据进行排序。

代码清单:codemysql-managersrcorgcrazyitmysqlui ableDataTable.java

         //返回列的名称

         private String getSelectColumnIdentifier() {

                   //得到选中列索引

                   int selectIndex = this.table.getSelectColumn();

                   if (selectIndex == -1) return null;

                   DefaultTableColumnModel colModel = (DefaultTableColumnModel)this.table.getColumnModel();

                   return (String)colModel.getColumn(selectIndex).getIdentifier();

         }

         //降序

         private void desc() {

                   //得到字段名称

                   String column = getSelectColumnIdentifier();

                   //没有选中整列, 不排序

                   if (column == null) return;

                   this.orderString = column + " " + MySQLUtil.DESC;

                   //刷新列表

                   refresh();

         }

以上代码实现降序。在接口QueryObject的getDatas方法中,需要提供参数orderString,即排序语句,因此只需要重新执行QueryObject的getDatas方法即可得到排序后的数据。升序的实现与降序一致,只是使用SQL的ORDER BY ASC即可实现。

10 执行SQL

执行SQL的界面已经在2.8中实现,只是一个简单的文本域让用户输入SQL,然后提供一个执行SQL与保存SQL的功能,由于我们已经实现了数据浏览的显示,因此实现起来十分简单。执行SQL的界面在本章中为QueryFrame类。

10.1 运行SQL

在2.8中,用户只需要输入相关的SQL语句,就可以执行该SQL语句,如2.8中的图l0所示。如果用户输入的是INSERT、UPDATE等语句,那么就可以将执行结果直接使用普通的提示框显示出来,如果用户输入的是SELECT(查询语句)、CALL(调用存储过程或者函数)语句,那么就需要将查询封装成一个QueryData对象(9.1中创建),再将QueryData对象作为构造参数传递给DataFrame,QueryData是QueryObject接口的其中一个实现。

代码清单:codemysql-managersrcorgcrazyitmysqluiQueryFrame.java

         //查询

         private void query() {

                   //得到SQL

                   String sql = this.editArea.getText();

                   try {

                            //封装一个QueryData对象

                            QueryData queryData = new QueryData(this.database, sql);

                            //显示DataFrame

                            DataFrame dataFrame =new DataFrame(queryData);

                            dataFrame.setVisible(true);

                   } catch (Exception e) {

                            e.printStackTrace();

                            showMessage(e.getMessage(), "错误");

                   }

         }

以上的query方法,只有当用户输入有SELECT或者CALL关键字的时候才调用,其他情况则直接弹出执行结果的提示(成功与否)。

10.2 保存SQL

保存SQL,只是将用户输入的SQL语句保存到一份文件中即可。从界面得到用户输入的数据,然后提供一个文件选择器让用户选择,最后使用IO流将内容输入到文件就可以实现。

代码清单:codemysql-managersrcorgcrazyitmysqlutilFileUtil.java

         //创建文件并将content写到File中

         public static void writeToFile(File file, String content) {

                   //创建新文件

                   createNewFile(file);

                   //写入文件

                   FileWriter writer = new FileWriter(file);

                   writer.write(content);

                   writer.close();

         }

代码清单:codemysql-managersrcorgcrazyitmysqluiQueryFrame.java

         //写入文件

         public void writeToFile(File file) {

                   String content = this.editArea.getText();

                   //将内容写到文件中

                   FileUtil.writeToFile(file, content);

         }

执行SQL的功能已经完成,这是用户输入SQL再运行的一种操作形式,用户还有另外一种操作形式,就是通过导入文件来执行一份SQL文件,下一小节将讲解如何实现SQL文件的导入与导出。

11 SQL文件的导入与导出

SQL文件的导入与导出,包括数据库的导入与导出、表的导出。当用户选择了某个数据库的时候,就提供鼠标右键菜单,让用户可以执行数据库的导出与导入操作。当用户选择了某个表的时候,就可以提供鼠标右键菜单,提供导出功能。右键菜单已经在2.9与2.10中实现。

11.1 执行SQL文件

进入管理器的第一个界面,就需要用户选择MySQL的安装目录,根据用户所选择的目录,我们就可以找到MySQL安装目录下面的bin目录,然后找到mysqldump与mysql这两个命令。mysqldump命令主要用于导出SQL文件,mysql命令主要用于执行SQL文件。

在3小节中,我们实现了用户选择MySQL安装目录的功能,并且将用户选择的目录存放到一个GlobalContext的对象中,那么如果需要使用mysql命令执行SQL文件,直接拼装命令执行即可。至于执行的方式,将在下面讲解。

例如MySQL中存在一个数据库,如果需要执行某份SQL文件,就需要执行以下语句:

"MySQL安装目录inmysql" –u用户名 –p密码 –h连接IP –D数据库名称 < "SQL文件绝对路径"

使用mysql命令执行SQL文件,mysql命令有许多的参数,以上语使用了-u、-p、-h和-D参数,-u参数表示数据库的用户名,-p表示密码,-h表示连接的服务器IP,-D表示需要执行文件的数据库,拼装好以上的语句后,可以使用Runtime类的exec方法执行。

注意:这里需要特别说明的是,如果MySQL安装目录有空格,或者SQL文件的绝对路径有空格,首先需要将mysql命令(包括安装目录)使用双引号括起来,再将SQL文件的绝对路径使用双引号括起来,但是直接使用Runtime的exec执行仍然会出错,我们可以将拼装的那句命令,使用IO流写入到一份.bat文件中,然后使用Runtime的exec方法执行:“cmd /c bat文件所在”可消除错误。当然,这是在Windows操作系统下,如果使用Linux的话,可以生成sh文件。

11.2 导出数据库与表

导出数据库与执行SQL文件一样,使用mysqldump命令即可实现。mysqldump语句格式如下:

"MySQL安装目录inmysqldump" -u用户名 -p密码 -h连接IP --force --databases 数据库名 > "导出的SQL保存目录"

以上使用了mysqldump集合的-u、-p、-h、--force和—databases参数,-u、-p和-h分别代表用户名、密码和数据库服务器IP,--force参数表示发生错误将继续执行,--database参数表示需要导出的数据库名称。导出SQL文件与执行SQL文件一样,都是将拼装的命令使用IO流写到一份bat或者sh文件中,再使用Runtime的exec方法执行。

导出表与导出数据库实现一致,只需要在导出数据库的参数的基础上,再加上—tables参数来声明需要导出的表即可。多个表之间使用空格将表名分隔。导出表使用的mysqldump语句格式如下:

"MySQL安装目录inmysqldump" -u用户名 -p密码 -h连接IP --databases 数据库名称 --tables 表一 表N  > "导出的SQL文件保存目录"

在本章中,处理SQL文件的导入与导出由BackupHandler接口完成,该接口有一个BackupHandlerImpl的实现类,已经对SQL文件的导出和导出进行实现,这些实现只是拼装一些语句,真正执行这些语句由CommandUtil中的executeCommand方法执行,该方法提供了Windows下的实现(生成一份bat文件并执行、最后删除)。

12 本章小节

本章实现了一个MySQL管理器,这个管理器中有多个功能,包括数据库元素的管理、数据浏览与SQL文件的导出和导出。我们可以在管理器实现的过程中,了解MySQL管理器的实现原理。实现MySQL管理器功能并不困难,困难的是一些界面的交互,特别是表管理界面。本章的这个MySQL管理器与一些流行的管理器有着部分的区别,例如我们在进入管理器的时候,需要用户选择MySQL的安装目录,目的为了使用MySQL的一些内置命令,但是例如平常使用的Navicat,它并不需要知道MySQL的安装目录(或者根本不需要安装MySQL),使用程序来导出SQL文件。当用户需要导出SQL文件的时候,我们也可以使用程序来对数据库进行分析,再写到SQL文件中,这样也是一种实现途径。本章的目的并理器竞争不是与这些商业管,而是让读者了解管理器实现的原理。希望大家能在本章的基础上,开发出功能更加强大的MySQL管理器。



时评:为新型城镇化的发展找对方向
一级建造师考试《市政公用工程》精华辅导(76)
安全管理监督的基本原则
姜斌:健康城市、健康景观
陆轶辰:大尺度的现代化大楼是反人类的
倪量:公平、学习、合作和分享,始终是水石国际的文化核心词
09年一级建造师考试辅导资料:知识产权相关知识
时评:城镇化不能处处建设中心城市
信息发布:名易软件http://www.myidp.net