Android數據庫設計——3,自動化 分庫、分表

分庫、分表

用戶量大的時候必須去分庫分表,分庫分表也需要自動化

分庫

前置工作

/**
 * Describe:修改註解,增加主鍵標識
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbField {
    String value();//表列名
    boolean isPrimary() default false;//是否是主鍵
}
public class BaseDao<T> implements IBaseDao<T> {	
	//獲得當前表的唯一標識,一般爲主鍵
    //這裏查詢到當前表主鍵對應的值,傳遞進來的entityClass是帶有唯一標識的,這裏將它找出來
    public String getPrimary(T entity) {
        for (Map.Entry<String, Field> entry : cacheMap.entrySet()) {
            Field cacheField = entry.getValue();
            DbField dbField = cacheField.getAnnotation(DbField.class);
            //當前表列名是主鍵
            if (dbField != null && dbField.isPrimary()) {
                try {
                    //得到主鍵值
                    return cacheField.get(entity).toString();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return "";
    }
}
/**
 * Describe:數據庫分庫的工廠類
 */
public class BaseDaoSubFactory extends BaseDaoFactory {

 	//單例
    private static final BaseDaoSubFactory instance = new BaseDaoSubFactory();

    public static BaseDaoSubFactory getInstance() {
        return instance;
    }

    //數據庫文件統一存放路徑
    private static final String def_db_path = "data/data/" + BuildConfig.APPLICATION_ID;

    //數據庫連接池,緩存分庫的連接池
    private Map<String, BaseDao> dbGroup = Collections.synchronizedMap(new HashMap<String, BaseDao>());


    /**
     * 獲得分庫的操作dao
     *
     * @param daoClass    dao的class對象
     * @param entityClass 對應表的class對象
     * @param <T>         繼承basedao的dao對象的類型
     * @param <M>         傳遞進dao的表對應的java實體類的類型
     * @param m           傳遞進dao的表對應的java實體類,必須包含唯一約束的主鍵
     * @return 數據庫分庫操作dao對象
     */
    public <T extends BaseDao<M>, M> T getSubDao(Class<T> daoClass, Class<M> entityClass, M m) {
        //在主數據庫裏先獲取到basedao對象
        T baseDao = BaseDaoSubFactory.getInstance().getBaseDao(daoClass, entityClass);
        //在主數據庫裏獲取到了對象才能創建分庫
        if (baseDao != null) {
            //創建分庫數據庫
            String dbPath = getSeparateTablePath(baseDao, m);
            //如果緩存有,則直接取緩存中的數據庫操作對象
            if (dbGroup.get(dbPath) != null) {
                return (T) dbGroup.get(dbPath);
            }
            //沒有緩存,則創建
            SQLiteDatabase sqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
            try {
                //獲得dao的對象,並初始化
                baseDao = daoClass.newInstance();
                baseDao.init(sqLiteDatabase, entityClass);
                //添加緩存
                dbGroup.put(dbPath, baseDao);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return baseDao;
        }
        return null;
    }

    //創建當前用戶的分數據庫路徑
    public <M> String getSeparateTablePath(BaseDao<M> baseDao, M m) {
        String separateTablePath = "";
        //創建分庫的唯一標識
        String primary = baseDao.getPrimary(m);
        //獲取到了唯一標識,創建數據庫文件
        if (!TextUtils.isEmpty(primary)) {
            File file = new File(def_db_path);
            boolean iscreate = true;
            //不存在默認的數據庫則創建
            if (!file.exists()) {
                //創建成功
                if (!file.mkdirs()) {
                    iscreate = false;
                }
            }
            if (iscreate) {
                //如果數據庫根目錄存在,則返回分庫路徑
                //這樣,每一個表都有一個單獨的數據庫。例如,用戶表,根據用戶id將每個用戶分到獨立的數據庫中,這個數據庫中只存在當前用戶的一些列表信息
                separateTablePath = file.getAbsolutePath() + "/" + baseDao.getTableName() + "_" + primary + ".db";
            }
        }
        return separateTablePath;
    }
}

注意:

對於前端應用來說,最好的方式就是一個用戶一個數據庫,以爲前端用戶少,一個用戶一個庫便於操作。

對於服務器來說,則需要定製一套規則。例如,一千萬用戶一個庫,1-10000000id的用戶分爲庫0,以此類推。那麼這一千萬用戶就有兩個id,主庫id(隨着用戶量的增長而增長,0~+∞)、分庫id(0~10000000,以此類推),並且主庫id和分庫id不可以一起查,因爲他們之間是沒有關聯的。

分表

當我們的數據量上升到一定大小的時候,操作速度就會明顯受影響,所以要根據有規律的唯一標識符按一定規則來分表,規則是靈活的。

例如:

某一個用戶操作表,需要根據用戶id來查到該用戶在這張表中的操作記錄。

如果這張操作表記錄了非常多的用戶操作,那麼我們就可以根據一定的特性來進行分表。

分表的本質和分庫一致。

例如,操作表中,用戶id是唯一的或者操作id是唯一的。那麼我們就可以根據唯一標識符來進行分表。

比如我們根據用戶id結尾的數字就可以分出十張表。0-9的十張操作表。這十張表分別存用戶id結尾爲0-9的操作記錄。

如果十張表不夠用,則我們可以將0-9的id轉成16進制,這樣就可以有十六張表。

如果十六張表不夠用,則我們可以根據用戶id的後兩位,也就是00-99分成10張表。

以此類推。

這,就是分表。

/**
 * Describe:用戶頭像表,有一百張,根據用戶id的後兩位數來分
 */
@DbTable("tb_user_img")
public class UserImg {
     private Long imgId;
    @DbField("_id")
    private Long userId;//用戶id,更嚴格的說,這裏應該指向user表的id,它是一個外鍵
    private String time;//創建時間
    private String imgPath;//頭像路徑
	
