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

2024-05-29 19:58

本文主要是介绍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徒手撸数据库系列——注解与反射数据库关系模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1014500

相关文章

Java 中的 @SneakyThrows 注解使用方法(简化异常处理的利与弊)

《Java中的@SneakyThrows注解使用方法(简化异常处理的利与弊)》为了简化异常处理,Lombok提供了一个强大的注解@SneakyThrows,本文将详细介绍@SneakyThro... 目录1. @SneakyThrows 简介 1.1 什么是 Lombok?2. @SneakyThrows

Java Spring 中 @PostConstruct 注解使用原理及常见场景

《JavaSpring中@PostConstruct注解使用原理及常见场景》在JavaSpring中,@PostConstruct注解是一个非常实用的功能,它允许开发者在Spring容器完全初... 目录一、@PostConstruct 注解概述二、@PostConstruct 注解的基本使用2.1 基本代

Android使用ImageView.ScaleType实现图片的缩放与裁剪功能

《Android使用ImageView.ScaleType实现图片的缩放与裁剪功能》ImageView是最常用的控件之一,它用于展示各种类型的图片,为了能够根据需求调整图片的显示效果,Android提... 目录什么是 ImageView.ScaleType?FIT_XYFIT_STARTFIT_CENTE

Java中的@SneakyThrows注解用法详解

《Java中的@SneakyThrows注解用法详解》:本文主要介绍Java中的@SneakyThrows注解用法的相关资料,Lombok的@SneakyThrows注解简化了Java方法中的异常... 目录前言一、@SneakyThrows 简介1.1 什么是 Lombok?二、@SneakyThrows

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Android实现在线预览office文档的示例详解

《Android实现在线预览office文档的示例详解》在移动端展示在线Office文档(如Word、Excel、PPT)是一项常见需求,这篇文章为大家重点介绍了两种方案的实现方法,希望对大家有一定的... 目录一、项目概述二、相关技术知识三、实现思路3.1 方案一:WebView + Office Onl

Android实现两台手机屏幕共享和远程控制功能

《Android实现两台手机屏幕共享和远程控制功能》在远程协助、在线教学、技术支持等多种场景下,实时获得另一部移动设备的屏幕画面,并对其进行操作,具有极高的应用价值,本项目旨在实现两台Android手... 目录一、项目概述二、相关知识2.1 MediaProjection API2.2 Socket 网络

Android实现悬浮按钮功能

《Android实现悬浮按钮功能》在很多场景中,我们希望在应用或系统任意界面上都能看到一个小的“悬浮按钮”(FloatingButton),用来快速启动工具、展示未读信息或快捷操作,所以本文给大家介绍... 目录一、项目概述二、相关技术知识三、实现思路四、整合代码4.1 Java 代码(MainActivi

Android Mainline基础简介

《AndroidMainline基础简介》AndroidMainline是通过模块化更新Android核心组件的框架,可能提高安全性,本文给大家介绍AndroidMainline基础简介,感兴趣的朋... 目录关键要点什么是 android Mainline?Android Mainline 的工作原理关键

数据库面试必备之MySQL中的乐观锁与悲观锁

《数据库面试必备之MySQL中的乐观锁与悲观锁》:本文主要介绍数据库面试必备之MySQL中乐观锁与悲观锁的相关资料,乐观锁适用于读多写少的场景,通过版本号检查避免冲突,而悲观锁适用于写多读少且对数... 目录一、引言二、乐观锁(一)原理(二)应用场景(三)示例代码三、悲观锁(一)原理(二)应用场景(三)示例