數據庫中我們最常用到的元素就是database、table、column。Mybatis作為強大的ORM框架,當中也包含了獲取處理這些元素的代碼,本篇將介紹相關的內容。
一、Catalog & Schema
在介紹具體的代碼實現之前,我們需要先了解兩個后面會用到的兩個名詞:Catalog & Schema。
按照SQL標准的解釋,在SQL環境下Catalog和Schema都屬於抽象概念,可以把它們理解為一個容器或者數據庫對象命名空間中的一個層次,主要用來解決命名沖突問題。從概念上說,一個數據庫系統包含多個Catalog,每個Catalog又包含多個Schema,而每個Schema又包含多個數據庫對象(表、視圖、字段等),反過來講一個數據庫對象必然屬於一個Schema,而該Schema又必然屬於一個Catalog,這樣我們就可以得到該數據庫對象的完全限定名稱從而解決命名沖突的問題了;例如數據庫對象表的完全限定名稱就可以表示為:Catalog名稱.Schema名稱.表名稱。這里還有一點需要注意的是,SQL標准並不要求每個數據庫對象的完全限定名稱是唯一的,就像域名一樣,如果喜歡的話,每個IP地址都可以擁有多個域名。
從實現的角度來看,各種數據庫系統對Catalog和Schema的支持和實現方式千差萬別,針對具體問題需要參考具體的產品說明書,比較簡單而常用的實現方式是使用數據庫名作為Catalog名,使用用戶名作為Schema名,具體可參見下表:

