Android徒手撸数据库系列——注解与反射数据库关系模型

前言

我们看看现在市面上有很多的数据库框架

LitePal

GreenDao

OrmLite

Realm

DBFlow

他们各有各的优势,各有各的缺点,不管怎样,都是为了让我们使用数据库简单一些

好了,进入正题

咱们来自己撸一个orm数据库框架,就让我的这篇博客作为这个系列的开篇,让我们一起见证他的诞生

本篇目录

1. 设计增删改查的接口

先设计简单的增删改查

public interface IDao<T> {
    // 插入
    long insert(T entity);
    // 更新
    int update(T entity, T where);
    // 删除
    int delete(T where);
    // 查询
    List<T> query(T where);
}

2. 完成建表

建表该如何建呢?

我们知道创建表需要知道表名和字段信息,我们如何拿到表名和字段信息呢?

如何像使用greenDao或者其他数据那样根据实体类自动创建数据库表呢?

参考greenDao的做法,通过反射和注解的方式获取到类名

反射可以通过class.getSimpleName获取到实体类的类名

注解可以获取到Class实体类设置的表名

表名注解如下

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

同样的套路:字段名注解

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

实体类如下配置

@DbTable("tb_user")    // 设置表名
public class User {
     @DbField("_id")
    private String id;
    private String name;
    private String password;

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

那么,整个创建表的过程如下

    private SQLiteDatabase mSqLiteDatabase;
    private String mTableName;
    private Class<T> mEntityClass;
    // 是否创建成功
    private boolean isInit = false;

    private HashMap<String, Field> cacheMap;

    public boolean init(SQLiteDatabase sqLiteDatabase, Class<T> entityClass) {
        mSqLiteDatabase = sqLiteDatabase;
        mEntityClass = entityClass;
        if (!isInit) {
            //取到表名
            if (entityClass.getAnnotation(DbTable.class) == null) {
                //反射到类名
                mTableName = entityClass.getSimpleName();
            } else {
                //取注解上的名字
                mTableName = entityClass.getAnnotation(DbTable.class).value();
            }
        }
        if (!sqLiteDatabase.isOpen()) {
            Log.e(TAG, "SQLiteDatabase is not open!");
            return false;
        }
        //执行建表
        String sql = generateCreateTableSql();
        Log.i(TAG, sql);
        sqLiteDatabase.execSQL(sql);
        isInit = true;
        return false;
    }

    // 生成创建数据表语句
    private String generateCreateTableSql() {
        StringBuilder sb = new StringBuilder();
        sb.append("create table if not exists ");
        sb.append(mTableName).append("(");
        //反射得到所有的成员变量
        Field[] fields = mEntityClass.getDeclaredFields();
        for (Field field : fields) {
            Class type = field.getType();
            if (field.getAnnotation(DbField.class) != null) {
                if (type == String.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" TEXT,");
                } else if (type == Integer.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" INTEGER,");
                } else if (type == Long.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" BIGINT,");
                } else if (type == Double.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" DOUBLE,");
                } else if (type == byte[].class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" BLOB,");
                } else {
                    //不支持的类型
                    continue;
                }
            } else {
                if (type == String.class) {
                    sb.append(field.getName()).append(" TEXT,");
                } else if (type == Integer.class) {
                    sb.append(field.getName()).append(" INTEGER,");
                } else if (type == Long.class) {
                    sb.append(field.getName()).append(" BIGINT,");
                } else if (type == Double.class) {
                    sb.append(field.getName()).append(" DOUBLE,");
                } else if (type == byte[].class) {
                    sb.append(field.getName()).append(" BLOB,");
                } else {
                    //不支持的类型
                    continue;
                }
            }
        }
        if (sb.charAt(sb.length() - 1) == ',') {
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append(")");
        return sb.toString();
    }

伏笔:如何加快数据库查询速度

3. 封装插入数据

我们知道插入一条数据需要知道字段和字段的值

伪代码如下

   public long insert(T entity) {
       // 准备好ContentValues中需要用的数据
        Map<String, String> map = getValues(entity);
        //设置插入的内容
        ContentValues values = getContentValues(map);
        //执行插入
        return mSqLiteDatabase.insert(mTableName, null, values);
    }

我们希望拿到ContentValues的key和value,来执行我们的插入操作

key从哪里来?

可以通过查询数据库

 Cursor cursor = mSqLiteDatabase.rawQuery(sql, null);
 String[] columnNames = cursor.getColumnNames();

然后通过反射和注解拿到实体类的值

