在上一篇博客[《打造android ORM框架opendroid(一)——ORM框架的使用》](http://blog.csdn.net/qibin0506/article/details/42736807)中相信你已經了解了opendroid的使用,那么從這篇博客開始,我們正式進入opendroid的源碼分析,打造一款自己的ORM框架!
在正式開始之前,你需要保證手里有一份opendroid的源碼,如果還沒下載opendroid,請到[http://git.oschina.net/qibin/OpenDroid](http://git.oschina.net/qibin/OpenDroid)?下載opendroid的源碼。
任何數據庫操作都是從創建數據庫開始的,今天我們就來看看opendroid是怎么幫我們自動創建數據庫的。 還記得關系映射怎么配置嗎? 在open-droid.xml中,通過配置mapping節點來告訴opendroid我們需要映射的java bean。那么數據庫操作是從何時開始的呢, 拿insert來說,就是調用了從OpenDroid繼承而來的save()方法!在這之前,我們沒有任何數據庫方面的操作,那么我們就從save()方法開始,看看opendroid是怎么創建數據庫的。
~~~
/**?
?*?插入數據?
?*?@return?最后插入的id?
*/??
public?long?save()?{??
????try?{??
????????Class?klass?=?(Class)?getClass();??
????????ContentValues?cv?=?new?ContentValues();??
????????generateData(klass,?cv);??
??????????????
????????return?CRUD.insert(klass.getSimpleName(),?cv,?sSqliteDatabase);??
????}?catch?(Exception?e)?{??
????????e.printStackTrace();??
????}??
????return?-1;??
}??
~~~
第11行,通過調用了CRUD的一個靜態方法insert將數據保存到數據庫中,insert的最后一個參數sSqliteDatabas是我們關心的,來看看它的定義:
~~~
private?static?SQLiteDatabase?sSqliteDatabase?=?sOpenHelper.getWritableDatabase();??
~~~
sSqliteDatabase是通過sOpenHelper調用getWriteableDatabase()返回的,相信這里大家應該非常熟悉了,再來看看sOpenHelper的定義:
` private?static?CreateDB?sOpenHelper?=?new?CreateDB();??`
在這里直接new了一個CreateDB,通過類名我們完全可以知道CreateDB就是創建數據庫的關鍵。來看看CreateDB吧:
~~~
public?class?CreateDB?extends?SQLiteOpenHelper?{??
??????
????public?CreateDB()?{??
????????super(DroidApplication.sContext,?OpenDroidHelper.getDBInfoBean().getName(),??
????????????????null,?OpenDroidHelper.getDBInfoBean().getVersion(),?new?DefaultDatabaseErrorHandler());??
????}??
??
????@Override??
????public?void?onCreate(SQLiteDatabase?db)?{??
????????for(String?sql?:?OpenDroidHelper.getDBInfoBean().getSqls())?{??
????????????db.execSQL(sql);??
????????}??
????}??
}??
~~~
這里我只截取了和創建數據庫有關的代碼, 可以看到CreateDB繼承自SQLiteOpenHelper,這里大家肯定也很熟悉,先從構造方法開始看, 在構造方法中直接調用了父類的構造方法,第一個參數是一個context對象,這個對象是在DroidApplication中,其實很簡單,就是在onCreate中調用getApplicationContext()為DroidApplication中的sContext靜態變量賦值,這里就不貼代碼了,可以在源碼中找到,在看看接下來幾個參數,都是通過OpenDroidHelper.getDBInfoBean獲取的。再往后看看發現onCreate中也是通過遍歷OpenDroidHelper.getDBInfoBean().getSqls()來獲取創建表的sql語句,那么現在我們就去OpenDroidHelper看看吧。
~~~
public?class?OpenDroidHelper?{??
????public?static?final?String?TAG_DROID?=?"open-droid";??
????public?static?final?String?TAG_VERSION?=?"version";??
????public?static?final?String?TAG_NAME?=?"name";??
????public?static?final?String?TAG_MAPPING?=?"mapping";??
??????
????private?static?DBBean?sDBBean;??
??????
????public?static?DBBean?getDBInfoBean()?{??
????????if(sDBBean?==?null)?{??
????????????generateDBInfoBean();??
????????}??
??????????
????????return?sDBBean;??
????}??
??????
????/**?
?????*?解析Asserts目錄下的open_droid.xml文件,生成DBInfoBean?
?????*/??
????private?static?void?generateDBInfoBean()?{??
????????try?{??
????????????XmlPullParser?pullParser?=?Xml.newPullParser();??
????????????InputStream?inputStream?=?DroidApplication.sContext.getAssets().open("open_droid.xml");??
????????????pullParser.setInput(inputStream,?"utf-8");??
??????????????
????????????int?type?=?pullParser.getEventType();??
????????????String?tagName?=?null;??
??????????????
????????????while(type?!=?XmlPullParser.END_DOCUMENT)?{??
????????????????if(type?==?XmlPullParser.START_TAG)?{??
????????????????????tagName?=?pullParser.getName();??
????????????????????if(tagName.equals(TAG_DROID))?{??
????????????????????????sDBBean?=?new?DBBean();??
????????????????????}else?if(tagName.equals(TAG_VERSION))?{??
????????????????????????//?獲取版本號??
????????????????????????sDBBean.setVersion(Integer.parseInt(pullParser.getAttributeValue(null,?"value")));??
????????????????????}else?if(tagName.equals(TAG_NAME))?{??
????????????????????????//?獲取數據庫名??
????????????????????????sDBBean.setName(pullParser.getAttributeValue(null,?"value"));??
????????????????????}else?if(tagName.equals(TAG_MAPPING))?{??
????????????????????????//?獲取所有建表語句??
????????????????????????sDBBean.addSql(generateSql(pullParser));??
????????????????????}??
????????????????}??
????????????????type?=?pullParser.next();??
????????????}??
????????}?catch?(Exception?e)?{??
????????????e.printStackTrace();??
????????}??
????}??
??
????/**?
?????*?生成建表sql語句?
?????*?@param?pullParser?
?????*?@return?
?????*?@throws?ClassNotFoundException?
?????*?@throws?XmlPullParserException?
?????*?@throws?IOException?
?????*/??
????private?static?String?generateSql(XmlPullParser?pullParser)??
????????????throws?ClassNotFoundException,?XmlPullParserException,?IOException?{??
????????//?反射獲取class??
????????Class?klass?=?(Class)?Class.forName(pullParser.getAttributeValue(null,?"class"));??
??????????
????????StringBuilder?sql?=?new?StringBuilder("create?table?");??
????????//?獲取類名,?getSimpleName獲取類名,?getName()獲取包名+類名??
????????sql.append(klass.getSimpleName()).append("(");??
????????//?自動創建一個_id??
????????sql.append("_id?integer?primary?key?autoincrement,");??
??????????
????????//?獲取所有的字段??
????????Field[]?fields?=?klass.getDeclaredFields();??
????????for(Field?field?:?fields)?{??
????????????//?如果是public的,?則表示不是一個表的字段??
????????????if(field.isAccessible())?{??
????????????????continue;??
????????????}??
??????????????
????????????//?獲取字段名??
????????????String?name?=?field.getName();??
????????????sql.append(name).append("?");??
??????????????
????????????//?獲取字段類型??
????????????Class?fieldType?=?field.getType();??
????????????if(fieldType?==?String.class)?{??//?如果是String??
????????????????sql.append("text,");??
????????????}else?if(fieldType?==?Integer.class?||?fieldType?==?int.class)?{??
????????????????sql.append("integer,");??
????????????}else?if(fieldType?==?Long.class?||?fieldType?==?long.class){??
????????????????sql.append("integer,");??
????????????}else?if(fieldType?==?Boolean.class?||?fieldType?==?boolean.class)?{??
????????????????sql.append("boolean,");??
????????????}else?if(fieldType?==?Float.class?||?fieldType?==?float.class)?{??
????????????????sql.append("float,");??
????????????}??
????????}??
????????sql.replace(sql.length()?-?1,?sql.length(),?"");??
????????sql.append(");");??
??????????
????????return?sql.toString();??
????}??
}??
~~~
額,代碼有點小長, 我們慢慢來看。首先來看看我們之前調用的getDBInfoBean(),這個方法很簡單,就是返回了一個DBBean對象,不過,它還調用了generateDBInfoBean()方法,通過方法名可以看出,它的作用是生成DBBean的。
~~~
/**?
*?解析Asserts目錄下的open_droid.xml文件,生成DBInfoBean?
*/??
private?static?void?generateDBInfoBean()?{??
????try?{??
????????XmlPullParser?pullParser?=?Xml.newPullParser();??
????????InputStream?inputStream?=?DroidApplication.sContext.getAssets().open("open_droid.xml");??
????????pullParser.setInput(inputStream,?"utf-8");??
??????????????
????????int?type?=?pullParser.getEventType();??
????????String?tagName?=?null;??
??????????????
????????while(type?!=?XmlPullParser.END_DOCUMENT)?{??
????????????if(type?==?XmlPullParser.START_TAG)?{??
????????????????tagName?=?pullParser.getName();??
????????????????if(tagName.equals(TAG_DROID))?{??
????????????????????sDBBean?=?new?DBBean();??
????????????????}else?if(tagName.equals(TAG_VERSION))?{??
????????????????????//?獲取版本號??
????????????????????sDBBean.setVersion(Integer.parseInt(pullParser.getAttributeValue(null,?"value")));??
????????????????}else?if(tagName.equals(TAG_NAME))?{??
????????????????????//?獲取數據庫名??
????????????????????sDBBean.setName(pullParser.getAttributeValue(null,?"value"));??
????????????????}else?if(tagName.equals(TAG_MAPPING))?{??
????????????????????//?獲取所有建表語句??
????????????????????sDBBean.addSql(generateSql(pullParser));??
????????????????}??
????????????}??
????????????type?=?pullParser.next();??
????????}??
????}?catch?(Exception?e)?{??
????????e.printStackTrace();??
????}??
}??
~~~
恩,在generateDBInfoBean這個方法中,都是我們熟悉的XMLPullParser的代碼,作用就是去解析open-droid.xml文件,獲取數據庫名稱、數據庫版本和數據表的信息。雖然很長,但是都很簡單,20行,我們獲取了數據庫的版本號,并保存到了DBBean中,同樣的23行獲取了數據庫的名稱,注意第26行,我們想DBBean中添加的sql語句,那添加的什么sql語句呢? 肯定是建表的sql語句了。來看看generateSql()方法。
~~~
/**?
?*?生成建表sql語句?
?*?@param?pullParser?
?*?@return?
?*?@throws?ClassNotFoundException?
?*?@throws?XmlPullParserException?
?*?@throws?IOException?
?*/??
private?static?String?generateSql(XmlPullParser?pullParser)??
????????throws?ClassNotFoundException,?XmlPullParserException,?IOException?{??
????//?反射獲取class??
????Class?klass?=?(Class)?Class.forName(pullParser.getAttributeValue(null,?"class"));??
??????????
????StringBuilder?sql?=?new?StringBuilder("create?table?");??
????//?獲取類名,?getSimpleName獲取類名,?getName()獲取包名+類名??
????sql.append(klass.getSimpleName()).append("(");??
????//?自動創建一個_id??
????sql.append("_id?integer?primary?key?autoincrement,");??
??????????
????//?獲取所有的字段??
????Field[]?fields?=?klass.getDeclaredFields();??
????for(Field?field?:?fields)?{??
????????//?如果是public的,?則表示不是一個表的字段??
????????if(field.isAccessible())?{??
????????????continue;??
????????}??
??????????????
????????//?獲取字段名??
????????String?name?=?field.getName();??
????????sql.append(name).append("?");??
??????????????
????????//?獲取字段類型??
????????Class?fieldType?=?field.getType();??
????????if(fieldType?==?String.class)?{??//?如果是String??
????????????sql.append("text,");??
????????}else?if(fieldType?==?Integer.class?||?fieldType?==?int.class)?{??
????????????sql.append("integer,");??
????????}else?if(fieldType?==?Long.class?||?fieldType?==?long.class){??
????????????sql.append("integer,");??
????????}else?if(fieldType?==?Boolean.class?||?fieldType?==?boolean.class)?{??
????????????sql.append("boolean,");??
????????}else?if(fieldType?==?Float.class?||?fieldType?==?float.class)?{??
????????????sql.append("float,");??
????????}??
????}??
????sql.replace(sql.length()?-?1,?sql.length(),?"");??
????sql.append(");");??
??????????
????return?sql.toString();??
}??
~~~
generateSql()里面全是反射的代碼,如果你對反射還不熟悉,建議你先去看看java反射,因為opendroid中大量使用了反射機制,
12行,通過反射獲取我們要映射的class,然后14~18行,是初始化創建表的sql語句,并且可以看到opendroid會自動為我們添加一個_id字段,所以在定義bean的時候,我們不需要再次定義了。
21行,獲取了這個類中定義的所有字段,并在22行循環遍歷這些字段。
14~26行,可以看到,如果字段是public的,那就忽略它,所以如果你不想把某個字段映射到數據庫中,就定義成public的。
29~30行,是向創建表的sql語句中追加表名。
33~44行,通過判斷這個字段類型,來向創建表的sql語句中追加該字段的類型。
46行的作用是刪除最后一個的“,”
47行,就完成了該表的創建sql語句。
至此,opendroid的自動創建數據庫的流程我們就分析完了,現在來總結一下:
1、數據庫的創建我們借助了android API的SQLiteOpenHelper。
2、在SQLiteOpenHelper的onCreate中遍歷我們自動生成的建表語句并執行。
3、如何生成建表語句? 在OpenDroidHelper中通過反射獲取類的類名和字段名,并通過StringBuilder來拼湊建表語句。
4、為了方便,我們在OpenDroidHelper中將解析出來的數據名、數據庫版本和建表語句都放到了DBBean中,那么我們在CreateDB類中就可以通過DBBean方便的獲取數據庫的信息了。
ok,數據庫的自動創建大體流程就是這樣,在接下來的博客中,我們還會去一一介紹opendroid的CRUD操作和它的數據庫升級機制。
馬上繼續[《打造android ORM框架opendroid(三)——持久化數據》](http://blog.csdn.net/qibin0506/article/details/42872361)
opendroid的開源地址:[http://git.oschina.net/qibin/OpenDroid](http://git.oschina.net/qibin/OpenDroid)