作者:林冠宏 / 指尖下的幽靈
掘金:https://juejin.im/user/587f0dfe128fe100570ce2d8
博客:http://www.cnblogs.com/linguanh/
GitHub : https://github.com/af913337456/
一直以來,我都是極其反感寫重復的代碼,所以喜歡利用面向對象的編程屬性來自己造輪,或者是二次封裝。
前序
GreenDao
相信很多 Android
開發者都熟悉,不知為何物的,這里不會再介紹它,建議自行百度,介紹文很多。
前天我再次在項目中使用到 Sqlite
來做緩存,一般的代碼是下面這樣的。
Entity userInfo = schema.addEntity("UserEntity");
userInfo.setTableName("UserInfo");
userInfo.setClassNameDao("UserDao");
userInfo.setJavaPackage(entityPath);
userInfo.addIdProperty().autoincrement();
userInfo.addIntProperty("peerId").unique().notNull().index();
userInfo.addIntProperty("gender").notNull();
userInfo.addStringProperty("mainName").notNull();
userInfo.addStringProperty("pinyinName").notNull();
userInfo.addStringProperty("realName").notNull();
userInfo.addStringProperty("avatar").notNull();
userInfo.addStringProperty("phone").notNull();
userInfo.addStringProperty("email").notNull();
userInfo.addIntProperty("departmentId").notNull();
表結構有多少個字段就寫多少行,表多了還要分開寫。GreenDao
本身已經是很方便了,但我覺得還是不夠方便。所以有了下面的"故事"。閱讀完這個"故事",從此你使用 GreenDao 真正需要你手寫的將會單表是不超過10行!
思想
做過服務端開發的都知道,一般 C/S
通訊采用的數據結構是 Json
,當你們公司的后端人員做好了接口后,也會提供測試接口給前端開發者,因為我的APP接口一般也是我寫,所以我有這個習慣,所以,為何不采用 Json
的格式來動態生成 客戶端
所需要的所有類。故,選擇讀取Json
-
GreenDao 的默認 main 函數
public class dao {
public static void main(String[] args) throws Exception {
/** 你的生成邏輯代碼 */
}
}
-
解析JSON
由於上述是 Java 程序,所以不能使用 Android 的 Json 包,我們需要下面的幾個 Jar 包,他們的作用的,在 Java 程序了里面使用到 Json 的操作 API,我們可以在解析完之后就不再引用這些 Jar 包。文末會提供
dependencies {
...
compile files('libs/commons-beanutils-1.7.0.jar')
compile files('libs/commons-collections-3.2.jar')
compile files('libs/commons-lang-2.4.jar')
compile files('libs/commons-logging-1.0.4.jar')
compile files('libs/ezmorph-1.0.3.jar')
compile files('libs/json-lib-2.2.3-jdk15.jar')
}
-
核心函數
利用 Java 關鍵字 instanceof 針對從 Json 里面解析出來的 value 的不同類型來生成不同的屬性,Key 做字段名稱,例如 {"name":"lgh"}
,解析出來就是 name
作為字段名詞,由於 lgh
是字符串,所以對應的是字符串類型。
private static void createTable(
Schema schema,
String tableName, /** 表名 */
String idName, /** 索引 */
String json /** Json */
) {
Entity create = schema.addEntity(tableName);
JSONObject jsonObject = JSONObject.fromObject(json);
Iterator iterator = jsonObject.keys();
String key;
Object value;
while(iterator.hasNext()){ /** 遍歷 Json */
key = (String) iterator.next(); /** 字段名詞 */
value = jsonObject.get(key);
if(value instanceof Integer){
if(key.equals(idName)){
/** 源碼限制了,主鍵必需是 long 類型 */
create.addLongProperty(key).primaryKey().autoincrement();
continue;
}
create.addIntProperty(key);
} else if (value instanceof String){
create.addStringProperty(key).notNull();
} else if (value instanceof Float){
create.addFloatProperty(key).notNull();
} else if (value instanceof Double){
create.addDoubleProperty(key).notNull();
/** 其它類型,請自行模仿添加 */
} else{
/** 集合類型違反了表結構 */
throw new IllegalFormatFlagsException("集合類型違反了表結構");
}
}
create.setHasKeepSections(true);
}
-
一個例子
import net.sf.json.JSONObject;
import java.util.IllegalFormatFlagsException;
import java.util.Iterator;
import de.greenrobot.daogenerator.DaoGenerator;
import de.greenrobot.daogenerator.Entity;
import de.greenrobot.daogenerator.Schema;
/**
* 作者:林冠宏
*
* author: LinGuanHong ,lzq is my dear wife.
*
* My GitHub : https://github.com/af913337456/
*
* My Blog : http://www.cnblogs.com/linguanh/
*
* on 2017/3/21.
*
*/
/** 創建一張表,以及它的字段邏輯,你不再需要手動一個個寫,只需要傳入 json */
public class dao {
private final static String YourOutPutDirPath = "./greendaohelper/src/main/java";
private final static String YourOutPutDirName = "dao";
public static void main(String[] args) throws Exception {
Schema schema = new Schema(1, YourOutPutDirName);
String tableJson =
"{\n" +
" \"d_id\": 1278,\n" + /** 整數類型 */
" \"d_area\": \"美國\",\n" + /** 字符串 */
" \"d_content\": \"講述一個軍事英雄回到美國,隨之也帶來了很多麻煩。他將會和CTU組織合作,來救自己的命或者來拯救一起發生在美國本土的恐怖襲擊的故事。\",\n" +
" \"d_directed\": \"斯蒂芬·霍普金斯 / 強·卡薩 / 尼爾森·麥科米克 / 布朗溫·休斯\",\n" +
" \"d_dayhits\": \"2.3\",\n" + /** 浮點類型 */
" \"d_true_play_url\": \"xxx\"\n" +
" }";
createTable(
schema,
"pushVideo", /** 表名 */
"d_id", /** 主鍵名詞 */
tableJson
);
createTable(
schema,
"lghTable", /** 表名 */
"id", /** 主鍵名詞 */
"{\n" +
" \"id\": 1278,\n" + /** 整數類型 */
" \"name\": \"林冠宏\",\n" + /** 字符串 */
" \"address\": \"陽江市\",\n" +
" \"head_url\": \"xxxxxxxx\"\n" +
" }"
);
new DaoGenerator().generateAll(schema,YourOutPutDirPath);
}
/** o(n) */
/** 聚合索引之類的,可以自己重載此函數 */
private static void createTable(
Schema schema,
String tableName,
String idName,
String json
) {
...
}
}
-
運行結果
在指定的路徑/greendaohelper/src/main/java
下生成文件夾dao
,里面包含有
其中lghTable
和 pushVideo
就是我們生成的 Bean 類,Dao后綴的就是數據表配置類
事實證明,完美符合理想的結果 。
拓展
上述講述了如何自動快速地使用 Json 快速生成 Bean、表及其結構,我覺得還是不夠爽,能更點地調用就更過癮了。
-
公共的抽象
把 增、刪、改、查,采用泛型
抽象出來。
添加或更新一條
public void insertOrUpdateInfo(K entity){
T userDao = getWirteDao();
userDao.insertOrReplace(entity);
}
注意這個函數,它是標准的插入或更新一條數據,存在則更新,否則就是插入,兩個泛型類型 K
和 T
,K 是 Bean 類,就是上面生成的, T 是dao 數據表配置類,也是上面生成的。到了這里,就是說,傳入的泛型也是自動生成的,你完全不需要去手動打碼。
-
泛型約束
上面說的 T 泛型是屬於 Dao 的配置類,稍作代碼分析就可以看出,GreenDao 所有生成的數據表配置類都是繼承於 AbstractDao
類。
所以,操作抽象類長這樣
public abstract class DBInterface<K,T extends AbstractDao<K,Long>> {
...
}
-
完整例子
public abstract class DBInterface<K,T extends AbstractDao<K,Long>> {
private LghLogger lghLogger = LghLogger.getLogger(DBInterface.class);
private DBInit dbInit;
public DBInterface(){
/** 不引起 DBInit 的重復實例化 */
dbInit = DBInit.instance(); /** 初始化用的,這個類在后面提供 */
}
protected abstract T getWirteDao();
protected abstract T getReadDao();
protected abstract Property getIdProperty();
private void isInit(){
if(dbInit.getOpenHelper() == null){
throw new NullPointerException("openHelper is null!");
}
}
/**
* Query for readable DB
*/
protected DaoSession openReadableDb() {
isInit();
SQLiteDatabase db = dbInit.getOpenHelper().getReadableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
DaoSession daoSession = daoMaster.newSession();
return daoSession;
}
/**
* Query for writable DB
*/
protected DaoSession openWritableDb(){
isInit();
SQLiteDatabase db = dbInit.getOpenHelper().getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
DaoSession daoSession = daoMaster.newSession();
return daoSession;
}
/** 增 */
public void insertOrUpdateInfo(K entity){
T userDao = getWirteDao();
userDao.insertOrReplace(entity);
}
public void batchInsertOrUpdateAllInfo(List<K> entityList){
if(entityList.size() <=0 ){
lghLogger.d("本地數據庫插入用戶信息失敗,條數是 0 ");
return ;
}
T userDao = getWirteDao();
userDao.insertOrReplaceInTx(entityList);
}
/** 刪 */
public void deleteOneById(int id){
T userDao = getWirteDao();
DeleteQuery<K> bd = userDao.queryBuilder()
.where(getIdProperty().eq(id))
.buildDelete();
bd.executeDeleteWithoutDetachingEntities();
}
public void deleteAllBeans(){
T userDao = getWirteDao();
DeleteQuery<K> bd = userDao.queryBuilder()
.buildDelete();
bd.executeDeleteWithoutDetachingEntities();
}
/** 改 */
/** 在查里面,因為本身就是 insertOrUpdate */
/** 查,加入了模糊查找 */
public K getBeanById(int id){
T dao = getReadDao();
return dao
.queryBuilder()
.where(getIdProperty().eq(id)).unique();
}
public K getBeanWithLike(Property property,String what){
T dao = getReadDao();
return dao
.queryBuilder()
.where(property.like("%"+what+"%")).unique();
}
public List<K> loadAllBeans(){
T dao = getReadDao();
/** 倒敘 */
return dao.queryBuilder().orderDesc(getIdProperty()).list();
}
public List<K> loadAllBeansWithLike(Property property, String what){
T dao = getReadDao();
return dao
.queryBuilder()
.where(property.like("%"+what+"%")).orderAsc(getIdProperty()).list();
}
}
-
DBInit.java 負責初始化,靜態內部類單例,避免了反復創建對象
public class DBInit {
private LghLogger lghLogger = LghLogger.getLogger(DBInit.class);
private int loginUserId = 0;
private DaoMaster.DevOpenHelper openHelper;
public static DBInit instance(){
return DBHelper.dbInit;
}
/** 私有 */
private DBInit(){
lghLogger.d("初始化 dbinit");
initDbHelp(LghApp.context,1); /** 可以自己遷移初始化位置 */
}
public DaoMaster.DevOpenHelper getOpenHelper(){
return openHelper;
}
private static class DBHelper{
private static DBInit dbInit = new DBInit();
}
/** 十分建議使用 Application 的 context
* 支持用用戶的 ID 區分數據表
* */
public void initDbHelp(Context ctx, int loginId){
if(ctx == null || loginId <=0 ){
throw new RuntimeException("#DBInterface# init DB exception!");
}
/** 切換用戶的時候, openHelper 不是 null */
String DBName = "lgh_"+loginId+".db";
if(openHelper!=null){
/** 判斷下 db name 是不是一樣的,不是一樣就重置 */
String dbNameTemp = openHelper.getDatabaseName().trim();
if(dbNameTemp.equals(DBName)){
lghLogger.d("相同的用戶,不用重新初始化本地 DB");
return;
}else{
lghLogger.d("不是相同的用戶,需要重新初始化本地 DB");
openHelper.close();
openHelper = null;
}
}
if(loginUserId !=loginId ){
loginUserId = loginId;
close();
lghLogger.d("DB init,loginId: "+loginId);
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(ctx, DBName, null);
this.openHelper = helper;
}else{
lghLogger.d("DB init,failed: "+loginId);
}
}
private void close() {
if(openHelper !=null) {
lghLogger.d("關閉數據庫接口類");
openHelper.close();
openHelper = null;
loginUserId = 0;
}
}
}
-
使用
有了上面的准備,就可以使用了,正在需要自己動手的代碼幾乎沒有。下面我們建一個操作類型的子類VideoInfoDbCache
,集成於 DBInterface
,重寫完三個抽象函數后,就是下面這樣。
public class VideoInfoDbCache
extends
DBInterface<pushVideo, pushVideoDao> {
@Override
protected pushVideoDao getWirteDao() {
return openWritableDb().getPushVideoDao(); /** 該函數由 GreenDao 提供,不用自己編寫 */
}
@Override
protected pushVideoDao getReadDao() {
return openReadableDb().getPushVideoDao(); /** 該函數由 GreenDao 提供,不用自己編寫 */
}
@Override
protected Property getIdProperty() {
return pushVideoDao.Properties.D_id; /** 自定義的拓展,這里獲取了一般的 id 作為主屬性 */
}
}
現在我們看看 MainActivity 里面的使用。直接采用匿名對象,直接 new,直接用。
public class MainActivity extends AppCompatActivity {
List<pushVideo> list;
List<lghTable> lghList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
...
list = new VideoInfoDbCache().loadAllBeans();
list = new VideoInfoDbCache().loadAllBeans();
list = new VideoInfoDbCache().loadAllBeans();
list = new VideoInfoDbCache().loadAllBeans();
list = new VideoInfoDbCache().loadAllBeans();
list = new VideoInfoDbCache().loadAllBeans();
lghList = new LghTableDbCache().loadAllBeans();
lghList = new LghTableDbCache().loadAllBeansWithLike(
lghTableDao.Properties.Name,"林冠宏"
);
...
}
}
現在,夠快了吧?還不夠?您請留言,我補刀。
開源地址 https://github.com/af913337456/GreenDaoHelper/
提示:在編譯APP的時候,最好把上述的 Java 程序的 json jar 包全部不再引用,而且注釋 dao.java 文件,然后刪除一次 greenDaoHelper library下的build文件夾,即可!