GreenDao 工具類 --- 使用 Json 快速生成 Bean、表及其結構,"炒雞"快!

林冠巨集發表於2017-03-21

作者:林冠巨集 / 指尖下的幽靈
掘金:juejin.im/user/587f0d…
部落格:www.cnblogs.com/linguanh/
GitHub : 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,裡面包含有
    GreenDao 工具類 --- 使用 Json 快速生成 Bean、表及其結構,"炒雞"快!

其中lghTablepushVideo 就是我們生成的 Bean 類,Dao字尾的就是資料表配置類
事實證明,完美符合理想的結果 。

擴充

上述講述瞭如何自動快速地使用 Json 快速生成 Bean、表及其結構,我覺得還是不夠爽,能更點地呼叫就更過癮了。

  • 公共的抽象

    把 增、刪、改、查,採用泛型抽象出來。

新增或更新一條

public void insertOrUpdateInfo(K entity){
    T userDao = getWirteDao();
    userDao.insertOrReplace(entity);
}複製程式碼

注意這個函式,它是標準的插入或更新一條資料,存在則更新,否則就是插入,兩個泛型型別 KT,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,"林冠巨集"
        );
        ...
    }
}複製程式碼

現在,夠快了吧?還不夠?您請留言,我補刀。

開源地址 github.com/af913337456…
提示:在編譯APP的時候,最好把上述的 Java 程式的 json jar 包全部不再引用,而且註釋 dao.java 檔案,然後刪除一次 greenDaoHelper library下的build資料夾,即可!

相關文章