|
當前IT行業(yè)流行的關系型數(shù)據(jù)庫有Oracle、SQL Server、DB2和MySQL等。其中MySQL是一個小型的關系型數(shù)據(jù)庫,開發(fā)者是瑞典的MySQL AB公司,于2008年6月1日被Sun公司收購。MySQL擁有體積小、速度快等優(yōu)點,被廣泛的應用在中小型企業(yè)的IT系統(tǒng)中,更重要的一點是,它是開源的。由于MySQL應用廣泛,因此涌現(xiàn)出許多MySQL的客戶端,例如MySQL Front、Navicat與MySQL自帶的MySQL Administrator等,這些都是我們平時在開發(fā)MySQL數(shù)據(jù)庫應用時十分常用的MySQL圖形管理工具。這些優(yōu)秀的工具為我們提供了十分方便的功能去管理MySQL數(shù)據(jù)庫,例如提供瀏覽數(shù)據(jù)的圖形界面、操作數(shù)據(jù)的界面、操作各個數(shù)據(jù)庫元素(表、視圖、存儲過程等)的界面,這些功能為我們帶來了極大的方便,可以在一個完全圖形化的界面進行數(shù)據(jù)庫處理。使用這些工具,你可以不懂如何編寫SQL語句,只需要通過操作圖形界面,就可以達到操作數(shù)據(jù)庫的目的。 在本章中,我們將自己開發(fā)一個簡單的MySQL管理器。在開發(fā)的過程中,讓大家了解前面所講到的那些優(yōu)秀工具的實現(xiàn)原理。在本章開頭已經(jīng)提到,這些管理工具,提供了各種的圖形界面讓我們?nèi)ミM行各種的操作,因此,開發(fā)一個MySQL管理器,除了需要編寫一些操作數(shù)據(jù)庫的SQL以外,還需要注意的是圖形界面的處理。這些管理工具,實現(xiàn)的原理并無太大差別,但是哪個工具更能得到多數(shù)使用者的青睞,更多的就是取決于這些工具給用戶帶來的使用體驗及方便性。 本章所開發(fā)的MySQL管理器是基于MySQL5.0開發(fā)的,因此如果要得到最佳的運行效果,請使用MySQL5.0。由于MySQL各個版本間都存在差別,例如筆者在開發(fā)這個管理器的時候,就遇到MySQL5.0與MySQL5.1之間的微小差別,這些差別對我們開發(fā)所產(chǎn)生的影響,將在下面的章節(jié)中詳細介紹。 1 MySQL管理器原理MySQL管理器,主要功能是讓用戶可以輕松進行各種的MySQL操作,包括連接管理、數(shù)據(jù)庫管理、表管理、視圖管理、存儲過程和函數(shù)管理,這些功能點我們都可以使用JDBC實現(xiàn),例如表管理中包括創(chuàng)建表、修改表等功能,我們可以使用JDBC直接執(zhí)行SQL語句中的CREATE TABLE和ALTER TABLE來達到目的。除了這些功能外,還需要對數(shù)據(jù)庫中的數(shù)據(jù)進行導出和導與的操作,進行這些操作,我們可以編寫程序來實現(xiàn),但是,更好辦法就是使用MySQL的命令(mysql或者mysqldump)來解決,這樣可以輕松解決數(shù)據(jù)的導出與導入,但是,前提就是使用的客戶端必須安裝MySQL數(shù)據(jù)庫,并且要告訴我們這個管理器,MySQL的具體目錄,我們可以使用程序去調(diào)用這些MySQL的命令。下面,我們就開始實現(xiàn)這些所定義的功能。 2 建立界面在編寫程序前,我們需要準備各個界面,包括連接管理界面、表管理界面、視圖管理界面、存儲過程(函數(shù))管理界面與查看數(shù)據(jù)界面等。表管理、視圖管理、存儲過程和函數(shù)管理我們可以建立一個主界面,根據(jù)不同的情況顯示不同的菜單,而連接管理我們可以使用一棵樹來進行管理,可以同時存在多個連接,這些連接下面的子節(jié)點就是該連接下面的數(shù)據(jù)庫。 2.1 MySQL安裝目錄選擇界面當進入管理器時,我們就需要讓用戶去選擇MySQL的安裝目錄,原因就是因為我們需要MySQL的內(nèi)置命令,因此需要指定MySQL的安裝目錄。圖1是安裝目錄選擇界面。 
圖1 MySQL安裝目錄選擇界面 讓用戶選擇MySQL安裝目錄十分簡單,只提供一個目錄選擇安裝以及顯示目錄路徑的JTextFeild,并且加入一個確定與取消按鈕。當用戶選擇了MySQL的安裝目錄,點擊了確定時,就顯示我們的主界面,這里需要注意的是,我們在實現(xiàn)的時候,需要判斷用戶所選擇的MySQL[安裝目錄是否正確,由于mysql與mysqldump等命令是存在于MySQL安裝目錄下的bin目錄的,因此判斷用戶所選擇的目錄是否正確,可以判斷在bin目錄下是否存在相應的命令,這些將在下面的章節(jié)中描述。MySQL安裝目錄在本章代碼中對應的是ConfigFrame類。 2.2 主界面主界面提供各種功能的入口,可以讓用戶在該界面中使用或者進入各個功能,除了需要提供這些入口外,還需要提供一棵樹,更直觀的展示當前所使用的連接,以及該連接下面所有的數(shù)據(jù)庫。主界面如圖2所示。 
圖2 主界面 主界面由一個工具欄,一棵樹以及一個JList組成,其中工具欄中包括的操作如下: 添加連接:可以讓用戶添加一個連接。 查看表:查看當前數(shù)據(jù)庫中所有的表。 查看視圖:查看當前數(shù)據(jù)庫中所有的視圖。 查看存儲過程(函數(shù)):查看數(shù)據(jù)庫中所有的存儲過程與函數(shù)。 打開執(zhí)行SQL的界面:打開一個執(zhí)行SQL語句的界面。 在主界面的左邊部分,提供一棵樹讓用戶十分直觀的看到連接的相關信息,這棵樹可以隱藏根節(jié)點,第一層節(jié)點就是連接,第二層節(jié)點就是該連接下所對應的所有的數(shù)據(jù)庫,每一個數(shù)據(jù)庫節(jié)點下面可以有三個子節(jié)點:表、視圖和存儲過程,當然,我們在平時使用其他管理工具的時候,還可以有觸發(fā)器等內(nèi)容,我們在本章的項目中不提供這些功能。 這里需要注意的是,我們更換了樹的各個節(jié)點圖片,因此需要為JTree添加一個DefaultTreeCellRenderer來設置各個節(jié)點的圖片以及文字,當然,還需要將各個節(jié)點抽象成不同的對象,新建各個視圖對象的接口ViewObject,該接口將是所有視圖對象的父類,這些視圖對象包括樹的各個節(jié)點,主界面右邊列表所顯示的各個元素等。 代碼清單:codemysql-managersrcorgcrazyitmysqlobjectViewObject.java public interface ViewObject { //返回顯示的圖片 Icon getIcon(); } 該接口只有一個getIcon方法,返回一個Icon對象,表示這些界面所對應的圖片,另外,樹上的各個節(jié)點對象,可以有兩種形式,第一種就是需要帶連接的節(jié)點,例如數(shù)據(jù)庫連接節(jié)點和數(shù)據(jù)庫節(jié)點,第二種就是不需要帶有連接的節(jié)點,因此我們可以將帶有連接的節(jié)點抽象成一個父類,讓連接節(jié)點和數(shù)據(jù)庫節(jié)點去繼承。另外,還需要提供一個connect的抽象方法,需要讓子類去實現(xiàn)。 代碼清單:codemysql-managersrcorgcrazyitmysqlobject reeConnectionNode.java public abstract class ConnectionNode implements ViewObject { //JDBC的Connection對象 protected Connection connection; //連接方法,由子類去實現(xiàn) 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方法 //實現(xiàn)接口ViewObject的方法, 根據(jù)不同的連接狀態(tài)顯示不同的圖片 public Icon getIcon() { if (super.connection == null) return ImageUtil.CONNECTION_CLOSE; else return ImageUtil.CONNECTION_OPEN; } //重寫toString方法, 返回連接名稱 public String toString() { return this.connectionName; } //實現(xiàn)父類的connect方法 public Connection connect() { } } 一個ServerConnection對象表示一個連接節(jié)點,一個連接節(jié)點當然需要包括一些連接的相關信息,包括連接名稱、MySQL用戶名、密碼、連接的IP與端口等信息。該對象實現(xiàn)了ViewObject的getIcon方法,判斷父類ConnectionNode的connection屬性是否為空來顯示不同的圖片,還需要重寫toString方法,返回連接的名稱。 代碼清單:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java public class Database extends ConnectionNode { private String databaseName; //數(shù)據(jù)庫名字 private ServerConnection serverConnection; //數(shù)據(jù)庫所屬的服務器連接 //需要使用數(shù)據(jù)庫名稱與服務器連接對象構造 public Database(String databaseName, ServerConnection serverConnection) { this.databaseName = databaseName; this.serverConnection = serverConnection; } //實現(xiàn)接口的方法, 判斷該數(shù)據(jù)庫是否連接, 再返回不同的圖片 public Icon getIcon() { if (this.connection == null) return ImageUtil.DATABASE_CLOSE; return ImageUtil.DATABASE_OPEN; } //重寫toString方法 public String toString() { return this.databaseName; } //實現(xiàn)父類的connect方法 public Connection connect() { } } Database節(jié)點對象包括數(shù)據(jù)庫的名字,另外還需要一個ServerConnection對象,由于每個數(shù)據(jù)庫都是某個連接節(jié)點下面的子節(jié)點,因此需要記錄它的父節(jié)點,當然,并不是簡單的進行記錄,還可以讓它們共享一些不會經(jīng)常創(chuàng)建的實例,例如Connection。另外,需要注意的是,無論ServerConnection或者Database對象,都需要實現(xiàn)ViewObject的getIcon方法,當連接節(jié)點或者數(shù)據(jù)庫節(jié)點被打開時,都需要改變它們的圖片,而顯示何種圖片,由getIcon方法決定。 代碼清單:codemysql-managersrcorgcrazyitmysqlobject reeTableNode.java public class TableNode implements ViewObject { private Database database; //所屬的數(shù)據(jù)庫節(jié)點 //返回表的樹節(jié)點圖片 public Icon getIcon() { return ImageUtil.TABLE_TREE_ICON; } //重寫toString方法 public String toString() { return "表"; } } 一個TableNode對象代表一個表的節(jié)點,需要提供一個Database屬性來表示這個對象是屬于哪個數(shù)據(jù)庫下面的子節(jié)點。如圖2所示,我們可以看樹中每個數(shù)據(jù)庫節(jié)點的表節(jié)點都是一致的(每個數(shù)據(jù)庫里面都有表),可以將這個表節(jié)點理解成是導航欄的某一組成部分,當用戶點擊了這個節(jié)點后,就可以在右邊的列表中顯示對應數(shù)據(jù)庫的表。 與TableNode一樣,另外再次創(chuàng)建兩個對象:ViewNode和ProcedureNode,分別代表數(shù)據(jù)庫節(jié)點下面的視圖節(jié)點和存儲過程節(jié)點,實現(xiàn)方法與TableNode類似。下面為樹節(jié)點添加一個DefaultTreeCellRenderer類,讓其得到這些節(jié)點對象,并設置相應的文字和圖片。 代碼清單: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; //獲得每個節(jié)點的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; } } 在節(jié)點處理類TreeCellRenderer類,得到每個節(jié)點的ViewObject后,就可以為節(jié)點設置文字和圖片,我們的ViewObject接口提供了getIcon方法,所以我們就可以在節(jié)點處理類中得到每個節(jié)點所對應的圖片與文字(從toString方法獲得)。 樹的相關處理就完成了,主界面的右邊是一個列表,對應的是一個JList對象,JList里面的每一個元素,都是ViewObject的實現(xiàn)類,只需要實現(xiàn)getIcon方法與重寫toString方法即可。每個列表的元素對象都可以將它們的name屬性抽象到一個父類中,各個對象去繼承它即可,在本例中,我們所涉及有三種數(shù)據(jù)類型:表、視圖和存儲過程(函數(shù)),我們需要建立三個對象,分別代表這三種數(shù)據(jù)類型。 在本章的代碼中,我們創(chuàng)建了TableData、ViewData和ProcedureData三個類分別代表表數(shù)據(jù)、視圖數(shù)據(jù)和存儲過程數(shù)據(jù),這三個對象都需要實現(xiàn)ViewObject接口,具體的實現(xiàn)與三個節(jié)點的實現(xiàn)類似,都需要實現(xiàn)getIcon方法并重寫toString。表、視圖和存儲過程都是某一個數(shù)據(jù)庫下面的元素,因此這三個數(shù)據(jù)對象都需要保存一個Database屬性,表示該數(shù)據(jù)所屬于的數(shù)據(jù)庫。與樹一樣,還需要提供一個元素處理類,來指定顯示的數(shù)據(jù)圖片。 代碼清單: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; } } 到這里,主界面的各個對象都創(chuàng)建好了,本章中對應的主界面對象是MainFrame類,可以在該類中創(chuàng)建對應的樹與列表。這里需要注意的是,當創(chuàng)建列表(JList)的時候,可以將JList設置為橫向滾動,調(diào)用以下代碼即可實現(xiàn): dataList.setLayoutOrientation(JList.VERTICAL_WRAP); //dataList是界面中的JList對象 創(chuàng)建主界面后,我們可以在創(chuàng)建樹與創(chuàng)建列表的時候加入一些模擬數(shù)據(jù)來查看效果,具體的效果如圖3所示: 
圖3 主界面效果 2.3 數(shù)據(jù)顯示界面在整個管理器中,我們需要一個數(shù)據(jù)顯示的界面,而且只有一個。打開數(shù)據(jù)顯示界面的途徑有兩種,一種是雙擊一個表查看數(shù)據(jù)的時候,另外一種就是執(zhí)行SQL的時候(執(zhí)行查詢的SQL),就會打開數(shù)據(jù)顯示界面,將用戶感興趣的數(shù)據(jù)顯示出來。由于一般會存在打開多個表或者多次執(zhí)行SQL的情況,因此我們在編寫打開數(shù)據(jù)顯示界面的代碼的時候,每次都需要去創(chuàng)建這個界面對象的實例。在本章中,界面顯示對象對應的類是DataFrame,數(shù)據(jù)顯示界面如圖4所示。 
圖4 數(shù)據(jù)顯示界面 界面比較簡單,一個工具條加一個表格即可,工具條中包括的功能有: 刷新:刷新當前界面的數(shù)據(jù)。 降序:當用戶選擇了某一列并點擊該圖標的時候,就對該列所對應的字段進行降序排序。 升序:操作與降序一樣,但是對所選字段進行升序排序。 在這個界面中,需要注意的是,這個列表對應的JTable對象并不像其他JTable一樣,擁有固定的列,由于我們不可能知道用戶將要打開的表有多少列,因此只能在用戶打開表的時候,得到該表的信息再動態(tài)的生成列與數(shù)據(jù)。除了這里之外,我們還需要為這個JTable對象進行一些額外的處理,例如我們需要讓這個JTable對象可以整列選擇,就需要自己編寫一個類去繼承JTable。 代碼清單:codemysql-managersrcorgcrazyitmysqlui ableDataTable.java //當點擊表頭時, 表示當前所選擇的列 private int selectColumn = -1; public DataTable(DefaultTableModel model) { //為表頭添加鼠標事件監(jiān)聽器 header.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { header.getTable().clearSelection(); int tableColumn = header.columnAtPoint(e.getPoint()); selectColumn = tableColumn; } }); //為JTable添加鼠標監(jiān)聽器 this.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { selectColumn = -1; updateUI(); } }); } 注意以上代碼中的類屬性selectColumn,當我們用鼠標點擊了表頭的時候,就將該值設為當前選擇的列的索引,當在JTable的其他地方點擊了鼠標時,就設置該值為-1,表示沒有選擇表頭。那么我們就需要重寫JTable的isCellSelected方法,如果selectColumn不是-1,那么就需要將用戶所選擇的列整列設為選中狀態(tài),以下是isCellSelected方法的實現(xiàn): //判斷一個單元格是否被選中, 重寫JTable的方法 public boolean isCellSelected(int row, int column) { if (this.selectColumn == column) return true; //如果列數(shù)與當前選擇的列相同,返回true return super.isCellSelected(row, column); } 另外,我們還需要提供一個返回selecColumn值的public的方法。做完這些后,可以點擊一列,看到效果如圖5所示。 
圖5 數(shù)據(jù)顯示界面選擇整列 2.4 創(chuàng)建連接界面連接是整個工具的最基礎部分,沒有連接,其他任何操作都不能進行,因此使用這個MySQL管理工具,就需要提供一個新增連接的界面,讓用戶去創(chuàng)建各個連接,界面如圖6所示。 
圖6 新建連接界面 圖6中新建連接的界面比較簡單,普通的一個表單,界面中包括的元素如下: 連接名稱:該名稱在管理器的樹中顯示,并且該名稱不可以重復。 連接IP:需要連接到的MySQL服務器IP。 端口:MySQL的端口,默認為3306。 用戶名:連接MySQL的用戶名,例如root。 密碼:連接MySQL的密碼。 測試連接:測試輸入的信息是否可以連接到MySQL服務器中,當然,如果測試不能連接,也可以添加這個連接。 確定和取消:點擊確定添加連接并關閉該窗口,點擊取消不保存連接并關閉窗口。 2.5 創(chuàng)建表界面當用戶需要創(chuàng)建一個表的時候,就需要提供一個界面讓用戶去輸入表的各種數(shù)據(jù),包括字段名稱、類型、是否允許空和主鍵等信息。創(chuàng)建表界面是本章中最為復雜的界面,用戶可以隨意的在表中進行操作,最后執(zhí)行保存,表界面如圖7所示。 
圖7 創(chuàng)建表界面 界面如圖7所示,該界面較為復雜,分成上下兩個表格,上面的表格主要處理表的字段信息,包括字段名、類型、是否允許空和主鍵,在該表格下面,有一個輸入默認值的文本框,并提供一個表示字段是否自動增長的多選框。當我們在表格中選中某行數(shù)據(jù)(字段)的時候,默認值就需要發(fā)生相應的改變,自動增長的多選框也要隨著改變。在本章中表界面對應的是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) { //判斷單元格的值類型,分別調(diào)用setIcon與setText方法 if (value instanceof Icon) this.setIcon((Icon)value); else this.setText((String)value); this.setHorizontalAlignment(CENTER); return this; } } FieldTableIconCellRenderer的實現(xiàn)十分簡單,只是判斷單格的值再進行處理。在FieldTable使用以下代碼即可實現(xiàn)顯示圖片。 this.getColumn(ALLOW_NULL).setCellRenderer(this.cellRenderer); this.getColumn(PRIMARY_KEY).setCellRenderer(this.cellRenderer); 以上代碼先得到允許空和主鍵的列后再設置單元格處理類。重新運行程序時,就可以看到效果如圖7所示,但是否需要對FieldTable加入鼠標事件處理,當點擊了允許空和主鍵的列單元格時,就需要改變它們圖片。為FieldTable加入鼠標監(jiān)聽器。 代碼清單:codemysql-managersrcorgcrazyitmysqlui ableFieldTable.java //鼠標在JTable中點擊的時候觸發(fā)該方法 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); //根據(jù)當前選中的圖片來更改允許空的圖片 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); } } } 只需要在創(chuàng)建FieldTableIconCellRenderer的時候為表格加入鼠標監(jiān)聽器,該監(jiān)聽器調(diào)用以上代碼的selectCell方法即可,selectCell方法再去調(diào)用點擊允許空和主鍵單元格的方法,即以上的selectAllowNullColumn和selectPrimaryKeyColumn方法,這兩個方法中判斷用戶所選擇的列,是否為需要進行圖片處理的列(允許空和主鍵),再對單元格的值(圖片)進行修改,就可以達到點擊單元格就顯示不同圖片的效果。另外,當我們點擊了某行數(shù)據(jù)(字段)的時候,還需要處理默認值與自動增長,我們在下面章節(jié)將會實現(xiàn)。 實現(xiàn)了字段列表后,還需要注意的是該列表下面的三個按鈕,分別是新字段、插入字段和刪除字段,新字段與插入字段的區(qū)別是,新字段在列表的最后加入一行數(shù)據(jù),插入字段在用戶所選擇的行的前面插入一行數(shù)據(jù)。 TableFrame下面的外鍵列表與字段列表不同的是,外鍵列表不需要進行圖片處理,但是每個單元格都需要使用下拉框來代替普通的文字。與字段列表一樣,新建一個ForeignTable的類來表示一個外鍵列表,外鍵列表有5列,而且每一列中的每個單元格都是下拉框,因此我們需要在ForeignTable中創(chuàng)建5個下拉框(JComboBox)以及5個單元格編輯器對象。 5個單元格編輯器對象,以下是ForeignTable的實現(xiàn)。 代碼清單:codemysql-managersrcorgcrazyitmysqlui ableForeignTable.java private DefaultCellEditor fieldNameEditor; //字段名稱編輯器對象 private DefaultCellEditor referenceTableEditor; //約束表 private DefaultCellEditor referenceFieldEditor; //約束字段 private DefaultCellEditor onDeleteEditor; //級聯(lián)刪除 private DefaultCellEditor onUpdateEditor; //級聯(lián)更新 那么在創(chuàng)建這些單元格編輯器對象的時候,就分別以各個下拉框的對象作為構造參數(shù): this.fieldNameEditor = new DefaultCellEditor(this.fieldNameComboBox); 接下來,得到相應的列,再設置編輯器對象即可: this.getColumn(FIELD_NAME).setCellEditor(this.fieldNameEditor); 做完這些工作后,外鍵列表中所有的單元格都變成可以使下拉來設定值,我們在開發(fā)界面的時候,由于缺乏真實的數(shù)據(jù),因此我們可以提供一些模擬的數(shù)據(jù)來實現(xiàn)效果,到需要實現(xiàn)的時候,就可以替換上真實的數(shù)據(jù)。新增表與修改表的界面可以共用一個界面,但是同時需要做新增與修改操作的時候,就需要做多一些額外的判斷,本章中新增表與修改表為同一個界面(TableFrame)。 2.6 視圖界面當用戶需要編寫一個視圖的時候,我們可以提供一個視圖界面。視圖界面實現(xiàn)十分簡單,只有一個JTextArea即可,并附帶有保存操作。這里需要注意的是,用戶點擊保存的時候,需要將視圖通過SQL的CREATE VIEW來創(chuàng)建,那么用戶查看視圖的時候,與查看表一樣,都是需要打開數(shù)據(jù)瀏覽界面。圖8是視圖界面。 
圖8 視圖界面 在本章中,創(chuàng)建表的界面一樣,無論新增視圖或者修改視圖,都使用相同的一個界面,對應的是ViewFrame。 2.7 存儲過程界面用戶需要新建一個存儲過程或者函數(shù)的時候,可以提供一個新建存儲過程界面讓用戶去操作。存儲界面在本章中對應的類是ProcedureFrame。存儲過程界面如圖9所示。 
圖9 存儲過程界面 界面元素說明: 輸入方法體的JTextArea:用戶可以在此輸入存儲過程或者函數(shù)的方法體。 參數(shù)JTextField:輸入存儲過程或者函數(shù)的參數(shù)。 返回值JTextField:可以輸入函數(shù)的返回值,因為函數(shù)才有返回值。如果選擇的類型為存儲過程,則該JTextField不可用。 類型下拉框:可以選擇編寫的類型,是存儲過程還是函數(shù)。 2.8 查詢界面當用戶需要執(zhí)行一些SQL的時候,可以提供一個查詢界面讓用戶去輸入,該界面提供執(zhí)行SQL與保存SQL的功能,執(zhí)行SQL的時候,如果是普通的INSERT、UPDATE或者其他無需瀏覽數(shù)據(jù)的SQL語句,則可以直接操作。如果執(zhí)行的是查詢、調(diào)用存儲過程或者函數(shù)的語句,那么就需要將結果顯示到數(shù)據(jù)界面,即2.3的界面。本章對應的查詢界面類是QueryFrame,查詢界面如圖10所示。 
圖10 查詢界面 2.9 樹節(jié)點右鍵菜單在主界面的連接樹中,當我們點擊了樹的某個節(jié)點的時候,可以提供一些右鍵菜單來執(zhí)行一些相關的操作,例如點擊了連接節(jié)點,就可以提供關閉連接、刪除連接等右鍵菜單,如果點擊了數(shù)據(jù)庫節(jié)點,就可以提供關閉數(shù)據(jù)庫或者刪除數(shù)據(jù)庫等右鍵菜單。 點擊連接節(jié)點的右鍵菜單如圖11所示。 
圖11 連接節(jié)點菜單 點擊數(shù)據(jù)庫節(jié)點的右鍵菜單如圖12所示。 
圖12 數(shù)據(jù)庫節(jié)點右鍵菜單 由于我們對連接節(jié)點或者數(shù)據(jù)庫節(jié)點進行選擇的時候,就可以打開連接或者數(shù)據(jù)庫,因此并不需要提供打開的菜單,本章中使用JPopupMenu來實現(xiàn)鼠標右鍵菜單,MainFrame中提供一個JPopupMenu對象來存放各個菜單當點擊了連接節(jié)點的時候JPopupMenu刪除所有的子菜單,再加入連接節(jié)點的菜單(JMenuItem),數(shù)據(jù)庫節(jié)點的實現(xiàn)方式與之相同。 2.10 數(shù)據(jù)列表右鍵菜單主界面中除了連接樹外,還有一個數(shù)據(jù)列表,當用戶在樹中點擊了表節(jié)點、視圖節(jié)點或者存儲過程節(jié)點的時候,數(shù)據(jù)列表中就顯示不同的數(shù)據(jù),我們可以根據(jù)當前所顯示的數(shù)據(jù)來創(chuàng)建不同的鼠標右鍵菜單。圖13是數(shù)據(jù)列表顯示表數(shù)據(jù)的時候的右鍵菜單。 
圖13 表數(shù)據(jù)菜單 表數(shù)據(jù)鼠標右鍵菜單說明: 新建表:打開創(chuàng)建表的界面,即2.5中的界面。 編輯表:修改一個表,與新建表使用同一個界面。 刪除表:刪除列表中選擇數(shù)據(jù)。 導出表:將一個表的數(shù)據(jù)導出。 視圖數(shù)據(jù)的鼠標右鍵菜單如圖14所示。 
圖14 視圖數(shù)據(jù)菜單 視圖數(shù)據(jù)鼠標右鍵菜單說明: 新建視圖:打開2.6中的視圖界面,用于創(chuàng)建視圖。 編輯視圖:修改所選擇的視圖,與新建視圖使用同一個界面。 刪除視圖:刪除所選擇的視圖。 存儲過程鼠標右鍵菜單如圖15所示。 
圖15 存儲過程數(shù)據(jù)菜單 存儲過程鼠標右鍵菜單說明: 新建存儲過程:打開2.7中的存儲過程界面,創(chuàng)建存儲過程。 編輯存儲過程:修改選擇的存儲過程,與新建存儲過程使用相同的界面。 刪除存儲過程:刪除所選擇的存儲過程或者函數(shù)。 以上為三種數(shù)據(jù)的右鍵菜單,實現(xiàn)方式與樹節(jié)點的右鍵菜單一樣,當界面的數(shù)據(jù)發(fā)生改變時,就相應的去刪除JPopupMenu所有的子菜單,再添加相應的菜單(JMenuItem)即可。 以上的菜單均在主界面(MainFrame)中創(chuàng)建,程序并不知道當前顯示的是哪種數(shù)據(jù),因此我們需要在MainFrame中提供一個ViewObject的類來標識當前顯示的類型,ViewObject是所有界面元素都需要實現(xiàn)的接口,表數(shù)據(jù)是TableData類,視圖數(shù)據(jù)是ViewData類,存儲過程數(shù)據(jù)是ProcedureData類,詳細請看2.2中的各個界面對象。當用戶點擊了工具欄或者樹上的某個節(jié)點時,就相應的改變MainFrame中的ViewObject即可。 到此,管理器的所有界面都創(chuàng)建完畢,接下來就可以實現(xiàn)相關的功能。 3 實現(xiàn)MySQL安裝目錄選擇功能實現(xiàn)MySQL安裝目錄選擇功能,我們使用2.1的界面。當用戶進入管理器的時候,就讓用戶選擇本地的MySQL安裝目錄,由于我們需要使用MySQL的一些內(nèi)置命令,因此選擇MySQL的安裝目錄是一個必要的操作,得到MySQL安裝目錄后,我們就可以找到bin目錄下面的命令。因此用戶選擇了安裝目錄后,我們的程序就需要對所選擇目錄進行驗證,判斷能否找到bin目錄。 3.1 實現(xiàn)目錄選擇選擇目錄實現(xiàn)十分簡單,只需要提供一個文件選擇器即可,而且這個文件選擇器只可以選擇目錄,當用戶選擇了對應的目錄后,就可以將其選擇的目錄顯示到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中,當點擊確定的時候,就可以進行判斷,以下代碼為點擊確定所執(zhí)行的代碼。 代碼清單: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安裝目錄選擇界面的時候,就可以調(diào)用以上的方法去獲得MySQL安裝目錄的值。 接下來實現(xiàn)保存安裝目錄的功能,在這之前,新建一個GlobalContext的類,用于保存管理器全局的一些信息,例如這里的mysql.home屬性。以下代碼實現(xiàn)保存配置的功能。 代碼清單: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文件中。這些做的話,就不需要每一次進入系統(tǒng)都去修改配置文件。 3.3 讀取連接信息在得到MySQL安裝目錄,進入主界面時,還需要得到用戶所有的連接信息,這些信息用來初始化主界面左邊的樹,管理器是針對MySQL數(shù)據(jù)庫的,但是這些連接信息可以不記錄到數(shù)據(jù)庫,與保存MySQL安裝目錄一樣,可以提供一些properties文件來保存,每一個連接作為一份properties文件。保存連接的信息我們在下面的章節(jié)中實現(xiàn),這里主要實現(xiàn)讀取的實現(xiàn)。 新建一個PropertiesHandler的接口,專門用于處理連接屬性文件。該接口提供一個讀取數(shù)據(jù)庫連接配置文件的方法,并返回ServerConnection集合,ServerConnection代表一個連接節(jié)點,并保存有一些數(shù)據(jù)庫連接的信息,詳細請看2.2中的ServerConnection類。以下代碼讀取一份properties,并返回一個Properties對象。 代碼清單:codemysql-managersrcorgcrazyitmysqlutilFileUtil.java //根據(jù)文件得到對應的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實現(xiàn)類中,就可以讀取相應目錄下的所有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; } 得到所有的連接信息后,先不需要初始化樹,需要將這些信息存放到一個對象中,因為在下面的實現(xiàn)中,這些類或者連接信息需要經(jīng)常使用到。在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的,所以就決定了在管理器中不允許出現(xiàn)重名的連接。那么在用戶選擇MySQL安裝目錄,點擊確定后,就可以將連接加入到GlobalContext中,用戶點擊確定按鈕執(zhí)行的部分代碼。 代碼清單:codemysql-managersrcorgcrazyitmysqluiConfigFrame.java //讀取全部的服務器連接配置 List<ServerConnection> conns = ctx.getPropertiesHandler().getServerConnections(); for (ServerConnection conn : conns) ctx.addConnection(conn); 到此,MySQL安裝目錄的功能已經(jīng)實現(xiàn),得到用戶的各個連接信息后,就可以根據(jù)這些連接實現(xiàn)創(chuàng)建樹的功能。 4 連接管理進入主界面后,我們需要將各個連接信息創(chuàng)建一棵樹,用戶往后的各個操作,都與這些棵樹息息相關。樹的第一層節(jié)點是管理器中的各個連接,只需要得到各個連接后,以這些連接對象創(chuàng)建第一層節(jié)點即可。本小節(jié)將實現(xiàn)連接相關的功能,這些功能包括創(chuàng)建連接節(jié)點、打開連接、刪除連接等。 4.1 創(chuàng)建連接節(jié)點進入主界面時,我們已經(jīng)可以得到GlobalContext對象,各個連接信息都保存在該對象中,因此可以根據(jù)這些連接信息來創(chuàng)建樹。在創(chuàng)建樹的時候,需要注意的是,我們只需要根據(jù)這些連接信息來創(chuàng)建第一層節(jié)點,而不需要再去創(chuàng)建下面的幾層節(jié)點,當用戶點擊第一層節(jié)點(連接節(jié)點)的時候,再去訪問該連接下面的數(shù)據(jù)庫信息,正常得到這些數(shù)據(jù)庫后,再創(chuàng)建數(shù)據(jù)庫節(jié)點。 MainFrame中創(chuàng)建連接節(jié)點的方法。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //創(chuàng)建樹中服務器連接的節(jié)點 private void createNodes(DefaultMutableTreeNode root) { Map<String, ServerConnection> conns = this.ctx.getConnections(); for (String key : conns.keySet()) { ServerConnection conn = conns.get(key); //創(chuàng)建連接節(jié)點 DefaultMutableTreeNode conntionNode = new DefaultMutableTreeNode(conn); root.add(conntionNode); } } MainFrame中創(chuàng)建樹的方法。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //創(chuàng)建樹 private void createTree() { DefaultMutableTreeNode root = new DefaultMutableTreeNode(new RootNode()); //創(chuàng)建連接節(jié)點 createNodes(root); this.treeModel = new DefaultTreeModel(root); //構造樹 JTree tree = new JTree(this.treeModel); //設置節(jié)點處理類 TreeCellRenderer cr = new TreeCellRenderer(); tree.setCellRenderer(cr); //設置監(jiān)聽器類 tree.addMouseListener(new TreeListener(this)); tree.setRootVisible(false); //添加右鍵菜單 tree.add(this.treeMenu); this.tree = tree; } 以上代碼中的TreeCellRenderer為節(jié)點的處理類,具體的實現(xiàn)請看2.2中TreeCellRenderer類的實現(xiàn)。TreeListener是一個鼠標事件監(jiān)聽器,當用戶點擊了樹的連接節(jié)點后,需要建立連接,我們在下面的章節(jié)中實現(xiàn)。 4.2 打開連接當用戶點擊連接節(jié)點后,就需要立即打開這個連接,我們使用了一個ServerConnection對象來保存連接的信息,并使用該對象來創(chuàng)建樹中的連接節(jié)點,打開連接的時候,就可以根據(jù)連接的信息去嘗試進行服務器連接,使用JDBC進行連接即可。如果成功進行連接,就馬上創(chuàng)建該連接節(jié)點的子節(jié)點(數(shù)據(jù)庫節(jié)點),如果不能成功連接,則彈出提示。ServerConnection繼承了ConnectionNode這個抽象類,ConnectionNode中保存了一個JDBC的Connection對象,ServerConnection中判斷是否連接的標準是判斷ConnectionNode的Connection對象是否為空,而且ServerConnection中需要實現(xiàn)父類(ConnectionNode)的connect方法,下面代碼是ServerConnection對ConnectionNode的connect方法的實現(xiàn)。 代碼清單:codemysql-managersrcorgcrazyitmysqlobject reeServerConnection.java //實現(xiàn)父類的方法 public Connection connect() { //Connection在本類中只有一個實例 if (super.connection != null) return super.connection; Class.forName(DRIVER); Connection conn = createConnection(""); super.connection = conn; return super.connection; } //創(chuàng)建連接, 參數(shù)是數(shù)據(jù)庫名稱 public Connection createConnection(String database) throws Exception { Class.forName(DRIVER); Connection conn = DriverManager.getConnection(getConnectUrl() + database, this.username, this.password); return conn; } 以上的代碼中先判斷ServerConnection的connection屬性是否為空,如果該屬性為空(沒有連接)則進行創(chuàng)建。注意createConnection方法,該方法聲明為public,可以讓外部去使用。下面實現(xiàn)樹的節(jié)點監(jiān)聽器,當節(jié)點被選中后,就可以執(zhí)行connect方法,但是需要注意的是,并不是每個節(jié)點都相同,只是連接節(jié)點被點擊的時候才去進行連接。 代碼清單:codemysql-managersrcorgcrazyitmysqlui reeTreeListener.java public void mousePressed(MouseEvent e) { if (e.getModifiers() == MouseEvent.BUTTON1_MASK) { //左鍵點擊,查看樹的節(jié)點,調(diào)用MainFrame的打開節(jié)點方法 this.mainFrame.viewTreeDatas(); } } 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //點擊樹節(jié)點的操作 public void viewTreeDatas() { //獲得選中的節(jié)點 DefaultMutableTreeNode selectNode = getSelectNode(); if (selectNode == null) return; //判斷點擊節(jié)點的類型 if (selectNode.getUserObject() instanceof ServerConnection) { clickServerNode(selectNode);//服務器連接節(jié)點 } } //點擊服務器節(jié)點 public void clickServerNode(DefaultMutableTreeNode selectNode) { //暫時不實現(xiàn) } 連接節(jié)點被點擊后,就會執(zhí)行clickServerNode方法,該方法需要做的是先去驗證被選中的節(jié)點是否可以進行連接,再創(chuàng)建該連接節(jié)點的子節(jié)點(數(shù)據(jù)庫節(jié)點)。要創(chuàng)建數(shù)據(jù)庫節(jié)點,就要得到該連接下面所有的數(shù)據(jù)庫,執(zhí)行MySQL的一句show databases就可以得到所有的數(shù)據(jù)庫。 代碼清單:codemysql-managersrcorgcrazyitmysqlobject reeServerConnection.java //獲得一個服務器連接下面所有的數(shù)據(jù)庫 public List<Database> getDatabases() { List<Database> result = new ArrayList<Database>(); try { //獲得一個連接下面所有的數(shù)據(jù)庫 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執(zhí)行show databases就可以得到所有的數(shù)據(jù)庫ResultSet對象,這里需要注意的是,我們需要得到Statement對象,直接使用ConnectionNode的connection屬性去創(chuàng)建Statement對象即可,并不需要再去重新創(chuàng)建JDBC的Connection對象。查詢到數(shù)據(jù)庫的ResultSet對象后,就將Database列的值封裝成一個Database對象,加入到結果集中即可。在本章中,一個Database對象代表一個數(shù)據(jù)庫節(jié)點。那么現(xiàn)在就可以實現(xiàn)clickServerNode方法,點擊了連接節(jié)點后,就會執(zhí)行該方法。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //點擊服務器節(jié)點 public void clickServerNode(DefaultMutableTreeNode selectNode) { ServerConnection server = (ServerConnection)selectNode.getUserObject(); //驗證是否可以進行連接 validateConnect(selectNode, server); //創(chuàng)建服務器子節(jié)點 buildServerChild(server, selectNode); } //創(chuàng)建數(shù)據(jù)庫一層的節(jié)點(樹的第二層) public void buildServerChild(ServerConnection server, DefaultMutableTreeNode conntionNode) { //如果有子節(jié)點,則不再創(chuàng)建 if (conntionNode.getChildCount() != 0) return; List<Database> databases = server.getDatabases(); //再創(chuàng)建連接節(jié)點下面的數(shù)據(jù)節(jié)點 for (Database database : databases) { DefaultMutableTreeNode databaseNode = new DefaultMutableTreeNode(database); //將數(shù)據(jù)庫節(jié)點加入到連接節(jié)點中 this.treeModel.insertNodeInto(databaseNode, conntionNode, conntionNode.getChildCount()); } } //判斷連接是否出錯,適用于服務器節(jié)點和數(shù)據(jù)庫節(jié)點 private void validateConnect(DefaultMutableTreeNode selectNode, ConnectionNode node) { //進行連接 node.connect(); } 打開連接的功能已經(jīng)實現(xiàn),可以運行程序查看效果??偟膩碚f,打開一個連接需要做的是:驗證連接和創(chuàng)建數(shù)據(jù)庫節(jié)點。 4.3 新建連接在2.4中,我們已經(jīng)提供了一個創(chuàng)建連接的界面,實現(xiàn)新建連接,只需要將用戶輸入的連接信息保存到一份properties文件中,再向樹中添加一個連接節(jié)點即可。我們?yōu)榻涌赑ropertiesHandler添加一個saveServerConnection的方法,PropertiesHandler是用于處理properties文件的接口,在3.3中已經(jīng)創(chuàng)建。 PropertiesHandler實現(xiàn)類對saveServerConnection的實現(xiàn)。 代碼清單:codemysql-managersrcorgcrazyitmysqlsystemPropertiesHandlerImpl.java public void saveServerConnection(ServerConnection conn) { //得到配置文件名, 這些properties文件存放于connections目錄下 String configFileName = FileUtil.CONNECTIONS_FOLDER + conn.getConnectionName() + ".properties"; //創(chuàng)建properties文件 File connConfigFile = new File(configFileName); //創(chuàng)建文件 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實現(xiàn)簡單,只需要將ServerConnection對象中的各個屬性寫到properties文件中即可。那么在ConnectionFrame(新建連接界面)中,當用戶輸入各個信息點擊確定后,就可以對這些連接進行保存,以下為點擊確定執(zhí)行的方法。 代碼清單:codemysql-managersrcorgcrazyitmysqluiConnectionFrame.java //保存連接 private void saveConnection() { //得到用戶輸入的信息并返回一個ServerConnection對象 ServerConnection conn = getDataConnectionFromView(); //判斷連接名稱是否重復 if (this.ctx.getConnection(conn.getConnectionName()) != null) { showMessage("已經(jīng)存在相同名字的連接", "錯誤"); return; } //直接保存, 不需要創(chuàng)建任何的連接, 添加到GlobalContext的連接Map中 this.ctx.addConnection(conn); //保存到屬性文件 this.ctx.getPropertiesHandler().saveServerConnection(conn); this.mainFrame.addConnection(conn); this.setVisible(false); } 注意以上代碼的黑體部分,需要進連接的名字進行判斷,先去GlobalContext的連接Map中獲取ServerConnection對象,如果能得到,則表示已經(jīng)存在相同名字的連接。保存到屬性文件后,就調(diào)用MainFrame的addConnection方法,以下是addConnection方法的實現(xiàn)。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //在添加連接界面添加了一個連接后執(zhí)行的方法, 向樹中添加一個連接 public void addConnection(ServerConnection sc) { //得到要節(jié)點 DefaultMutableTreeNode root = (DefaultMutableTreeNode)this.treeModel.getRoot(); DefaultMutableTreeNode newChild = new DefaultMutableTreeNode(sc); //向要節(jié)點添加連接節(jié)點 this.treeModel.insertNodeInto(newChild, root, root.getChildCount()); if (root.getChildCount() == 1) this.tree.updateUI(); } addConnection方法直接使用DefaultTreeModel的insertNodeInto方法向樹添加一個ServerConnection節(jié)點。運行程序并進行添加一個連接,可以看到具體的效果。除了添加連接的功能外,界面中還有一個測試連接的功能,在添加連接前,可以先測試一下服務器是否可以連接。以下是點擊測試連接按鈕觸發(fā)的方法。 代碼清單: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 刪除連接用戶選擇了一個連接需要刪除的時候,就需要提供一個刪除連接的功能,刪除連接的功能我們在右鍵菜單中提供,當用戶選擇了某個連接節(jié)點的時候,就彈出該菜單,該菜單已經(jīng)在2.9中實現(xiàn),下面實現(xiàn)刪除連接的功能。首先我們需要明白的是,刪除一個連接,就是從管理器中徹底刪除這個連接信息,再從樹中刪除這個連接節(jié)點,最后還需要從GlobalContext的連接Map中刪除該連接。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //刪除一個連接 private void removeConnection() { DefaultMutableTreeNode selectNode = getSelectNode(); ServerConnection conn = (ServerConnection)selectNode.getUserObject(); //從上下文件中刪除 this.ctx.removeConnection(conn); //從樹節(jié)點中刪除 this.treeModel.removeNodeFromParent(selectNode); } 當用戶選擇了某個連接節(jié)點的時候,選擇右鍵菜單中的刪除連接,就會觸發(fā)上面的removeConnection方法,只需要為菜單對象添加ActionListener即可。先調(diào)用GlobalContext的刪除連接方法將ServerConnection從全局上下文中刪除,再使用DefaultTreeModel將該節(jié)點從樹上刪除。以下是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后,連接節(jié)點的圖標就自然會變成關閉的圖標,因為ServerConnection中實現(xiàn)了ViewObject的getIcon方法。另外,還需要幫ServerConnection節(jié)點刪除它的全部子節(jié)點。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //刪除一個節(jié)點的所有子節(jié)點 private void removeNodeChildren(DefaultMutableTreeNode node) { //獲取節(jié)點數(shù)量 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); //刪除所有的子節(jié)點 removeNodeChildren(selectNode); //設置樹不選中 this.tree.setSelectionPath(null); } 以上代碼的removeNodeChildren方法,從樹中刪除一個節(jié)點的所有子節(jié)點,用戶選擇了某個節(jié)點再進行刪除節(jié)點后就會觸發(fā)closeConnection方法。 5 數(shù)據(jù)庫管理數(shù)據(jù)庫管理功能不多,包括打開數(shù)據(jù)庫、關閉數(shù)據(jù)庫和刪除數(shù)據(jù)庫,這三個功能與連接管理中的功能類似,例如打開連連與打開數(shù)據(jù)庫,都需要創(chuàng)建子節(jié)點,但是每個數(shù)據(jù)庫的子節(jié)點都只有三個,分別是表節(jié)點、視圖節(jié)點和存儲過程節(jié)點,而連接則是根據(jù)數(shù)據(jù)庫來創(chuàng)建子節(jié)點的。在本章中,我們使用一個Database對象來代表一個數(shù)據(jù)庫節(jié)點,具體請看2.2中的Database類。Database對象與ServerConnection對象都是繼承于ConnectionNode的,因此都需要去實現(xiàn)connect方法,并都有一個屬性自己的Connection對象。實現(xiàn)打開數(shù)據(jù)庫或者關閉數(shù)據(jù)庫功能時,都與ServerConnection的實現(xiàn)類似。 5.1 打開數(shù)據(jù)庫當數(shù)據(jù)庫節(jié)點被點擊后,就可以進行打開操作,在4.2中,當樹的某個節(jié)點被點擊后,就會調(diào)用MainFrame的viewTreeDatas方法,在4.2中,我們只判斷了用戶點擊服務器節(jié)點的情況,下面再幫該方法加入判斷數(shù)據(jù)庫節(jié)點的情況,當然,該方法還需要加入表節(jié)點、視圖節(jié)點和存儲過程節(jié)點的點擊判斷,判斷用戶點擊的是哪種類型節(jié)點,再進行處理。 MainFrame的viewTreeDatas方法。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //判斷點擊節(jié)點的類型 if (selectNode.getUserObject() instanceof ServerConnection) { clickServerNode(selectNode);//服務器連接節(jié)點,在4.2中已經(jīng)實現(xiàn) } else if (selectNode.getUserObject() instanceof Database) { clickDatabaseNode(selectNode);//數(shù)據(jù)庫連接節(jié)點 } 以上的代碼判斷了用戶點擊節(jié)點的類型,以下是上面代碼中clickDatabaseNode的實現(xiàn),點擊數(shù)據(jù)庫節(jié)點后,需要進行數(shù)據(jù)庫連接,再為數(shù)據(jù)庫節(jié)點添加三個子節(jié)點(表、視圖和存儲過程)。 MainFrame的clickDatabaseNode方法。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //創(chuàng)建數(shù)據(jù)庫節(jié)點子節(jié)點 private void buildDatabaseChild(Database database, DefaultMutableTreeNode databaseNode) { //判斷如果已經(jīng)連接,則不創(chuàng)建節(jié)點 if (databaseNode.getChildCount() != 0) return; //創(chuàng)建三個子節(jié)點(表、視圖、存儲過程) 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()); } //點擊數(shù)據(jù)庫節(jié)點 public void clickDatabaseNode(DefaultMutableTreeNode selectNode) { //獲取點擊樹節(jié)點的對象 Database database = (Database)selectNode.getUserObject(); validateConnect(selectNode, database); //創(chuàng)建節(jié)點 buildDatabaseChild(database, selectNode); } 點擊數(shù)據(jù)庫節(jié)點與點擊連接節(jié)點的實現(xiàn)類似,都是先進行驗證連接,驗證都是調(diào)用ConnectionNode的connect方法進行,而這個方法都由ServerConnection和Database分別進行實現(xiàn)。驗證了連接后,再進行創(chuàng)建節(jié)點,數(shù)據(jù)庫節(jié)點的子節(jié)點只有三個:表、視圖和存儲過程,以上代碼的黑體部分創(chuàng)建這三個子節(jié)點。以下是Database對父類的connect方法的實現(xiàn)。 代碼清單:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java //創(chuàng)建本類的連接對象 public Connection connect() { //如果已經(jīng)連接, 則返回 if (super.connection != null) return super.connection; //創(chuàng)建數(shù)據(jù)庫連接 super.connection = this.serverConnection.createConnection(this.databaseName); return super.connection; } 我們在2.2中創(chuàng)建Database對象時,為該對象指定了一個構造器,構造Database對象必須要一個ServerConnection對象,表明一個Database所屬的服務器連接,因為我們可以直接使用ServerConnection的createConnection方法去創(chuàng)建Connection連接,createConnection方法在4.2中已經(jīng)實現(xiàn)。 5.2 新建數(shù)據(jù)庫新建一個數(shù)據(jù)庫,使用JDBC執(zhí)行CREATE DATABASE即可實現(xiàn),當用戶選擇了一個連接節(jié)點的時候,就可以選擇彈出的右鍵菜單來創(chuàng)建數(shù)據(jù)庫,如圖11所示,接下來顯示數(shù)據(jù)庫創(chuàng)建界面,該界面只有一個JTextField,給用戶去輸入數(shù)據(jù)庫名稱,在本章對應的是DatabaseFrame類。為Database對象新建一個create方法,該方法用于創(chuàng)建數(shù)據(jù)庫。 代碼清單:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java //創(chuàng)建數(shù)據(jù)庫 public void create() { Statement stmt = this.serverConnection.getStatement(); stmt.execute("create database " + this.databaseName); } 為DatabaseFrame的確定按鈕加入監(jiān)聽器并調(diào)用Database的create方法即可,需要注意的是,顯示DatabaseFrame的時候,需要將當前選擇的連接節(jié)點對象(ServerConnection)也傳遞到DatabaseFrame中。創(chuàng)建了數(shù)據(jù)庫后,以Database對象來創(chuàng)建一個樹節(jié)點,添加到相應的連接節(jié)點下。 5.3 刪除數(shù)據(jù)庫刪除數(shù)據(jù)庫,只需要使用JDBC執(zhí)行DROP DATABASE語句即可實現(xiàn),以下是刪除數(shù)據(jù)庫的實現(xiàn)。 代碼清單:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java //刪除一個數(shù)據(jù)庫 public void remove() { Statement stmt = this.serverConnection.getStatement(); stmt.execute("drop database " + this.databaseName); } 刪除數(shù)據(jù)庫后,還需要將該節(jié)點從樹上刪除。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //刪除一個數(shù)據(jù)庫 private void removeDatabase() { //得到選擇中的節(jié)點 DefaultMutableTreeNode selectNode = getSelectNode(); Database db = (Database)selectNode.getUserObject(); db.remove(); this.treeModel.removeNodeFromParent(selectNode); } 5.4 關閉數(shù)據(jù)庫與4.5中關閉連接一樣,都是將本類中的connection屬性設置為null,再將子節(jié)點全部刪除。以下是關閉數(shù)據(jù)庫的實現(xiàn)。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //關閉數(shù)據(jù)庫連接 private void closeDatabase() { DefaultMutableTreeNode selectNode = getSelectNode(); Database db = (Database)selectNode.getUserObject(); db.setConnection(null); //刪除所有的子節(jié)點 removeNodeChildren(selectNode); //設置樹不選中 this.tree.setSelectionPath(null); } 以上代碼的黑體部分已經(jīng)在4.5中實現(xiàn)。到此,數(shù)據(jù)庫的相關管理功能已經(jīng)實現(xiàn),數(shù)據(jù)庫的功能相對比較簡單,只需要使用CREATE DATABASE和DROP DATABASE即可實現(xiàn),如果需要修改數(shù)據(jù)庫,可以使用ALTER DATABASE實現(xiàn)。 6 視圖管理視圖管理主要包括讀取視圖、新建視圖、修改視圖和查詢視圖。當用戶選擇了某個數(shù)據(jù)庫,并點工具欄的視圖菜單或者點擊視圖節(jié)點,就可以查詢?nèi)康囊晥D,再選擇某個具體的視圖,點擊鼠相當規(guī)模右鍵,就彈出相關的右鍵菜單,具體的菜單在2.10中已經(jīng)提供(圖14)。 6.1 讀取視圖列表用戶選擇了某個數(shù)據(jù)庫節(jié)點后,就可以打開這個數(shù)據(jù)庫的連接,再點擊這個數(shù)據(jù)庫節(jié)點下面的視圖節(jié)點,就可以查詢這個數(shù)據(jù)庫中所有的視圖。在4.2中,當點擊了樹中的某個節(jié)點時,我們就會執(zhí)行一個viewTreeDatas方法,該方法在5.1中也實現(xiàn)了數(shù)據(jù)庫節(jié)點的點擊,現(xiàn)在再為該方法加入點擊視圖節(jié)點的實現(xiàn)。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //點擊樹節(jié)點的操作 public void viewTreeDatas() { //獲得選中的節(jié)點 DefaultMutableTreeNode selectNode = getSelectNode(); if (selectNode == null) return; //清空列表數(shù)據(jù) this.dataList.setListData(this.emptyData); //判斷點擊節(jié)點的類型 if (selectNode.getUserObject() instanceof ServerConnection) { clickServerNode(selectNode);//服務器連接節(jié)點, 在4.2中實現(xiàn) } else if (selectNode.getUserObject() instanceof Database) { clickDatabaseNode(selectNode);//數(shù)據(jù)庫連接節(jié)點, 在5.1中實現(xiàn) } else if (selectNode.getUserObject() instanceof ViewNode) { Database db = getDatabase(selectNode); clickViewNode(db);//視圖節(jié)點 } } 在本章中,數(shù)據(jù)列表由一個JList實現(xiàn),由于點擊了視圖節(jié)點會在JList中顯示視圖數(shù)據(jù),因此我們需要將JList中原有的數(shù)據(jù)清空(以上代碼的黑體部分)。當用戶選擇的是一個視圖節(jié)點,那么就會執(zhí)行clickViewNode方法(該方法在下面實現(xiàn)),該方法主要去讀取數(shù)據(jù)庫中的所有視圖,再將數(shù)據(jù)放入JList中,我們將讀取數(shù)據(jù)庫視圖的方法寫在Database中。要查詢所有的視圖,我們需要到MySQL內(nèi)置的數(shù)據(jù)庫information_schema中的VIEWS表查詢,該表保存了MySQL中所有的視圖,因此查詢的時候,我們需要加入數(shù)據(jù)庫名稱作為查詢條件。 Database中查詢視圖代碼。 代碼清單:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java //返回數(shù)據(jù)為中所有的視圖 private ResultSet getViewsResultSet() throws Exception { Statement stmt = getStatement(); //到information_schema數(shù)據(jù)庫中的VIEWS表查詢 String sql = "SELECT * FROM information_schema.VIEWS sc WHERE " + "sc.TABLE_SCHEMA='" + this.databaseName + "'"; ResultSet rs = stmt.executeQuery(sql); return rs; } 以上代碼執(zhí)行一句查詢的SQL并返回ResultSet對象,但是這樣并不滿足要求,我們需要將ResultSet對象轉換成界面顯示的數(shù)據(jù)格式。在列表中,我們使用一個ViewData代表一個視圖,ViewData對象在2.2中已經(jīng)創(chuàng)建,該對象包含一個database和一個content(String類型)屬性,database屬性代表這個ViewData對象所屬的數(shù)據(jù)庫,content屬性表示這個視圖的內(nèi)容,以下代碼將ResultSet對象封裝成ViewData集合。 代碼清單:codemysql-managersrcorgcrazyitmysqlobject reeDatabase.java //返回這個數(shù)據(jù)庫里的所有視圖 public List<ViewData> getViews() { List<ViewData> result = new ArrayList<ViewData>(); ResultSet rs = getViewsResultSet(); while (rs.next()) { //得到視圖的定義內(nèi)容 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; } 得到了視圖對象的集合后,我們就可以實現(xiàn)點擊視圖節(jié)點的方法(本小節(jié)前面的clickViewNode方法),將得到的視圖集合放入JList中,并創(chuàng)建相應的右鍵菜單(圖14)。 代碼清單:codemysql-managersrcorgcrazyitmysqluiMainFrame.java //點擊視圖節(jié)點,查找全部的視圖 private void clickViewNode(Database db) { List<ViewData> datas = db.getViews(); this.dataList.setListData(datas.toArray()); //顯示視圖后,創(chuàng)建右鍵菜單 createViewMenu(); //設置當前顯示的數(shù)據(jù)類型為視圖 this.currentView = new ViewData(db, null); } 注意最后還需要將當前的ViewObject設置為視圖對象,用于標識當前所瀏覽的數(shù)據(jù)類型。實現(xiàn)效果如圖16所示。 
圖16 視圖列表 6.2 新建視圖創(chuàng)建視圖使用JDBC執(zhí)行CREATE VIEW語句即可實現(xiàn),視圖界面只提供一個保存功能,由于我們創(chuàng)建視圖與修改視圖都是使用同一個界面,因此在執(zhí)行保存的時候,就需要判斷新增還是修改。當?shù)玫接脩粼谝晥D界面輸入的視圖定義后,就可以執(zhí)行CREATE VIEW語句進行創(chuàng)建視圖。為ViewData對象加入一個創(chuàng)建視圖的方法。 代碼清單:codemysql-managersrcorgcrazyitmysqlobjectlistViewData.java //創(chuàng)建視圖 public void createView() { //拼裝CREATE VIEW語句 String sql = MySQLUtil.CREATE_VIEW + name + " " + MySQLUtil.AS + " " + content; database.getStatement().execute(sql); } 用戶點擊保存,彈出另外一個窗口讓用戶輸入視圖名稱,最后調(diào)用上面的createView方法,即可以創(chuàng)建視圖, 6.3 修改視圖與刪除視圖與創(chuàng)建視圖一樣,使用同樣的界面,只是執(zhí)行不同的SQL語句,修改視圖可以使用ALTER VIEW即可,為ViewData對象加入修改視圖的方法。當修改完視圖后,調(diào)用修改方法即可。 代碼清單:codemysql-managersrcorgcrazyitmysqlobjectlistViewData.java //修改視圖 public void alterView() { String sql = MySQLUtil.ALTER_VIEW + name + " " + MySQLUtil.AS + " " + content; database.getStatement().execute(sql); } 同樣地,刪除視圖使用JDBC執(zhí)行DROP VIEW即可實現(xiàn)。 ViewData: //刪除視圖 public void dropView() { String sql = MySQLUtil.DROP_VIEW + this.name; database.getStatement().execute(sql); } 到此,查詢?nèi)恳晥D、創(chuàng)建視圖、修改視圖和刪除視圖功能已經(jīng)實現(xiàn),但是,還缺少一個最重要的功能,就是查看視圖。當我們選擇了某個視圖進行雙擊操作的時候,就需要瀏覽該視圖的數(shù)據(jù),這一個功能我們將在下面的章節(jié)中實現(xiàn)。 7 存儲過程與函數(shù)管理存儲過程與函數(shù)管理使用相同的界面,因此我們可以一起實現(xiàn),它們的區(qū)別在于是否有返回值,通過一些界面判斷即可實現(xiàn)。與視圖管理一樣,都是有新增、修改和刪除功能。 7.1 新增存儲過程和函數(shù)存儲過程和函數(shù)的界面已經(jīng)在2.7中創(chuàng)建,得到存儲過程或者函數(shù)的定義、參數(shù)、返回值(函數(shù))與名稱后,就可以使用命令去創(chuàng)建存儲過程或者函數(shù)。創(chuàng)建存儲過程使用CREATE PROCEDURE,創(chuàng)建函數(shù)使用CREATE FUNCTION。在本章中,視圖數(shù)據(jù)使用的是一個ViewData對象(6章節(jié)),在2.2中也創(chuàng)建了一個ProcedureData對象來表示一個存儲過程的數(shù)據(jù)對象,因此將創(chuàng)建存儲過程或者函數(shù)加入到該類中即可。 ProcedureData創(chuàng)建存儲過程方法。 代碼清單:codemysql-managersrcorgcrazyitmysqlobjectlistProcedureData.java //創(chuàng)建存儲過程 public void createProcedure() { String sql = MySQLUtil.CREATE_PROCEDURE + this.name + " (" + this.arg + ") " + this.content; this.database.getStatement().execute(sql); } ProcedureData中創(chuàng)建函數(shù)方法。 代碼清單:codemysql-managersrcorgcrazyitmysqlobjectlistProcedureData.java //創(chuàng)建函數(shù) public void createFunction() { String sql = MySQLUtil.CREATE_FUNCTION + this.name + " (" + this.arg + ") returns " + this.returnString + " " + this.content; this.database.getStatement().execute(sql); } 存儲過程對象(ProcedureData)與視圖對象(ViewData)一樣,都是屬于某個數(shù)據(jù)庫的,因此這兩個對象都會保存一個數(shù)據(jù)庫的屬性,直接就可以通過數(shù)據(jù)庫對象(Database)的getStatement方法得到Statement對象,再執(zhí)行SQL語句。 7.2修改存儲過程與函數(shù)修改存儲過程(函數(shù))與新增存儲過程(函數(shù))使用的是相同的界面,因此在保存的時候需要作出判斷。與修改視圖不同的是,修改存儲過程或者函數(shù),不使用ALTER PROCEDURE(ALTER FUNCTION)來實現(xiàn),這是由于MySQL中的ALTER PROCEDURE和ALTER FUNCTION并不能修改存儲過程或者函數(shù)的方法體與參數(shù),因此,實現(xiàn)時需要將原來的存儲過程或者函數(shù)先刪除,再重新創(chuàng)建。 ProcedureData中修改存儲過程。 代碼清單:codemysql-managersrcorgcrazyitmysqlobjectlistProcedureData.java //修改存儲過程 public void updateProcedure() { //修改存儲過程需要先把原來的先刪除 //刪除語句 String dropSQL = MySQLUtil.DROP_PROCEDURE + this.name; this.database.getStatement().execute(dropSQL); //創(chuàng)建語句 String createSQL = MySQLUtil.CREATE_PROCEDURE + this.name + " (" + this.arg + ") " + this.content; this.database.getStatement().execute(createSQL); } 代碼清單:codemysql-managersrcorgcrazyitmysqlobjectlistProcedureData.java //修改函數(shù) 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); } 以上兩個方法就可以修改存儲過程和函數(shù),但是如果一旦存儲過程或者函數(shù)編寫有誤,那么就會將原來的存儲過程或者函數(shù)刪除,為了解決這個問題,可以將原來的存儲過程改名,再創(chuàng)建一個修改后的存儲過程,如果創(chuàng)建失敗,就將改名后的舊的存儲過程改回來,這樣就可以確保錯誤發(fā)生后無法恢復原來的存儲過程。修改存儲過程或者函數(shù)的名稱使用ALTER PROCEDURE或者ALTER FUNCTION即可實現(xiàn)。 在修改存儲過程與函數(shù)的時候,我們就使用了DROP PROCEDURE和DROP FUNCTION來刪除一個存儲過程和函數(shù),刪除存儲過程和函數(shù)不再詳細描述。存儲過程或者函數(shù)的調(diào)用可以使用CALL來調(diào)用,在實現(xiàn)了SQL查詢功能后,就可以執(zhí)行一句CALL的SQL來調(diào)用查看效果。 8 表管理表管理在本章中相對較難,我們需要從界面中得到創(chuàng)建表的信息,例如字段信息、外鍵字段信息等。在修改表的時候,用戶在界面的表格中會進行各種操作,操作完后進行保存,就需要收集這些被操作過的數(shù)據(jù),再進表的修改。 在2.5中,我們已經(jīng)創(chuàng)建的表管理界面(如2.5中的圖7所示),對應的是TableFrame類,界面中存的有兩個列表對象,分別是字段列表和外鍵字段列表,對應的類是FieldTable與ForeignTable,它們都繼承于JTable。字段列表有以下操作: 新字段:向字段列表的尾部追加一個新的字段行。 插入字段:在所選擇的行前面插入一個字段行。 刪除字段:刪除所選的行。 設置默認值:在文本框中輸入該字段的默認值。 設置自動增長:設置字段是否可以自動增長。 外鍵字段有以下操作: 新外鍵:向表外尾部追加一個新的外鍵。 刪除外鍵:刪除一個選擇的外鍵。 8.1 新字段為了能體現(xiàn)一個字段,我們新建一個字段對象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(表管理界面)中,創(chuàng)建一個字段集合用來保存當前界面所顯示的字段,那么如果進行新建字段操作,就可以對該集合進行操作了。下面實現(xiàn)刷新字段列表的方法,由于在加入新字段、修改字段或者刪除字段后,都需要將列表進行一次刷新。 代碼清單:codemysql-managersrcorgcrazyitmysqluiTableFrame.java //刷新字段列表 public void refreshFieldTable() { DefaultTableModel tableModel = (DefaultTableModel)this.fieldTable.getModel(); //設置數(shù)據(jù) tableModel.setDataVector(getFieldDatas(), this.fieldTable.getFieldTableColumn()); //設置列表樣式,包括行高、列寬等 this.fieldTable.setTableFace(); } //得到字段列表數(shù)據(jù) 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(); //如果是修改狀態(tài),則添加addFields集合中 if (this.table.getName() != null) this.addFields.add(field); } 以上的黑體代碼,是在用戶是行修改表的時候才需要,我們創(chuàng)建一個addFields集合,用來保存用戶添加過的字段(修改的時候)。該集合的作用,我們將修改表的時候詳細描述。 8.2 插入字段與刪除字段插入新字段,只需要得到用戶當前所選擇的行,并在該行的前面加入一個數(shù)據(jù)行即可,需要注意的是,當用戶沒有選擇任意一行的時候,就可以調(diào)用新字段的方法,即8.1中創(chuàng)建新字段的方法。 代碼清單:codemysql-managersrcorgcrazyitmysqluiTableFrame.java //插入新字段 private void insertField() { //得到選擇中的行索引 int selectRow = this.fieldTable.getSelectedRow(); if (selectRow == -1) { //沒有選中,調(diào)用加新字段方法,加入新字段 newField(); return; } Field field = new Field(); this.fields.add(selectRow, field); //刷新字段列表 refreshFieldTable(); //如果是修改狀態(tài),則添加addFields集合中 if (this.table.getName() != null) this.addFields.add(field); } 刪除字段實現(xiàn)與插入字段一樣,只需要將字段從集合中刪除并刷新列表即可,另外,如果是修改表的話,就需要加入另外的操作,在下面修改表的章節(jié)中描述。 代碼清單: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對象,并設置新的信息。當用戶對列表停止編輯的時候,就可以觸發(fā)相應的方法。這里需要注意的是,當停止編輯的時候,需要修改對應的Field對象,只需要修改該對象的字段名稱與字段類型,因為這兩個屬性才可以輸入,其他兩個屬性(是否允許空和主鍵)進行選擇才會發(fā)生值的改變。 代碼清單:codemysql-managersrcorgcrazyitmysqlui ableFieldTable.java //重寫JTable的方法, 列表停止編輯的時候觸發(fā)該方法 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); } } 在列表停止編輯的時候,得到用戶所編輯的單元格的行索引和編輯后的值,再調(diào)用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中已經(jīng)實現(xiàn)了部分,當用戶選擇了列表的時候,就會觸發(fā)該方法。 代碼清單:codemysql-managersrcorgcrazyitmysqlui ableFieldTable.java //鼠標在JTable中點擊的時候觸發(fā)該方法 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 //當發(fā)生鼠標點擊單元格事件的時候,改變值,一般只改變允許空和主鍵列 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); } } 判斷用戶所點擊的單元格所屬的列,如果是“允許空”或者“主鍵”列,就可以調(diào)用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); } 同樣地,與設置默認值一樣,也可以為多選框加入監(jiān)聽器,如果發(fā)生點擊事件時,就執(zhí)行某個方法。 代碼清單: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集合中。但是,還需要去修改點擊字段列表的方法,將默認值與自動增長的值展現(xiàn)到界面的元素中。修改FieldTable的selectCell方法,列表被點擊時,會觸發(fā)該方法。 代碼清單:codemysql-managersrcorgcrazyitmysqlui ableFieldTable.java //鼠標在JTable中點擊的時候觸發(fā)該方法 private void selectCell() { //省略其他代碼 //修改默認值 this.tableFrame.setDefaultValue(row); //修改是否自動增長的checkbox this.tableFrame.setIsAutoIncrement(row); } 點擊了列表某行的時候,就可以得到相應的Field對象,再設置界面的文本框和多選框即可。 8.5 新外鍵與實現(xiàn)新字段一樣,新建一個FoeignField對象來代表一個外鍵,并在TableFrame中創(chuàng)建一個集合來保存當前界面中的外鍵。 代碼清單:codemysql-managersrcorgcrazyitmysql ableobjectForeignField.java private String constraintName; //約束的名稱 private Field field; //被約束的字段,根據(jù)該字段可以找出該外鍵對象所屬于的表 private Field referenceField; //外鍵的字段,可以根據(jù)此屬性找出該關系中的外鍵表 private String onDelete; //級聯(lián)刪除策略 private String onUpdate; //級聯(lián)更新策略 private String referenceTableName; //約束表的名稱 private String referenceFieldName; //約束字段的名稱 private String uuid; //字段的uuid //省略setter和getter方法 在2.5中已經(jīng)創(chuàng)建了外鍵列表對象(ForeignTable),新建刷新外鍵列表的方法。 代碼清單:codemysql-managersrcorgcrazyitmysqluiTableFrame.java //刷新外鍵字段列表 public void refreshForeignFieldTable() { //設置外鍵數(shù)據(jù) DefaultTableModel tableModel = (DefaultTableModel)this.foreignTable.getModel(); tableModel.setDataVector(getForeignDatas(), this.foreignTable.getForeignColumns()); //設置外鍵列表的樣式 this.foreignTable.setTableFace(); } 加入一個外鍵的時候,就可以調(diào)用refreshForeignFieldTable方法刷新外鍵列表。 代碼清單:codemysql-managersrcorgcrazyitmysqluiTableFrame.java //新增一個外鍵字段 private void newForeignField() { ForeignField foreignField = new ForeignField(); this.foreignFields.add(foreignField); //設置該外鍵的constraintName,用UUID設置 foreignField.setConstraintName(UUID.randomUUID().toString()); //刷新外鍵列表 refreshForeignFieldTable(); //如果是修改狀態(tài),加到添加的外鍵集合中 if (this.table.getName() != null) this.addForeignFields.add(foreignField); } 以上的黑體代碼,與8.1中新字段一樣,都是在修改表的時候需要使用到的代碼,將在下面修改表的章節(jié)中加以描述。這里需要注意的是,為了標識新加的“外鍵”在數(shù)據(jù)庫中的唯一性,因為需要將ForeignField的constraintName屬性設置為唯一的。 8.6 刪除一個外鍵刪除外鍵實現(xiàn)比較簡單,只需要從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); } 前面幾個小節(jié)中,我都講解了如何實現(xiàn)表管理中的一些界面操作,接下來實現(xiàn)具體的表管理,包括查詢表信息、保存表和修改表。 8.7 查詢字段信息查詢一個表的信息需要到MySQL的系統(tǒng)表中查詢,這些信息包括字段信息與外鍵信息等。由于我們在TableFrame中建立了一個字段集合來保存當前界面中的字段信息,因此,只需要從數(shù)據(jù)庫中查詢所有的表字段并封裝成一個Field集合即可。在本章中,一個表由一個TableData對象來代表,TableData中包含了一個Database對象,Database對象可以取到數(shù)據(jù)庫的連接信息,可以將查詢字段的方法寫到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,到系統(tǒng)表中查詢一個表的所有字段,使用JDBC執(zhí)行getFieldSQL方法返回的SQL,就可以得到ResultSet對象,再得到各個列的值。 我們需要從查詢到的ResultSet中得到以下值: COLUMN_NAME:字段名。 COLUMN_TYPE:字段類型。 IS_NULLABLE:是否允許空。 COLUMN_KEY:如果是主鍵,那么該值為“PRI”。 COLUMN_DEFAULT:該字段的默認值。 EXTRA:如果該值為auto_increment,則表示是自動增長。 得到以上的值,就可以封裝成一個字段對象,并加到結果集合中,那么TableFrame就可以根據(jù)這個集合來顯示字段數(shù)據(jù)。 8.8 查詢外鍵信息查詢外鍵信息與查詢字段信息一樣,都是到MySQL的系統(tǒng)表(KEY_COLUMN_USAGE)中進行查詢,但是,如果使用的MySQL5.0,則不能到系統(tǒng)表中查詢到一個外鍵的ON DELETE和ON UPDATE的值。如果使用的是MySQL5.1,就可以到系統(tǒng)表中查詢到一個字段的這兩個值。本章使用的MySQL版本是5.0,因此要得到ON DELETE和ON UPDATE的值,就需要得到建表時的SQL,并對該句SQL進行分析,得到外鍵的ON DELETE和ON UPDATE,這就是本章開頭所講的MySQL5.0與MySQL5.1的差別對我們開發(fā)這個管理器所產(chǎn)生的影響。 執(zhí)行下面的SQL就可以返回外鍵字段信息的ResultSet: SELECT * FROM information_schema.KEY_COLUMN_USAGE sc WHERE sc.TABLE_SCHEMA='數(shù)據(jù)庫名' 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:約束的字段名。 得到約束的字段名后,就可以再次到系統(tǒng)表(COLUMNS)中查詢約束的字段。得到這些信息后,就可以創(chuàng)建一個ForeignField對象,但是,F(xiàn)oreignField中還包含有onDelete和onUpdate兩個屬性,為了得到這個屬性,我們需要得到創(chuàng)建表的SQL,并對該句SQL進行分析。如果你使用的是MySQL5.1,就可以直接到系統(tǒng)表中查詢。得到創(chuàng)建表的SQL,可以使用JDBC執(zhí)行SHOW CREATE TABLE來實現(xiàn)。 以下方法分析創(chuàng)建表的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新外鍵中創(chuàng)建該集合。得到字段與外鍵字段的集合后,就可以在TableFrame中初始化列表,使用DefaultTableModel的setDataVector方法即可初始列表數(shù)據(jù)。 8.9 新建表我們可以TableFrame(表管理界面)中得到字段的集合,集合中保存了一系列的Field對象,那么我們在創(chuàng)建表的時候,就可以根據(jù)這些Field對象的屬性來拼裝CREATE TABLE的SQL語句,然后使用JDBC執(zhí)行即可。 編寫拼裝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`)) 具體實現(xiàn)在TableData中的addTable方法,拼裝SQL語句的方法是TableData中的getTableSQL方法,getTableSQL方法先拼裝CREATE TABLE命令,再依次拼裝字段的SQL、外鍵字段的SQL和創(chuàng)建主鍵的SQL。 拼裝字段SQL的方法。 代碼清單:codemysql-managersrcorgcrazyitmysqlobjectlistTableData.java //根據(jù)字段創(chuàng)建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 //創(chuàng)建外鍵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 //創(chuàng)建主鍵SQL private void createPrimary(StringBuffer sql, List<Field> fields) { for (Field f : fields) { if (f.isPrimaryKey()) { sql.append(PRIMARY_KEY).append("(`" + f.getFieldName() + "`)").append(","); } } } 得到創(chuàng)建表的SQL后,使用JDBC執(zhí)行即可。如果創(chuàng)建失敗,可以將異常信息顯示到界面中。 8.10 修改表實現(xiàn)修改表的功能較為復雜,由于保存在界面中可能涉及的操作包括添加字段、修改字段、刪除字段、添加外鍵字段、修改外鍵字段和刪除外鍵字段,因此修改表的話,我們需要知道用戶添加、修改、刪除了哪些字段與外鍵字段。也就是說,用戶每次進行添加、修改、刪除字段與外鍵字段的時候,我們都需要對用戶所操作的數(shù)據(jù)進行保存,最后,得到這些數(shù)據(jù)后,就可以使用ALTER TALBE來進行表的修改。在8.1新字段中,創(chuàng)建一個字段的時候,另外建立了一個集合來保存用戶所添加的字段,因此還需要在修改和刪除操作的時候,保存用戶所操作的數(shù)據(jù)。 得到用戶操作過的數(shù)據(jù)集合后,我們就可以為TableData加入一個修改表的方法,將這些集合里面的對象都轉換成SQL,然后使用JDBC執(zhí)行。 代碼清單: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字段語句,參數(shù)為用戶添加的字段 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都比較復雜,實現(xiàn)起來比較麻煩。在實現(xiàn)的過程中可以小步快跑,慢慢地一個一個來實現(xiàn)。 8.11 刪除表刪除表實現(xiàn)十分簡單,只需要執(zhí)行DROP TABLE即可實現(xiàn),在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()); } 表管理的大部分功能都已經(jīng)實現(xiàn),以上實現(xiàn)的這些功能中,大部分實現(xiàn)原理都十分簡單,只是使用JDBC來執(zhí)行一些SQL語句即可,但是界面的交互比較復雜,在實現(xiàn)的時候需要特別注意。本章中還有打開表、導出表數(shù)據(jù)的功能,將在下面的章節(jié)中描述。 9 數(shù)據(jù)瀏覽在6中,我們實現(xiàn)了視圖管理功能,在8中實現(xiàn)了表管理功能,在用戶的實際操作中,當雙擊一個視圖或者一個表的時候,我們需要將視圖或者表對應的數(shù)據(jù)展現(xiàn)給用戶,并在界面中顯示,數(shù)據(jù)顯示數(shù)據(jù)的界面已經(jīng)在2.3中創(chuàng)建(DataFrame類),如2.3中的圖4所示。該界面包括刷新、降序和升序功能。 9.1 瀏覽數(shù)據(jù)可以打開數(shù)據(jù)顯示界面的地方有多個,包括打開視圖、打開表、執(zhí)行查詢,視圖在本章中使用一個ViewData對象來表示,表使用一個TableData對象表示,但是這些查詢都是共一個界面,因此我們新建一個QueryObject的接口,讓ViewData(視圖對象)和TableData去實現(xiàn)這個接口,該接口定義如下方法。 代碼清單:codemysql-managersrcorgcrazyitmysqlobjectQueryObject.java public interface QueryObject { //得到數(shù)據(jù) ResultSet getDatas(String orderString); //得到查詢的名稱 String getQueryName(); //返回查詢的SQL String getQuerySQL(String orderString); } 除了ViewData與TableData外,當執(zhí)行一些SQL語句的時候,也需要將數(shù)據(jù)顯示到DataFrame中,因此還需要建立一個QueryData的對象,表示一次查詢所顯示的數(shù)據(jù)對象,同樣去實現(xiàn)QueryObject接口。QueryObject接口只需要實現(xiàn)得到數(shù)據(jù)的方法,在ViewData和TableData,只需要使用SELECT語句就可以從視圖和表中得到相應的ResultSet對象。在本章中,ViewData和TableData都保存一個Database對象,因此可以直接使用JDBC去執(zhí)行,但是新建的QueryData對象就不屬于任何的數(shù)據(jù)庫,因此需要為該類提供構造器。 代碼清單:codemysql-managersrcorgcrazyitmysqlobjectlistQueryData.java //此次查詢的SQL語句 private String sql; //對應的數(shù)據(jù)庫對象 private Database database; public QueryData(Database database, String sql) { this.sql = sql; this.database = database; } 編寫完ViewData、TableData和QueryData對接口QueryObject的方法后,就可以將數(shù)據(jù)顯示到DataFrame中。 代碼清單:codemysql-managersrcorgcrazyitmysqluiDataFrame.java public DataFrame(QueryObject queryObject) { this.queryObject = queryObject; //省略其他代碼 } 為DataFrame提供一個構造器,參數(shù)是接口QueryObject,那么DataFrame就可以不用關心數(shù)據(jù)來源,只需要負責數(shù)據(jù)顯示就可以了,這些數(shù)據(jù)來源只封裝在一個QueryObject中,有可能是表的數(shù)據(jù),也有可能是視圖的數(shù)據(jù)。DataFrame中處理數(shù)據(jù)的時候需要注意的是,JTable的列也是動態(tài)的,它的列數(shù)由ResultSet來決定的,得到一個ResultSet的列數(shù),可以使用ResultSetMetaData對象的getColumnCount方法來得到列數(shù)。 本章使用一個DataTable對象表示一個數(shù)據(jù)列表,該對象繼承JTable,在2.3中已經(jīng)創(chuàng)建。新建一個DataColumn對象,表示一個數(shù)據(jù)列,新建一個DataCell對象,表示一個數(shù)據(jù)單元格對象。 DataColumn包括如下屬性。 //該列在JTable中的索引 private int index; //該列的名稱 private String text; DataCell包括如下屬性: //該單元格所在的行 private int row; //該單元格所在的列 private DataColumn column; //該單元格的值 private String value; 另外,需要為DataCell對象重寫toString方法,返回該對象的value值。那么我們可以使用以下代碼來創(chuàng)建列表的數(shù)據(jù)。 代碼清單:codemysql-managersrcorgcrazyitmysqluiDataFrame.java while (this.rs.next()) { DataCell[] data = new DataCell[columnCount]; //遍歷列, 創(chuàng)建每一個單元格對象 for (int j = 0; j < columnCount; j++) { //得到具體的某一列 DataColumn column = this.columns.get(j); //創(chuàng)建單元格對象 DataCell dc = new DataCell(i, column, this.rs.getString(column.getText())); data[j] = dc; } datas.add(data); } 遍歷一個ResultSet對象,遍歷所有的列,最后構造一個DataCell對象,并存放到結果的集合中,注意結果集合是一個List對象,List對象里面的元素是DataCell數(shù)組。最后在設置列表數(shù)據(jù)時候,可以DefaultTableModel的setDataVector方法將數(shù)據(jù)與列的信息設置到列表中。這里需要注意的是,不管以什么方式進入數(shù)據(jù)顯示界面,都需要重新創(chuàng)建一個DataFrame對象。 9.2 刷新數(shù)據(jù)在DataFrame中,已經(jīng)保存有一個QueryObject的對象,如果需要對界面中的數(shù)據(jù)進行刷新,只需要重新讀取一次數(shù)據(jù),即執(zhí)行QueryObject的getDatas方法,再重新將數(shù)據(jù)設置到列表中即可。 代碼清單:codemysql-managersrcorgcrazyitmysqluiDataFrame.java //刷新數(shù)據(jù) private void refresh() { //更新數(shù)據(jù) this.rs = this.queryObject.getDatas(this.orderString); //得到全部列 this.columns = getColumns(this.rs); //設置數(shù)據(jù)到列表中 this.model.setDataVector(getDatas(), this.columns.toArray()); //設置每一列的寬 setTableColumn(this.table); } 除了刷新數(shù)據(jù)外,還有降序與升序的功能,當用戶對數(shù)據(jù)進行了降序或者升序操作時,就可以調(diào)用refresh方法對列表進行刷新。 9.3 數(shù)據(jù)排序我們在2.3中,已經(jīng)實現(xiàn)了DataTable的整列選擇(具體請看2.3章節(jié)),實現(xiàn)整列選擇的時候,DataTable中保存一個selectColumn的值,表示用戶當前所選擇的列索引,當用戶選擇了整列的時候,selectColumn就等于具體的某列索引,因此就可以得到這一列的名稱(數(shù)據(jù)庫的字段名),然后再使用SQL中的ORDER BY語句對數(shù)據(jù)進行排序。 代碼清單: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(); } 以上代碼實現(xiàn)降序。在接口QueryObject的getDatas方法中,需要提供參數(shù)orderString,即排序語句,因此只需要重新執(zhí)行QueryObject的getDatas方法即可得到排序后的數(shù)據(jù)。升序的實現(xiàn)與降序一致,只是使用SQL的ORDER BY ASC即可實現(xiàn)。 10 執(zhí)行SQL執(zhí)行SQL的界面已經(jīng)在2.8中實現(xiàn),只是一個簡單的文本域讓用戶輸入SQL,然后提供一個執(zhí)行SQL與保存SQL的功能,由于我們已經(jīng)實現(xiàn)了數(shù)據(jù)瀏覽的顯示,因此實現(xiàn)起來十分簡單。執(zhí)行SQL的界面在本章中為QueryFrame類。 10.1 運行SQL在2.8中,用戶只需要輸入相關的SQL語句,就可以執(zhí)行該SQL語句,如2.8中的圖l0所示。如果用戶輸入的是INSERT、UPDATE等語句,那么就可以將執(zhí)行結果直接使用普通的提示框顯示出來,如果用戶輸入的是SELECT(查詢語句)、CALL(調(diào)用存儲過程或者函數(shù))語句,那么就需要將查詢封裝成一個QueryData對象(9.1中創(chuàng)建),再將QueryData對象作為構造參數(shù)傳遞給DataFrame,QueryData是QueryObject接口的其中一個實現(xiàn)。 代碼清單: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關鍵字的時候才調(diào)用,其他情況則直接彈出執(zhí)行結果的提示(成功與否)。 10.2 保存SQL保存SQL,只是將用戶輸入的SQL語句保存到一份文件中即可。從界面得到用戶輸入的數(shù)據(jù),然后提供一個文件選擇器讓用戶選擇,最后使用IO流將內(nèi)容輸入到文件就可以實現(xiàn)。 代碼清單:codemysql-managersrcorgcrazyitmysqlutilFileUtil.java //創(chuàng)建文件并將content寫到File中 public static void writeToFile(File file, String content) { //創(chuàng)建新文件 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(); //將內(nèi)容寫到文件中 FileUtil.writeToFile(file, content); } 執(zhí)行SQL的功能已經(jīng)完成,這是用戶輸入SQL再運行的一種操作形式,用戶還有另外一種操作形式,就是通過導入文件來執(zhí)行一份SQL文件,下一小節(jié)將講解如何實現(xiàn)SQL文件的導入與導出。 11 SQL文件的導入與導出SQL文件的導入與導出,包括數(shù)據(jù)庫的導入與導出、表的導出。當用戶選擇了某個數(shù)據(jù)庫的時候,就提供鼠標右鍵菜單,讓用戶可以執(zhí)行數(shù)據(jù)庫的導出與導入操作。當用戶選擇了某個表的時候,就可以提供鼠標右鍵菜單,提供導出功能。右鍵菜單已經(jīng)在2.9與2.10中實現(xiàn)。 11.1 執(zhí)行SQL文件進入管理器的第一個界面,就需要用戶選擇MySQL的安裝目錄,根據(jù)用戶所選擇的目錄,我們就可以找到MySQL安裝目錄下面的bin目錄,然后找到mysqldump與mysql這兩個命令。mysqldump命令主要用于導出SQL文件,mysql命令主要用于執(zhí)行SQL文件。 在3小節(jié)中,我們實現(xiàn)了用戶選擇MySQL安裝目錄的功能,并且將用戶選擇的目錄存放到一個GlobalContext的對象中,那么如果需要使用mysql命令執(zhí)行SQL文件,直接拼裝命令執(zhí)行即可。至于執(zhí)行的方式,將在下面講解。 例如MySQL中存在一個數(shù)據(jù)庫,如果需要執(zhí)行某份SQL文件,就需要執(zhí)行以下語句: "MySQL安裝目錄inmysql" –u用戶名 –p密碼 –h連接IP –D數(shù)據(jù)庫名稱 < "SQL文件絕對路徑" 使用mysql命令執(zhí)行SQL文件,mysql命令有許多的參數(shù),以上語使用了-u、-p、-h和-D參數(shù),-u參數(shù)表示數(shù)據(jù)庫的用戶名,-p表示密碼,-h表示連接的服務器IP,-D表示需要執(zhí)行文件的數(shù)據(jù)庫,拼裝好以上的語句后,可以使用Runtime類的exec方法執(zhí)行。 注意:這里需要特別說明的是,如果MySQL安裝目錄有空格,或者SQL文件的絕對路徑有空格,首先需要將mysql命令(包括安裝目錄)使用雙引號括起來,再將SQL文件的絕對路徑使用雙引號括起來,但是直接使用Runtime的exec執(zhí)行仍然會出錯,我們可以將拼裝的那句命令,使用IO流寫入到一份.bat文件中,然后使用Runtime的exec方法執(zhí)行:“cmd /c bat文件所在”可消除錯誤。當然,這是在Windows操作系統(tǒng)下,如果使用Linux的話,可以生成sh文件。 11.2 導出數(shù)據(jù)庫與表導出數(shù)據(jù)庫與執(zhí)行SQL文件一樣,使用mysqldump命令即可實現(xiàn)。mysqldump語句格式如下: "MySQL安裝目錄inmysqldump" -u用戶名 -p密碼 -h連接IP --force --databases 數(shù)據(jù)庫名 > "導出的SQL保存目錄" 以上使用了mysqldump集合的-u、-p、-h、--force和—databases參數(shù),-u、-p和-h分別代表用戶名、密碼和數(shù)據(jù)庫服務器IP,--force參數(shù)表示發(fā)生錯誤將繼續(xù)執(zhí)行,--database參數(shù)表示需要導出的數(shù)據(jù)庫名稱。導出SQL文件與執(zhí)行SQL文件一樣,都是將拼裝的命令使用IO流寫到一份bat或者sh文件中,再使用Runtime的exec方法執(zhí)行。 導出表與導出數(shù)據(jù)庫實現(xiàn)一致,只需要在導出數(shù)據(jù)庫的參數(shù)的基礎上,再加上—tables參數(shù)來聲明需要導出的表即可。多個表之間使用空格將表名分隔。導出表使用的mysqldump語句格式如下: "MySQL安裝目錄inmysqldump" -u用戶名 -p密碼 -h連接IP --databases 數(shù)據(jù)庫名稱 --tables 表一 表N > "導出的SQL文件保存目錄" 在本章中,處理SQL文件的導入與導出由BackupHandler接口完成,該接口有一個BackupHandlerImpl的實現(xiàn)類,已經(jīng)對SQL文件的導出和導出進行實現(xiàn),這些實現(xiàn)只是拼裝一些語句,真正執(zhí)行這些語句由CommandUtil中的executeCommand方法執(zhí)行,該方法提供了Windows下的實現(xiàn)(生成一份bat文件并執(zhí)行、最后刪除)。 12 本章小節(jié)本章實現(xiàn)了一個MySQL管理器,這個管理器中有多個功能,包括數(shù)據(jù)庫元素的管理、數(shù)據(jù)瀏覽與SQL文件的導出和導出。我們可以在管理器實現(xiàn)的過程中,了解MySQL管理器的實現(xiàn)原理。實現(xiàn)MySQL管理器功能并不困難,困難的是一些界面的交互,特別是表管理界面。本章的這個MySQL管理器與一些流行的管理器有著部分的區(qū)別,例如我們在進入管理器的時候,需要用戶選擇MySQL的安裝目錄,目的為了使用MySQL的一些內(nèi)置命令,但是例如平常使用的Navicat,它并不需要知道MySQL的安裝目錄(或者根本不需要安裝MySQL),使用程序來導出SQL文件。當用戶需要導出SQL文件的時候,我們也可以使用程序來對數(shù)據(jù)庫進行分析,再寫到SQL文件中,這樣也是一種實現(xiàn)途徑。本章的目的并理器競爭不是與這些商業(yè)管,而是讓讀者了解管理器實現(xiàn)的原理。希望大家能在本章的基礎上,開發(fā)出功能更加強大的MySQL管理器。
信息發(fā)布:廣州名易軟件有限公司 http://m.jetlc.com
|