    //獲得用戶id的後兩位
    public int getUserIdSuffix() {
        if (userId < 100) {
            return userId.intValue();
        } else {
            String idStr = userId + "";
            return Integer.parseInt(idStr.substring(idStr.length() + 2));
        }
    }
 
/**
 * Describe:用戶頭像,分了一百張,規則是用戶id的後兩位00-99
 */
public class PhotoDao extends BaseDao<UserImg> {
    
 	//重寫初始化方法,在初始化數據庫操作對象的時候創建一百張表
    @Override
    public void init(SQLiteDatabase sqLiteDatabase, Class<UserImg> entityClass) {
//        super.init(sqLiteDatabase,entityClass);
        this.sqLiteDatabase = sqLiteDatabase;
        this.entityClass = entityClass;
        if (!isInit) {
            //通過反射和註解獲得表名
            //自動建表
            //如果沒有添加註解,則通過反射去拿表明
            DbTable dbTable = entityClass.getAnnotation(DbTable.class);
            if (dbTable != null) {
                //註解拿表名
                this.tableName = dbTable.value();
            } else {
                //反射拿表名
                this.tableName = entityClass.getSimpleName();
            }
            //這裏開始建一百張表
            for (int i = 0; i < 100; i++) {
                //創建表的sql語句
                String createTableSql = getCreateTableSql(tableName + getTableSuffix(i));
                this.sqLiteDatabase.execSQL(createTableSql);
            }
            initCacheMap();
            isInit = true;
        }
    }

    //實際查詢,根據用戶id先定位到哪張表再查詢
    @Override
    public List<UserImg> queryByWhere(UserImg where) {
        //準備好條件語句
        Map<String, String> values = getValues(where);
        Condition condition = new Condition(values);
        String photoName = tableName + getTableSuffix(where.getUserIdSuffix());
        Cursor cursor = sqLiteDatabase.query(photoName, null, condition.whereClause, condition.whereArgs, null, null, null, null);
        List<UserImg> resultList = getResult(cursor, where);
        cursor.close();
        return resultList;
    }
    
    //處理分表的後綴
    private String getTableSuffix(int i) {
        return (i < 10 ? "0" : "") + i;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章