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

一、概述

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

二、整个的一个设计思路

我用一张图表示:

Android手写数据库框架设计(增)_第1张图片
思路图.PNG

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

三、具体代码细节

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

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

    Long update(T entity,T where);
}

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

public abstract class BaseDao implements IBaseDao {

    private SQLiteDatabase mDatabase;

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

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

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

    //表名
    private String tableName;

    protected synchronized boolean init(Class 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 getValues(T entity) {
        HashMap result = new HashMap<>();
        Iterator 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 map) {
        ContentValues contentValues = new ContentValues();
        Set keys = map.keySet();
        Iterator 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 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 extends BaseDao{
    @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 ,M> T getDataHelper(Class clazz,Class 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);
        }
    }

}

操作图和效果图搞起:

Android手写数据库框架设计(增)_第2张图片
save.gif

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

Android手写数据库框架设计(增)_第3张图片
db.gif

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

你可能感兴趣的:(Android手写数据库框架设计(增))