当前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管理器。
信息发布:广州名易软件有限公司 http://www.myidp.net
|