当前位置:首页>软件介绍>MySQL管理器 查询:
     
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管理器。



汽车检测软件滤波汽车维修管理软件介绍
汽车4s管理软件怎么选如何利用汽车维修管理软件完善汽修厂前台接待规范
汽车美容管理软件功能分析汽车会员积分管理软件
汽车租赁软件汽车整车构造虚拟教学软件
汽修/汽配/汽车美容软件汽车快修服务软件
汽车4S店管理软件汽车构造仿真教学软件
MySQL个人学习笔记MySQL排序语句
MySQL入门篇MySQL缓存技术
信息发布:广州名易软件有限公司 http://www.myidp.net
  • 名易软件销售服务
  • 名易软件销售服务
  • 名易软件技术服务

  • MySQL管理器