本文主要是介绍Android徒手撸数据库系列——注解与反射数据库关系模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
前言
我们看看现在市面上有很多的数据库框架
LitePal
GreenDao
OrmLite
Realm
DBFlow
他们各有各的优势,各有各的缺点,不管怎样,都是为了让我们使用数据库简单一些
好了,进入正题
咱们来自己撸一个orm数据库框架,就让我的这篇博客作为这个系列的开篇,让我们一起见证他的诞生
本篇目录
文章目录
- 前言
- 1. 设计增删改查的接口
- 2. 完成建表
- 3. 封装插入数据
- 4. 执行操作
- 详细代码
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
这篇关于Android徒手撸数据库系列——注解与反射数据库关系模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!