Android手写数据库框架设计(增)

一、概述

Android数据库在存储数据方面很重要,我们项目当中一般用的SQLiteOpenHelper这个类进行数据库的创建,升级,然后在用数据库操作类(譬如UserDao)进行增删改查,整个设计都是面向过程的,可扩展性不高,现在我们以面向对象的角度来设计数据库。
整个设计所设计到的知识点:
泛型 注解 反射
数据库语句拼接
设计模式
1、单例模式 2、简单工厂模式 3、模板方法模式
对于简单工厂模式和模板方法模式不太懂的可以看我的这两篇博客
简单工厂模式
模板方法模式

二、整个的一个设计思路

我用一张图表示:

思路图.PNG

对于这张图,我解释几点
1、整张图就是调用端activity通过调用BaseDaoFactory实例化数据库操作类(userDao)插入一条数据。
2、调用层不关系数据库的创建在哪里创建,不关心表的创建,不关心sql语句的拼写。
3、BaseDaoFactory是创建BaseDao的工厂类,用到了工厂设计模式,其本身又是一个单例模式,BaseDao里面提供了创建表的语句,由具体操作数据库的子类去实现,这正体现了模板方法模式。
4、对象的成员变量和表的列名是一一对应关系,不是说表的字段名就一定是成员变量名称,用户可以通过注解设置在要操作的类的成员变量上,以此来产生映射关系。

三、具体代码细节

IBaseDao接口,提供数据库操作的方法(增删改查)

public interface IBaseDao<T> {
    //插入数据
    Long insert(T t);

    Long update(T entity,T where);
}

插入的数据不知道,用泛型表示
我们看他的实现类,BaseDao

public abstract class BaseDao<T> implements IBaseDao<T> {

    private SQLiteDatabase mDatabase;

    private boolean isInit = false; //保证实例化一次

    //持有操作数据库表对应的Java类型
    private Class<T> entityClass;

    //维护这表名与成员变量名的映射关系
    private HashMap<String, Field> cacheMap;

    //表名
    private String tableName;

    protected synchronized boolean init(Class<T> entity, SQLiteDatabase sqLiteDatabase) {
        if (!isInit) {
            this.entityClass = entity; //给操作数据库表对应的类型赋值
            mDatabase = sqLiteDatabase;
            if (entityClass.getAnnotation(DbTable.class).value() == null) {  1
                tableName = entityClass.getSimpleName();
            } else {
                tableName = entityClass.getAnnotation(DbTable.class).value();
            }
            if (!mDatabase.isOpen()) {
                return false;
            }
            if (!TextUtils.isEmpty(createTable())) { 2  //创建表的语句
                sqLiteDatabase.execSQL(createTable()); //创建表
            }
            //实体类的成员变量和表的字段名的映射关系的缓存集合
            cacheMap = new HashMap<>();
            initCacheMap(); 3
            isInit = true;
        }
        return isInit;
    }

