美文网首页Android开发经验谈Android开发Android技术知识
移动架构-理解数据库框架设计 part2 - 增删改查

移动架构-理解数据库框架设计 part2 - 增删改查

作者: 289346a467da | 来源:发表于2018-03-24 21:31 被阅读74次

在上一篇目中,讲解了如何自动建表代码.
本期来实现增删改查,这里现实最简单的增删改查,主要讲解实现思想,复杂的会在后续文章中讲解。

准备工作

1.首先在BaseDao init 方法,在建表时,缓存一个map , 存储 (key - 字段名 ) ( value - 成员变量 ) private Map<String, Field> cacheMap;

 //建表
        if (!isInit) {
            //判断数据库是否打开
            if (!sqLiteDatabase.isOpen()) {
                return false;
            }
            //数据库已打开
            //建表的sql语句
            sqLiteDatabase.execSQL(getCreateSql());
            cacheMap = new HashMap<>();
            initCacheMap();
            isInit = true;
        }

initCacheMap(); 方法实现的很简单,就是将字段名和成员变量缓存到map中去,这样的好处就是在建表时就缓存好了,我们进行增删改查的操作就可以用缓存的map来进行操作,从map中拿到 字段名 和成员变量 根据成员变量可以获得成员变量的值。
下面来看map的实现

/**
     * 初始化缓存值
     */
    private void initCacheMap() {
        // 1 获取数据库的所有 列名
        String sql = "select * from " + tbName + " limit 1,0";//空表
        // 拿到所有的列名
        Cursor cursor = sqLiteDatabase.rawQuery(sql, null);//获取游标
        String[] columnNames = cursor.getColumnNames();
        //获取所有的成员变量
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (String columnName : columnNames) {
            Field columnField = null;
            for (Field field : declaredFields) {
                String fieldName = null;
                if (field.getAnnotation(DbField.class) != null) {
                    fieldName = field.getAnnotation(DbField.class).value();
                } else {
                    fieldName = field.getName();
                }
                if (fieldName.equals(columnName)) {
                    columnField = field;
                    break;
                }
            }
            if (columnField != null) {
                cacheMap.put(columnName, columnField);
            }
        }
    }

缓存好之后,根据cacheMap准备好ContentValues 所需要的数据 ,返回一个map,这个map存储的就是存取 key - 字段名,value - 插入的数据

/**
     * 获取 map 存取 key - 字段名,value - 插入的数据
     *
     * @param entity
     * @return 准备好ContentValues 所需要的数据
     */
    private Map<String, String> getValues(T entity) {
        Map<String, String> map = new HashMap<>();
        // 获取成员变量
        Iterator<Field> iterator = cacheMap.values().iterator();
        while (iterator.hasNext()) {
            Field next = iterator.next();
            next.setAccessible(true);
            //获取成员变量的值
            try {
                Object o = next.get(entity);
                if (o == null) {
                    continue;
                }
                String value = o.toString();
                //获取列名
                String key = null;
                if (next.getAnnotation(DbField.class) != null) {
                    key = next.getAnnotation(DbField.class).value();
                } else {
                    key = next.getName();
                }
                map.put(key, value);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return map;
    }

根据Map<String, String> getValues得到的map 把数据转移到 ContentValues

    private ContentValues getContentValues(Map<String, String> values) {
        ContentValues contentValues = new ContentValues();
        Set<String> keys = values.keySet();
        Iterator<String> iterator = keys.iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            String value = values.get(key);
            if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) {
                contentValues.put(key, value);
            }
        }
        return contentValues;
    }

准备工作完毕,我们进行插入操作

很简单,看一下代码

 @Override
    public long insert(T entity) {
        Map<String, String> values = getValues(entity);
        //把数据转移到 ContentValues
        ContentValues contentValues = getContentValues(values);
        long result = sqLiteDatabase.insert(tbName, null, contentValues);
        return result;
    }

调用方式

       User user = new User();
        user.id = 1;
        user.name = "sss";
        user.pass = "123";
        BaseDaoFactory.getInstance().getBaseDao(User.class).insert(user);

https://blog.csdn.net/linchunhua/article/details/7184439 sqlite3 命令
来看一下,是否将这条数据插入到了数据库中了,

image.png
image.png

OK,这条数据确实插入进去了。

更新操作