最后一點需要注意的是Schema這個單詞,它在SQL環境下的含義與其在數據建模領域中的含義是完全不同的。在SQL環境下,Schema是一組相關的數據庫對象的集合,Schema的名字為該組對象定義了一個命名空間,而在數據建模領域,Schema(模式)表示的是用形式語言描述的數據庫的結構;簡單來說,可以這樣理解,數據建模所講的Schema<也就是元數據>保存在SQL環境下相應Catalog中一個Schema<名叫DEFINITION_SCHEMA>下的表中,同時可以通過查詢該Catalog中的另一個Schema<名叫INFORMATION_SCHEMA>下的視圖而獲取,具體細節不再贅述。
注明:該小節的內容來自於博客http://blog.sina.com.cn/s/blog_515015800100evtc.html
二、Database、Table、Column
按照包容關系,從大到小的順序為database、table、column。因而在介紹的時候我們將從最小的開始,也就是從Column開始,而后是Table,最后是database的順序進行。
1.Column
對於數據庫中的列而言,主要有這么幾個屬性:名稱、類型(有些有精度或者長度)、默認值、是否可為空、注釋等。熟悉mysql的讀者可以看看information_schema庫中的COLUMNS表,這個表中的每一條記錄描述了一列的各種屬性。在mybatis的Column類只記錄常用的兩個屬性:列名和類型
//列名 private String name; /** * 類型,具體的數字代表的類型可以參考java.sql.Types中一系列的靜態常量 * 如 public final static int BIT = -7; * public final static int INTEGER = 4; * public final static int DOUBLE = 8; */ private int type;
這個類中的函數都是一些常用的基本函數,沒有什么特殊的地方,也相當的簡單明了,有興趣的讀者可以直接看源代碼。個人覺得equals函數的實現挺有意思,在此做下說明,與大家分享:
/** * 有必要提下 “==”和equals的區別和聯系 * “==” 比較的是兩個變量在內存中的是否指向同一個地方,即內存地址是否相同 * “equals” 比較的是兩個對象的內容是不是相同 * 因而 “==”的true的兩個變量“equals”一定為true,但反之則不然.有興趣的同學可以執行下下面幾行代碼: * String a = new String("22"); String b = new String("22"); String csString = a; System.out.println(a==b?"====":"!==="); System.out.println(a.equals(b)?"equals":"!equals"); System.out.println(a==csString?"====":"!==="); * */ public boolean equals(Object o) { // 比較這兩個變量的內存地址,如果指向同一塊內存,則必然相等 if (this == o) return true; // 如果兩個對象的類型都不一樣,內容肯定也不一樣,要不然也無需定義兩個不同的類了 if (o == null || getClass() != o.getClass()) return false; final Column column = (Column) o; //比較類型,如果類型不同則必然是兩個不同的列 if (type != column.type) return false; //請看下面這條語句,寫的相當巧妙,一行語句針對兩種請看做了處理,比if else要好 //如果這個變量的name不為空,則判斷這兩個變量的類型名稱是不是相同(用equals,而不是==);如果為空,則判斷另一個變量的name是不是為空 if (name != null ? !name.equals(column.name) : column.name != null) return false; //當然,也可以先比較名稱然后再比較名稱, return true; }
注意,在重寫equals方法時,要注意滿足離散數學上的特性
1 自反性 :對任意引用值X,x.equals(x)的返回值一定為true.
2 對稱性: 對於任何引用值x,y,當且僅當y.equals(x)返回值為true時,x.equals(y)的返回值一定為true;
3 傳遞性:如果x.equals(y)=true, y.equals(z)=true,則x.equals(z)=true
4 一致性:如果參與比較的對象沒任何改變,則對象比較的結果也不應該有任何改變
5 非空性:任何非空的引用值X,x.equals(null)的返回值一定為false
2.Table
接下來我們來看Table這個類,數據庫中的表通常有這么幾個屬性:表名、一些列、主鍵、索引、表的注釋、表的引擎類型、表的編碼格式等。在mybatis中Table類的屬性有如下幾個:
private final String name;//表名 private String catalog;//表的catalog,具體哪些數據庫支持該屬性請見第一節 private String schema;//表的schema,對於mysql而言,就是庫名 //包含的列,key的列的名稱,value是列的具體信息 private final Map<String, Column> columns = new HashMap<String, Column>(); //主鍵,看這個定義不支持聯合主鍵 private Column primaryKey;
這個類中的函數沒有什么特殊之處,都是常見見的,在此不再進行說明。
3.Database
接下來我們來看Database這個類,在mybatis中Database類的屬性有如下幾個:
private final String catalog;//庫的catalog,具體哪些數據庫支持該屬性請見第一節 private final String schema;//可以看做庫名 //該數據庫包含的表 private final Map<String, Table> tables = new HashMap<String, Table>();
這個類中的函數沒有什么特殊之處,都是常見見的,在此不再進行說明。
三、DatabaseFactory
前面介紹了mybatis用來描述列、表、數據庫的三個類,那如何獲得一個數據庫中的這些信息呢?這就要用到這一小節中的DatabaseFactory類了。這個類只有兩個函數,一個是私有的不帶參數的構造函數,另一個是返回Database對象的一個靜態函數。對於私有的不帶參數的構造函數我們無需進行介紹,它的作用就是不讓其他的類去實例化該類,我們着重了解下這個靜態函數。建議讀者親自創建一個數據庫、表去執行下這個函數看看返回的結果,這樣會對這個函數有更深刻的理解,在這個過程中徹底的理解他人的思想,轉化為自己的能力。 在這里貼出newDatabase函數的主要內容:
// 先初始化一個database對象 Database database = new Database(catalogFilter, schemaFilter); ResultSet rs = null; try { // 獲取數據庫連接的元數據 // ResultSet類也有getMetaData()函數,但返回的結果為ResultSetMetaData DatabaseMetaData dbmd = conn.getMetaData(); try { // 獲取這個鏈接對應的數據庫的列信息 /** * getColumns前兩個參數說明,來自於該類的源代碼 * * @param catalog * a catalog name; must match the catalog name as it * is stored in the database; "" retrieves those * without a catalog; <code>null</code> means that * the catalog name should not be used to narrow the * search * 如果參數值是空字符串,返回沒有catalog的數據庫;如果參數為null,在查詢時不用這個catalog做為查詢條件 * ;如果為其他值,則按照輸入值進行查詢。 schema的查詢條件類似 * @param schemaPattern * a schema name pattern; must match the schema name * as it is stored in the database; "" retrieves * those without a schema; <code>null</code> means * that the schema name should not be used to narrow * the search * */ rs = dbmd.getColumns(catalogFilter, schemaFilter, null, null); while (rs.next()) { //獲取數據 String catalogName = rs.getString("TABLE_CAT"); String schemaName = rs.getString("TABLE_SCHEM"); String tableName = rs.getString("TABLE_NAME"); String columnName = rs.getString("COLUMN_NAME"); int dataType = Integer.parseInt(rs.getString("DATA_TYPE")); //從database根據表名獲取table對象,如果沒有這個表,則初始化 Table table = database.getTable(tableName); if (table == null) { table = new Table(tableName); table.setCatalog(catalogName); table.setSchema(schemaName); database.addTable(table); } //將列添加到表對象中 table.addColumn(new Column(columnName, dataType)); }
四、實驗
數據庫服務器:mysql
數據庫名:test
表結構:
CREATE TABLE `mybatis` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(20) DEFAULT '', `password` varchar(32) DEFAULT '', `sex` tinyint(1) DEFAULT '1' COMMENT '1表示男,2表示女;默認值為1', `email` varchar(30) DEFAULT '', `role` tinyint(4) DEFAULT '0' COMMENT '角色,默認值為0,表示還沒有賦予角色', `status` tinyint(1) DEFAULT '1' COMMENT '1表示有效,0表示無效;默認值為1', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
執行的關鍵代碼:
String catalogName = resultSet.getString("TABLE_CAT"); String schemaName = resultSet.getString("TABLE_SCHEM"); String tableName = resultSet.getString("TABLE_NAME"); String columnName = resultSet.getString("COLUMN_NAME"); int dataType = Integer.parseInt(resultSet.getString("DATA_TYPE")); String commentString = resultSet.getString("REMARKS"); if (logger.isDebugEnabled()) { logger.debug(null); logger.debug("catalogName:" + catalogName + "; schemaName:" + schemaName + "; tableName:" + tableName + "; columnName:" + columnName + "; comment:" + commentString); }
為方便查看,去掉了log4j打印的標准前綴執行結果:
catalogName:test; schemaName:null; tableName:mybatis; columnName:id; comment: catalogName:test; schemaName:null; tableName:mybatis; columnName:username; comment: catalogName:test; schemaName:null; tableName:mybatis; columnName:password; comment: catalogName:test; schemaName:null; tableName:mybatis; columnName:sex; comment:1表示男,2表示女;默認值為1 catalogName:test; schemaName:null; tableName:mybatis; columnName:email; comment: catalogName:test; schemaName:null; tableName:mybatis; columnName:role; comment:角色,默認值為0,表示還沒有賦予角色 catalogName:test; schemaName:null; tableName:mybatis; columnName:status; comment:1表示有效,0表示無效;默認值為1
請注意:輸出結果中的數據 catalogName為test,而 schemaName為null。和第一小節中提到的內容不符合,也和mysql官網中的說明不符合(http://dev.mysql.com/doc/refman/5.1/zh/information-schema.html)。
有哪位高手了解原因還請告知!