Android数据库设计——3,自动化 分库分表

Posted 夜辉疾风

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
    

以上是关于Android数据库设计——3,自动化 分库分表的主要内容,如果未能解决你的问题,请参考以下文章

数据库相关面试题

如何设计可以动态扩容缩容的分库分表方案?

分库分表设计时,需要避开哪些坑?

如何设计可以动态扩容缩容的分库分表方案?

一个关于 MySQL 分库分表的面试 3 连炮!

JavaP6大纲MySQL篇:如何设计可以动态扩容缩容的分库分表方案?