Mobile Architecture 03-In-depth analysis of object-oriented database framework

Mobile Architecture 03 - Database Framework

I. Introduction

This is an object-oriented database framework that is smaller and easier to use than GreenDao.

If you just need simple functionality, or want to learn a database framework, then this framework is just for you.

2. Use

Let's talk about how to use this framework?

1. Basic use

The basic use is to add, delete, modify and check. Here, you only need to create BaseDao and entity classes, and then adjust the methods at will.

Entity class:

//设置表名
@DBTable("tb_person")
public class Person {
    public String name;
    public Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

Call the CRUD method:

public class SqliteActivity extends AppCompatActivity {
    ...
    /**
     * 添加数据
     *
     * @param view
     */
    public void clickInsert(View view) {
        IBaseDao baseDao = BaseDaoFactory.getInstance().getBaseDao(Person.class);
        baseDao.insert(new Person("0", 12));
    }

    /**
     * 删除数据
     *
     * @param view
     */
    public void clickDelete(View view) {
        BaseDao baseDao = BaseDaoFactory.getInstance().getBaseDao(Person.class);
        baseDao.delete(new Person());
    }

    /**
     * 修改数据
     *
     * @param view
     */
    public void clickUpdate(View view) {
        BaseDao baseDao = BaseDaoFactory.getInstance().getBaseDao(Person.class);
        Person user = new Person();
        user.age = 13;
        Person where = new Person();
        where.age = 12;
        baseDao.update(user, where);
    }

    /**
     * 查询数据
     *
     * @param view
     */
    public void clickSelect(View view) {
        IBaseDao baseDao = BaseDaoFactory.getInstance().getBaseDao(Person.class);
        List<Person> list = baseDao.query(null);
    }
}

2. Sub-library design

Generally, when multiple users are involved, it is necessary to perform sub-library design according to users.

Here, you only need to pass in the user name and database name to achieve sub-database.

Entity class:

/**
 * 用户名和数据库名
 */
public class DBInfo {
    //用户名
    private String userName;
    //数据库名
    private String dbName;