我们将 IBaseDao 添加一下代码

public interface IBaseDao<T> {
    long insert(T entry);

    long update(T entry, T where);

    int delete(T where);

    List<T> query(T where);

    /**
     * @param where      查询的条件
     * @param orderBy    排序
     * @param startIndex 开始的位置
     * @param limit      查多少个
     * @return
     */
    List<T> query(T where, String orderBy, Integer startIndex, Integer limit);

    List<T> query(String sql);
}

先看一下,原始的更新操作

sqLiteDatabase.update("表名","ContentValues","name = ?",new String[]{}) 原始操作

分析一下,首先表名和ContentValues,我们都可以拿到,在上面准备工作中,写了一个方法getContentValues,这样我们就可以很方便的拿到ContentValues

Map<String, String> values = getValues(entry);// 拿到
ContentValues contentValues = getContentValues(values);

那么对于 "name = ?",new String[]{} 如何更灵活的封装呢?
在BaseDao里面写一个 内部类,来专门处理条件。

/**
     * 条件 拼接处理类
     */
    private class Codition {
        private String whereCause;//name = ?
        private String[] whereArgs;//new String[]{}

        Codition(Map<String, String> whereCasue) {
            this(whereCasue, true);
        }

        Codition(Map<String, String> whereCasue, boolean isAnd) {
            ArrayList<String> valueList = new ArrayList<>();
            StringBuilder keyBuilder = new StringBuilder();
            keyBuilder.append("1==1");
            Set<String> keySet = whereCasue.keySet();
            for (String key : keySet) {
                String value = whereCasue.get(key);
                if (value != null) {
                    // or
                    if (isAnd) {
                        keyBuilder.append(" and ").append(key).append("=?");
                    } else {
                        keyBuilder.append(" or ").append(key).append("=?");
                    }
                    valueList.add(value);
                }
            }
            this.whereCause = keyBuilder.toString();
            this.whereArgs = valueList.toArray(new String[valueList.size()]);
        }
    }

以上代码比较简单,主要是通过 ArrayList<String> valueList = new ArrayList<>(); 来装载条件的值。StringBuilder keyBuilder = new StringBuilder(); 来拼接where 条件。

那么更新操作就可以写成这样:

 @Override
    public long update(T entry, T where) {
//        sqLiteDatabase.update("表名","ContentValues","name = ?",new String[]{}) 原始操作
        int result = -1;
        Map<String, String> values = getValues(entry);// 拿到
        ContentValues contentValues = getContentValues(values);
        Map<String, String> whereCause = getValues(where);
        Codition codition = new Codition(whereCause);
        result = sqLiteDatabase.update(tbName, contentValues, codition.whereCause, codition.whereArgs);
        return result;
    }

调用更新操作,通过对象的方式来进行封装,这样就不用再去写复杂的SQL语句了:

  public void update(View view) {
        User user = new User();
        user.name = "bbbbb";
        User whereUser = new User();
        whereUser.id = 2;
        BaseDaoFactory.getInstance().getBaseDao(User.class).update(user, whereUser);
    }
image.png

删除操作

删除操作就更叫简单了,一样的道理。

  @Override
    public int delete(T where) {
        // 原始的写法
//        sqLiteDatabase.delete("table","where","whereArgs");
        int result;
        Codition codition = new Codition(getValues(where));
        result = sqLiteDatabase.delete(tbName, codition.whereCause, codition.whereArgs);
        return result;
    }
public void delete(View view) {
        User user = new User();
        user.id = 2;
        BaseDaoFactory.getInstance().getBaseDao(User.class).delete(user);
    }

image.png

查询操作

查询操作比较复杂,查询方法有多个,我们这里只讲一个,剩下的都是同样的道理
原始的查询方法:sqLiteDatabase.query(tbName,null,"id = ?",new String[],null,null,orderBy,"1,5")
方法传递的值都很简单,这里就不在复述了。

 @Override
    public List<T> query(T where, String orderBy, Integer startIndex, Integer limit) {}

tbName "id = ?", new String[] 这几个都可以通过 Codition codition = new Codition(getValues(where)); 拿到。

limit 就是:

 String strLimit = null;
        if (startIndex != null && limit != null) {
            strLimit = startIndex + " , " + limit;
        }

那么这样就可以写成;

