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

相关文章

Oracle数据库定时备份脚本方式(Linux)

《Oracle数据库定时备份脚本方式(Linux)》文章介绍Oracle数据库自动备份方案,包含主机备份传输与备机解压导入流程,强调需提前全量删除原库数据避免报错,并需配置无密传输、定时任务及验证脚本... 目录说明主机脚本备机上自动导库脚本整个自动备份oracle数据库的过程(建议全程用root用户)总结

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

springboot自定义注解RateLimiter限流注解技术文档详解

《springboot自定义注解RateLimiter限流注解技术文档详解》文章介绍了限流技术的概念、作用及实现方式,通过SpringAOP拦截方法、缓存存储计数器,结合注解、枚举、异常类等核心组件,... 目录什么是限流系统架构核心组件详解1. 限流注解 (@RateLimiter)2. 限流类型枚举 (

Java中使用 @Builder 注解的简单示例

《Java中使用@Builder注解的简单示例》@Builder简化构建但存在复杂性,需配合其他注解,导致可变性、抽象类型处理难题,链式编程非最佳实践,适合长期对象,避免与@Data混用,改用@G... 目录一、案例二、不足之处大多数同学使用 @Builder 无非就是为了链式编程,然而 @Builder

spring中的@MapperScan注解属性解析

《spring中的@MapperScan注解属性解析》@MapperScan是Spring集成MyBatis时自动扫描Mapper接口的注解,简化配置并支持多数据源,通过属性控制扫描路径和过滤条件,利... 目录一、核心功能与作用二、注解属性解析三、底层实现原理四、使用场景与最佳实践五、注意事项与常见问题六

虚拟机Centos7安装MySQL数据库实践

《虚拟机Centos7安装MySQL数据库实践》用户分享在虚拟机安装MySQL的全过程及常见问题解决方案,包括处理GPG密钥、修改密码策略、配置远程访问权限及防火墙设置,最终通过关闭防火墙和停止Net... 目录安装mysql数据库下载wget命令下载MySQL安装包安装MySQL安装MySQL服务安装完成

MySQL进行数据库审计的详细步骤和示例代码

《MySQL进行数据库审计的详细步骤和示例代码》数据库审计通过触发器、内置功能及第三方工具记录和监控数据库活动,确保安全、完整与合规,Java代码实现自动化日志记录,整合分析系统提升监控效率,本文给大... 目录一、数据库审计的基本概念二、使用触发器进行数据库审计1. 创建审计表2. 创建触发器三、Java

SQL server数据库如何下载和安装

《SQLserver数据库如何下载和安装》本文指导如何下载安装SQLServer2022评估版及SSMS工具,涵盖安装配置、连接字符串设置、C#连接数据库方法和安全注意事项,如混合验证、参数化查... 目录第一步:打开官网下载对应文件第二步:程序安装配置第三部:安装工具SQL Server Manageme

C#连接SQL server数据库命令的基本步骤

《C#连接SQLserver数据库命令的基本步骤》文章讲解了连接SQLServer数据库的步骤,包括引入命名空间、构建连接字符串、使用SqlConnection和SqlCommand执行SQL操作,... 目录建议配合使用:如何下载和安装SQL server数据库-CSDN博客1. 引入必要的命名空间2.

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期