    /**
     * 判断值是否相等
     *
     * @param info
     * @return
     */
    public boolean equals(DBInfo info) {
        if (info == null || !this.toString().equals(info.toString())) {
            return false;
        }
        return true;
    }
}


/**
 * 用户
 */
//表名
@DBTable("tb_user")
public class BeanUser {
    @DBField("id3")
    private String id;
    private String name;
    private String password;
    ...
}

Sub-library call:

/**
 * 多用户登录
 *
 * @param view
 */
public void clickLogin(View view) {
    String showResult = "";
    for (int i = 1; i < 4; i++) {
        BeanUser user = new BeanUser(Integer.toString(i), "李涛" + i, "123456");
        DBInfo dbInfo = new DBInfo("litao" + i, "test" + i);
        //根据用户名创建用户目录,根据数据库名创建数据库
        BaseDao baseDao = BaseDaoFactory.getInstance(dbInfo).getBaseDao(BeanUser.class);
        baseDao.delete(null);
        baseDao.insert(user);

        showResult += BaseDaoFactory.getDBPath() + "\n" + baseDao.query().toString() + "\n";
    }
    result.setText(showResult);
}

3. Database upgrade

Database upgrade is also a more commonly used function. This is implemented in an object-oriented way, so the call is very convenient, just create a new entity class.

New entity class:

/**
 * 用户2
 */
@DBTable("tb_user")
public class BeanUser2 {
    @DBField("id4")
    private String id;
    private String name;
    private String age;
    private String password;
    ...
}

transfer:

/**
 * 升级数据库
 *
 * @param view
 */
public void clickUpdateDB(View view) {
    DBInfo dbInfo = new DBInfo("lili", "test");
    BeanUser user = new BeanUser(Integer.toString(11), "莉莉", "123456");
    BaseDao baseDao = BaseDaoFactory.getInstance(dbInfo).getBaseDao(BeanUser.class);
    baseDao.deleteTable();
    //重启数据库,清除Cursor缓存
    BaseDaoFactory.getInstance(dbInfo).restartDB();
    baseDao = BaseDaoFactory.getInstance(dbInfo).getBaseDao(BeanUser.class);
    baseDao.insert(user);

    String showResult = "";
    showResult += BaseDaoFactory.getDBPath() + "\n 旧表数据:\n" + baseDao.query().toString() + "\n";
    result.setText(showResult);

    //升级数据库
    baseDao.upgrade(dbInfo, BeanUser2.class);
    baseDao = BaseDaoFactory.getInstance(dbInfo).getBaseDao(BeanUser2.class);
    showResult += " 新表数据:\n" + baseDao.query().toString() + "\n";
    result.setText(showResult);
}

3. Principle

The core of this framework is still SQLiteDatabase, but it is encapsulated with object-oriented ideas, making it easier to use and close to the system’s native performance.

When using SQLiteDatabase, you need to pass in the ContentValues ​​object, or splice sql statements. When using the framework, the ContentValues ​​object and sql statement are obtained through reflection, and then the SQLiteDatabase operation is used.

In addition, user name, database name, table name and field name can be set through annotations.

4. Realization

1. Automatically create tables

A good database framework needs to realize the function of automatic table creation.

Automatic table building is to splicing out create table if not EXISTS tb_user(_id INTEGER,name TEXT,password TEXT)such sql statements through reflection and annotation, and then calling sqLiteDatabase.execSQL(createTableSql);to complete table building.

/**
 * 基础操作类
 * 能够自动建表
 *
 * @param <T>
 */
public class BaseDao<T> implements IBaseDao<T> {
    ...
    /**
     * 生成自动建表语句
     * 并缓存字段名与成员变量,方便后续操作,减少反射次数
     *
     * @return
     */
    private String getCreateTableSql() {
        cacheMap = new HashMap<>();

        //create table if not EXISTS  tb_user(_id INTEGER,name TEXT,password TEXT)
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("create table if not EXISTS  ");
        stringBuffer.append(tableName + "(");
        //反射得到所有的成员变量
        Field[] fields = entityClass.getDeclaredFields();
        for (Field field : fields) {
            //获取字段名
            String fieldName = field.getName();
            //使用注解信息设置字段名
            if (field.getAnnotation(DBField.class) != null) {
                fieldName = field.getAnnotation(DBField.class).value();
            }
            //拿到成员的类型
            Class type = field.getType();
            //根据成员的类型设置字段的类型
            if (type == String.class) {
                stringBuffer.append(fieldName + " TEXT,");
            } else if (type == Integer.class) {
                stringBuffer.append(fieldName + " INTEGER,");
            } else if (type == Long.class) {
                stringBuffer.append(fieldName + " BIGINT,");
            } else if (type == Double.class) {
                stringBuffer.append(fieldName + " DOUBLE,");
            } else if (type == byte[].class) {
                stringBuffer.append(fieldName + " BLOB,");
            } else {
                continue;
            }

            cacheMap.put(fieldName, field);
        }
        //去掉尾部的','
        if (stringBuffer.charAt(stringBuffer.length() - 1) == ',') {
            stringBuffer.deleteCharAt(stringBuffer.length() - 1);
        }
        stringBuffer.append(")");
        return stringBuffer.toString();
    }
    ...
}

When creating a table automatically, you need to cache the field names and member variables, so that you can obtain the field names and values ​​according to the incoming object, and then perform database operations. The advantage of this is that it reduces reflections and improves performance.

2. Additions, deletions and modifications

The operations of adding, deleting, and modifying the database are relatively simple. You only need to encapsulate the entity object into ContentValues, and then call the API of SQLiteDatabase.

The Insert operation is used here to illustrate, the first is to convert ContentValues:

public class BaseDao<T> implements IBaseDao<T> {
    ...
    /**
     * 将实体对象转化为ContentValues
     *
     * @param entity
     * @return
     */
    private ContentValues getContentValues(T entity) {
        if (entity == null) {
            return null;
        }
        ContentValues contentValues = new ContentValues();
        for (Map.Entry<String, Field> entry : cacheMap.entrySet()) {
            String fieldName = entry.getKey();
            Field field = entry.getValue();
            String fieldValue = null;
            try {
                field.setAccessible(true);
                Object o = field.get(entity);
                if (o != null) {
                    fieldValue = o.toString();
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            if (!TextUtils.isEmpty(fieldName) && !TextUtils.isEmpty(fieldValue)) {
                contentValues.put(fieldName, fieldValue);
            }
        }
        return contentValues;
    }
    ...
}

Then call the insert method:

public class BaseDao<T> implements IBaseDao<T> {
   /**
     * 添加数据
     *
     * @param entity
     * @return
     */
    @Override
    public long insert(T entity) {
//        原始写法
//        ContentValues contentValues = new ContentValues();
//        contentValues.put("_id", "1");//缓存   _id   id变量
//        contentValues.put("name", "jett");
//        sqLiteDatabase.insert(tableName, null, contentValues);

        //准备好ContentValues中需要的数据
        ContentValues values = getContentValues(entity);
        //开始插入
        long result = sqLiteDatabase.insert(tableName, null, values);
        return result;
    }
    ...
}

3. Query data

The query operation is relatively complicated, because there are many parameters, and they are all strings.

Here I have implemented a simple query function, the first is to encapsulate the query conditions:

/**
 * 查询条件
 */
private class Condition {
    private String whereCasue;//"name=? and password=?"
    private String[] whereArgs;//new String[]{"jett"}

    public Condition(Map<String, String> whereCasue) {
        if (whereCasue == null) {
            return;
        }
        ArrayList list = new ArrayList();//whereArgs里面的内容存入list
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("1=1");
        //取所有的字段名
        Set keys = whereCasue.keySet();
        Iterator iterator = keys.iterator();
        while (iterator.hasNext()) {
            String key = (String) iterator.next();
            String value = whereCasue.get(key);
            if (value != null) {
                stringBuilder.append(" and " + key + "=?");
                list.add(value);
            }
        }
        this.whereCasue = stringBuilder.toString();
        this.whereArgs = (String[]) list.toArray(new String[list.size()]);
    }
}

Then pass in the parameters:

public class BaseDao<T> implements IBaseDao<T> {
    /**
     * 按条件查询数据
     *
     * @param where   查询条件
     * @param orderBy 排序方式
     * @param start   返回结果起始行
     * @param end     返回结果结束行(不包括)
     * @return 查询结果
     */
    @Override
    public List<T> query(T where, String orderBy, Integer start, Integer end) {
        //sqLiteDatabase.query(tableName, null, "id=?", new String[], null, null, orderBy, "1,5");

        Map map = getValues(where);
        String limitString = null;
        if (start != null && end != null) {
            limitString = start + " , " + end;
        }
        //封装成指定格式的查询条件
        Condition condition = new Condition(map);
        Cursor cursor = sqLiteDatabase.query(tableName, null, condition.whereCasue, condition.whereArgs, null, null, orderBy, limitString);
        //定义一个用来解析游标的方法
        List<T> result = getResult(cursor, where);
        return result;
    }
}

Finally, encapsulate the query results:

public class BaseDao<T> implements IBaseDao<T> {
   ...
   /**
     * 将游标结果转化为对象集合
     *
     * @param cursor 游标结果
     * @param bean   实体对象
     * @return 对象集合
     */
    private List<T> getResult(Cursor cursor, T bean) {
        List list = new ArrayList();
        Object item = null;
        while (cursor.moveToNext()) {
            try {
                if (bean == null) {
                    item = entityClass.newInstance();
                } else {
                    item = bean.getClass().newInstance();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            for (Map.Entry<String, Field> entry : cacheMap.entrySet()) {
                String columnName = entry.getKey();
                Field field = entry.getValue();
                Class type = field.getType();
                Integer columnIndex = cursor.getColumnIndex(columnName);
                if (columnIndex != -1) {
                    try {
                        if (type == String.class) {
                            field.set(item, cursor.getString(columnIndex));
                        } else if (type == Double.class) {
                            field.set(item, cursor.getDouble(columnIndex));
                        } else if (type == Integer.class) {
                            field.set(item, cursor.getInt(columnIndex));
                        } else if (type == Long.class) {
                            field.set(item, cursor.getLong(columnIndex));
                        } else if (type == byte[].class) {
                            field.set(item, cursor.getBlob(columnIndex));
                        } else {
                            continue;
                        }
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
            list.add(item);
        }
        cursor.close();
        return list;
    }
    ...
}

4. Sub-library design

The idea of ​​database sub-database is to create its dedicated database according to the logged in user.

/**
 * 创建指定数据库
 *
 * @param dbInfo
 */
public BaseDaoFactory(DBInfo dbInfo) {
    if (dbInfo == null) {
        //创建数据库文件的父目录
        dbPath = ToolFile.getSDDirPath(dbDefaultBasePath) + "/" + dbDefaultName;
    } else {
        String path = dbDefaultBasePath;
        String name = dbDefaultName;
        if (!Tool.isEmpty(myDBInfo.getUserName())) {
            path += "/" + myDBInfo.getUserName();
        }
        if (!Tool.isEmpty(myDBInfo.getDbName())) {
            name = myDBInfo.getDbName() + ".db";
        }
        dbPath = ToolFile.getSDDirPath(path) + "/" + name;
    }

    if (sqLiteDatabase != null) {
        sqLiteDatabase.close();
    }
    sqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(dbPath, null);
}

5. Database upgrade

Here is the general implementation idea:

  1. Modify the table name of the old table
  2. Create new table
  3. Insert the data of the old table into the new table: here is implemented using native sql statements
  4. delete old table

I use object-oriented thinking here to achieve:

  1. Get all data of old table
  2. delete old table
  3. Create new table
  4. Insert the data of the old table into the new table: This is implemented in an object-oriented way

The advantage of this is that it is convenient to call, very convenient.

When updating the database, you need to pass an entity class of a new table, and create a new table based on this class.

/**
 * 更新数据库
 */
@Override
public <M> int upgrade(DBInfo dbInfo, Class<M> newTable) {
    if (entityClass == null || newTable == null || entityClass.getSimpleName().equals(newTable.getSimpleName())) {
        return UPGRADE_FAIL;
    }
    //查询旧表所有数据
    List<T> dataList = query();
    //删除旧表
    deleteTable();
    BaseDaoFactory baseDaoFactory = BaseDaoFactory.getInstance(dbInfo);
    //创建新表
    BaseDao<M> newDao = baseDaoFactory.getBaseDao(newTable);
    List<ContentValues> newDataList = getNewEntryList(dataList, newDao);
    newDao.insertContentValues(newDataList);
    //重启数据库,是为了清空游标缓存数据(如果不清空,那就会使用旧表的Cursor缓存)
    baseDaoFactory.restartDB();
    return UPGRADE_SUCCESS;
}

When inserting data into the new table, you need to match the fields of the old table with the fields of the new table one-to-one, which is achieved by comparing the variable name and the annotation name.

/**
 * 获取新表数据
 * 如果旧表的字段名或变量名等于新表的字段名或变量名,则把这个数据插入新表
 *
 * @param dataList
 * @return
 */
private List<ContentValues> getNewEntryList(List<T> dataList, BaseDao newDao) {
    if (Tool.isEmpty(dataList)) {
        return null;
    }
    List<ContentValues> newDataList = new ArrayList<>();
    HashMap<String, Field> newCacheMap = newDao.getCacheMap();
    for (T t : dataList) {
        ContentValues values = new ContentValues();
        for (Map.Entry<String, Field> entry : cacheMap.entrySet()) {
            Field value = entry.getValue();
            //字段名
            String columnName = entry.getKey();
            //变量名
            String fieldName = value.getName();
            //变量值
            String fieldValue = null;
            try {
                Object o = value.get(t);
                if (o != null) {
                    fieldValue = o.toString();
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

            //判断新、旧表的字段名或变量名是否相等
            for (Map.Entry<String, Field> newEntry : newCacheMap.entrySet()) {
                //新表字段名
                String newColumnName = newEntry.getKey();
                //新表变量名
                String newFieldName = newEntry.getValue().getName();
                if (columnName.equals(newColumnName) || columnName.equals(newFieldName) ||
                        fieldName.equals(newColumnName) || fieldName.equals(newFieldName)) {
                    values.put(newColumnName, fieldValue);
                }
            }
        }
        newDataList.add(values);
    }
    return newDataList;
}

The code has been uploaded to gitee, and students who like it can download it. By the way, give a like!

Previous: Mobile Architecture 02-Analysis and Use of Tinker Hot Repair

Next: Mobile Architecture 04-In-depth Analysis of Dynamic Skinning Framework )

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325527764&siteId=291194637