String strLimit = null;
        if (startIndex != null && limit != null) {
            strLimit = startIndex + " , " + limit;
        }
        Codition codition = new Codition(getValues(where));
        Cursor query = sqLiteDatabase.query(tbName, null,
        codition.whereCause, codition.whereArgs, null, null, orderBy, strLimit);

最重要的就是通过Cursor 转成List<T> 这里涉及到反射的知识,写一遍就懂了,代码都有注释。

 // obj 是用来表示user的结构的
    private List<T> getListResult(Cursor cursor, T obj) {
        List list = new ArrayList<>();// 存储 user对象
        Object item = null;// Object 的对象
        while (cursor.moveToNext()) {
            try {
                item = obj.getClass().newInstance();// new User 但是不知道Object 中的成员变量
                Iterator<Map.Entry<String, Field>> iterator = cacheMap.entrySet().iterator();//cacheMap 中存的是列名和成员变量
                while (iterator.hasNext()) {// 迭代 map 将成员变量和值 放到item中去 循环完成list 添加一个
                    Map.Entry<String, Field> entry = iterator.next();
                    //取列名
                    String colnumName = entry.getKey();
                    //然后以列名拿到列名在游标的位置
                    Integer columnIndex = cursor.getColumnIndex(colnumName);
                    Field field = entry.getValue();// 拿到列名对应的成员变量
                    Class<?> type = field.getType();//成员变量的类型
                    if (columnIndex != -1) {// 查询成功的
                        if (type == String.class) {
                            //将成员变量还有值 设置到item中去
                            field.set(item, cursor.getString(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 == Double.class) {
                            field.set(item, cursor.getDouble(columnIndex));
                        } else if (type == byte[].class) {
                            field.set(item, cursor.getBlob(columnIndex));
                        } else if (type == int.class) {
                            field.set(item, cursor.getInt(columnIndex));
                        } else {
                            continue;
                        }
                    }
                }
                list.add(item);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        cursor.close();//关闭游标
        return list;
    }

那么我们的查询操作就是:

@Override
    public List<T> query(T where) {
//        sqLiteDatabase.query()
        return query(where, null, null, null);
    }

    @Override
    public List<T> query(T where, String orderBy, Integer startIndex, Integer limit) {
//        sqLiteDatabase.query(tbName,null,"id = ?",new String[],null,null,orderBy,"1,5")
        String strLimit = null;
        if (startIndex != null && limit != null) {
            strLimit = startIndex + " , " + limit;
        }
        Codition codition = new Codition(getValues(where));
        Cursor query = sqLiteDatabase.query(tbName, null,
                codition.whereCause, codition.whereArgs, null, null, orderBy, strLimit);
        //定义一个用来解析游标的方法
        return getListResult(query, where);
    }

调用:

public void query(View view) {
        User user = new User();
        user.id = 1;
        List<User> query = BaseDaoFactory.getInstance().getBaseDao(BaseDaoNewImpl.class, User.class).query(user);
        for (int i = 0; i < query.size(); i++) {
            Log.e(TAG, "query: " + query.get(i).toString());
        }
    }

OK,查询这里稍微麻烦一些,明白思想就可以了,具体的细节可以自己来写,来更深入的理解。如何来实现分组查询。

 public List<T> query(T where, String orderBy, Integer startIndex, Integer limit, String groupBy) {
        return null;
    }

    public List<T> query(T where, String orderBy, Integer startIndex, Integer limit, String groupBy, String having) {
        return null;
    }

最后BaseDaoFactory的更改

 /**
     * 生产basedao 对象
     *
     * @param entityClass
     * @param <T>         User 对象
     * @param <M>         BaseDao {@link BaseDao}
     * @return
     */
    public synchronized <M extends BaseDao<T>, T> M getBaseDao(Class<M> daoClass, Class<T> entityClass) {
        BaseDao baseDao = null;
        try {
            baseDao = daoClass.newInstance();
            baseDao.init(sqLiteDatabase, entityClass);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return (M) baseDao;
    }

这样的好处,遵循里氏替换原则。进行新的实现在BaseDao的基础上实现一个新的方法。

相关文章

网友评论

    本文标题:移动架构-理解数据库框架设计 part2 - 增删改查

    本文链接:https://www.haomeiwen.com/subject/vidqcftx.html