Field[] columnFields = mEntityClass.getDeclaredFields();
String fieldName = null;
 if (field.getAnnotation(DbField.class) != null) {
 		fieldName = field.getAnnotation(DbField.class).value();
 } else {
        fieldName = field.getName();
}

这样看起来并没有什么问题

可是数据量比较大的时候呢,每次都反射,肯定影响效率啊,后面我们会实际测试

下面知识点来了

我们如何缓存实体类的属性与数据库字段之间的对应关系,

缓存一下这种关系

好吧,我们可以在创建表的时候缓存

// 数据库字段名作为 key  实体类的属性Field作为value 
private HashMap<String, Field> cacheMap;
 // 缓存数据库字段--->加快查询对应关系的速度
    private void cacheRelationship() {
        try {
            //1.取所有的列名====(查空表)
            String sql = "select * from " + mTableName + " limit 1,0";
            Cursor cursor = mSqLiteDatabase.rawQuery(sql, null);
            String[] columnNames = cursor.getColumnNames();
            //2.取所有的成员变量(反射)
            Field[] columnFields = mEntityClass.getDeclaredFields();
            //3.进行列名和成员变量的映射,存入到缓存中
            for (Field field : columnFields) {
                field.setAccessible(true);
            }
            for (String columnName : columnNames) {
                Field columnFiled = null;
                for (Field field : columnFields) {
                    String fieldName = null;
                    if (field.getAnnotation(DbField.class) != null) {
                        fieldName = field.getAnnotation(DbField.class).value();
                    } else {
                        fieldName = field.getName();
                    }
                    if (columnName.equals(fieldName)) {
                        columnFiled = field;
                        break;
                    }
                }
                if (columnFiled != null) {
                    cacheMap.put(columnName, columnFiled);
                }
            }
        } catch (Exception e) {
            Log.e(TAG, "cacheRelationship:" + e.toString());
        }


    }

在第2步创建表的地方缓存一下就可以了

现在数据有了

开始插入数据

获取对应关系

   private Map<String, String> getValues(T entity) {
        HashMap<String, String> map = new HashMap<>();
        Iterator<Field> fieldIterator = cacheMap.values().iterator();
        while (fieldIterator.hasNext()) {
            Field field = fieldIterator.next();
            field.setAccessible(true);
            //获取成员变量的值
            try {
                Object object = field.get(entity);
                if (object == null) {
                    continue;
                }
                String value = object.toString();
                //获取列名
                String key;
                if (field.getAnnotation(DbField.class) != null) {
                    key = field.getAnnotation(DbField.class).value();
                } else {
                    key = field.getName();
                }
                if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) {
                    map.put(key, value);
                }

            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return map;
    }

转换为ContentValues

    private ContentValues getContentValues(Map<String, String> map) {
        ContentValues contentValues = new ContentValues();
        Set keys = map.keySet();
        for (Object key1 : keys) {
            String key = (String) key1;
            String value = map.get(key);
            if (value != null) {
                contentValues.put(key, value);
            }
        }
        return contentValues;
    }

咱们回看上面的伪代码,就可以了

4. 执行操作

写了半天,咱们还没有个入口程序

数据库初始化需要两个参数

    // 数据库名称
    protected String mDbName;
    // 数据库路径
    protected String mDbPath;

初始化方法

// dataBase实例
private SQLiteDatabase sdb;

public void init(Context context, String dbName) {
        if (null == sdb) {
            mDbName = dbName;
            mDbPath = context.getDatabasePath(dbName).getPath();
            File dir = new File(context.getDatabasePath(dbName).getParent());
            if (!dir.exists()) {
                dir.mkdirs();
            }
            sdb = SQLiteDatabase.openOrCreateDatabase(mDbPath, null);
        }
    }

获取Dao实例,需要考虑同步问题

   public synchronized <T> BaseDao getBaseDao(Class<T> entityClass) {
        BaseDao<T> baseDao = null;
        try {
            baseDao = BaseDao.class.newInstance();
            baseDao.init(sdb, entityClass);
        } catch (Exception e) {
            Log.i(TAG, "getBaseDao failed:" + e.toString());
        }
        return baseDao;
    }

界面插入数据

User user = new User();
user.setId("n000" + i);
user.setPassword("123456");
user.setName("张三" + (++i));
// 数据库增加一条数据
BaseDao<User> baseDao = DaoFactory.getInstance().getBaseDao(User.class);
long insert = baseDao.insert(user);
Log.e("dds_test", "返回结果:" + insert);

今天先这样吧,完成第一步

详细代码

https://github.com/ddssingsong/AnyTool

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ddssingsong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值