    //维护映射关系
    private void initCacheMap() {
        String sql = "select * from " + this.tableName + " limit 1 , 0";
        Cursor cursor = null;
        try {
            cursor = mDatabase.rawQuery(sql, null);
            String[] columns = cursor.getColumnNames(); //表的列名数组
            //拿到fields数组
            Field[] columsFileds = entityClass.getFields();
            for (Field field : columsFileds) {
                field.setAccessible(true); //暴力反射
            }
            //开始找对应关系
            for (String columsName : columns) {
                Field columFiled = null;
                for (Field filed : columsFileds) {
                    String fieldName = null;
                    if (!TextUtils.isEmpty(filed.getAnnotation(DbFiled.class).value())) {
                        //如果字段上面有对应的数据库列名
                        fieldName = filed.getAnnotation(DbFiled.class).value();
                    } else {
                        fieldName = filed.getName();
                    }
                    //如果表的列名等于了成员变量的注解的名字或者成员变量的名字
                    if (fieldName.equals(columsName)) {
                        columFiled = filed;  //赋值
                        break;
                    }
                }
                //找到了对应关系
                if (columFiled != null) {
                    cacheMap.put(columsName, columFiled);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            cursor.close();
        }
    }

    //将T entity转换为map
    public Map<String, String> getValues(T entity) {
        HashMap<String, String> result = new HashMap<>();
        Iterator<Field> fieldIterator = cacheMap.values().iterator();
        // 循环遍历 映射map的  Filed
        while (fieldIterator.hasNext()) {
            Field colmunToFiled = fieldIterator.next();
            String cacheKey = null;
            String cacheValue = null;
            if (colmunToFiled.getAnnotation(DbFiled.class).value() != null) {
                cacheKey = colmunToFiled.getAnnotation(DbFiled.class).value();
            } else {
                cacheKey = colmunToFiled.getName();
            }

            try {
                if (null == colmunToFiled.get(entity)) {
                    continue;
                }
                cacheValue = colmunToFiled.get(entity).toString();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            result.put(cacheKey, cacheValue);
        }
        return result;
    }

    /**
     * 将map 转换为ContentValues
     *
     * @return
     */
    public ContentValues getContentValues(Map<String, String> map) {
        ContentValues contentValues = new ContentValues();
        Set keys = map.keySet();
        Iterator<String> iterator = keys.iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            String value = map.get(key);
            if (value != null) {
                contentValues.put(key, value);
            }
        }
        return contentValues;
    }

    //创建数据库表的语句
    public abstract String createTable(); 6

    @Override
    public Long insert(T entity) {
        5
        Map<String,String> map = getValues(entity);
        ContentValues contentValues = getContentValues(map);
        Long result = mDatabase.insert(tableName,null,contentValues);
        return result;
    }

    @Override
    public Long update(T entity, T where) {
        return null;
    }
}

分析下这个类,主要功能都集中在init方法里面。

在代码1处,通过if判断当期类有没有加上表名的注解,给表名tabName赋值。

代码2处通过判断创建表的语句是否为空,来创建表,createTable方法给具体子类实现,代码在6处。

在代码4处,初始化hashMap维护实体bean类的成员变量和表的字段名的映射关系,key为String类型,value为Filed类型。具体的逻辑请看代码。

在代码5处就是将map集合字段名和字段值转换为ContentValues,然后进行插入数据库操作。

这个类分析大概就这么多,涉及到的其他类的代码我这里贴一下
DbTable注解类

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbTable {
    String value();
}

这个类就是给实体类指定一个表名没有指定默认为类名,譬如这里的User类

@DbTable("tb_user")
public class User {

    public User(String name,String password){
        this.password = password;
        this.name = name;
    }

    public User(){

    }

    @DbFiled("password")
    public String password;

    @DbFiled("name")
    public String name;
}

DbTable指定表名通过这个注解和数据库表名产生映射关系,这个User类的成员变量用到了DbFiled注解,用于指定字段名。贴代码:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbFiled {
    String value();
}

这是数据库操作基类,我们接着看具体的操作类UserDao


public class UserDao<User> extends BaseDao<User>{
    @Override
    public String createTable() {
        return "create table if not exists tb_user(name varchar(20),password varchar(10))";
    }
}

这个类主要是就是返回了创建数据库表的语句,逻辑都是父类给处理了,指定了泛型。

好,我们最后看下数据库操作工厂类

/**
 * Created by dell on 2017/7/16.
 * 数据库操作工程类
 */

public class BaseDaoFactory {
    private String mSqlitePath;
    private SQLiteDatabase mSqliteDatabase;
    private static BaseDaoFactory instance = new BaseDaoFactory(); 1

    public BaseDaoFactory() {
        mSqlitePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/student.db";2
        openDataBase();3
    }

    public synchronized <T extends BaseDao<M>,M> T getDataHelper(Class<T> clazz,Class<M> entityClass){
         BaseDao baseDao = null;
        try {
            baseDao = clazz.newInstance();
            baseDao.init(entityClass,mSqliteDatabase);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T) baseDao;
    }

    private void openDataBase() {
        this.mSqliteDatabase = SQLiteDatabase.openOrCreateDatabase(mSqlitePath, null);
    }

    public static BaseDaoFactory getInstance(){
        return instance;
    }
}

单例模式,路径放在sd卡的根目录,创建数据库,这是1,2,3处代码做的操作。

getDataHelper方法的泛型T表示BaseDao的子类,也就是具体的操作数据库类,继承自BaseDao由用户去实现,扩展性高,M表示插入的实体类型,通过反射实例化BaseDao然后调用init方法进行初始化。最后返回BaseDao的子类。也就是生产数据库操作类。

最后一步,调用端:
在MainActivity中我们这样操作:

public class MainActivity extends Activity {
    UserDao userDao;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        userDao = BaseDaoFactory.getInstance().getDataHelper(UserDao.class, User.class);
    }

    public void save(View view) {
        for (int i=0;i<4;i++){
            User user = new User("lcty", "123456");
            userDao.insert(user);
        }
    }

}

操作图和效果图搞起:

save.gif

点击save保存四条数据
看插入的数据:

db.gif

好,我们看到数据已经插入成功,分析完毕,有说的不对的地方望指正,多多交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,258评论 25 707
  • 一、简述 众所周知,移动端(不管是Android还是iOS)使用的数据库是Sqlite,这种小型的数据库很适合移动...
    GitLqr阅读 2,151评论 2 10
  • 网易云音乐的地铁刷屏已经过去一段时间了,但是每每看到这句话,还是能瞬间戳动我的心。 ——小时候刮奖刮出“谢”字还不...
    舒陌染阅读 747评论 0 3
  • 小的时候,期盼着长大~ 长大了羡慕小的时候~人总是要等到失去后方才会后悔莫及…… 墨和雨是大学同学,他们的相遇没有...
    爾柒阅读 159评论 0 0
  • 3月11日、12日,是绍宋画馆的第一次课。 开始一件事情的时候,心里往往有点兴奋。一兴奋,往往就会出现意想不到的差...
    梅洛的听雨轩阅读 371评论 4 0