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

相关文章

SQL Server修改数据库名及物理数据文件名操作步骤

《SQLServer修改数据库名及物理数据文件名操作步骤》在SQLServer中重命名数据库是一个常见的操作,但需要确保用户具有足够的权限来执行此操作,:本文主要介绍SQLServer修改数据... 目录一、背景介绍二、操作步骤2.1 设置为单用户模式(断开连接)2.2 修改数据库名称2.3 查找逻辑文件名

SQL Server数据库死锁处理超详细攻略

《SQLServer数据库死锁处理超详细攻略》SQLServer作为主流数据库管理系统,在高并发场景下可能面临死锁问题,影响系统性能和稳定性,这篇文章主要给大家介绍了关于SQLServer数据库死... 目录一、引言二、查询 Sqlserver 中造成死锁的 SPID三、用内置函数查询执行信息1. sp_w

mapstruct中的@Mapper注解的基本用法

《mapstruct中的@Mapper注解的基本用法》在MapStruct中,@Mapper注解是核心注解之一,用于标记一个接口或抽象类为MapStruct的映射器(Mapper),本文给大家介绍ma... 目录1. 基本用法2. 常用属性3. 高级用法4. 注意事项5. 总结6. 编译异常处理在MapSt

Java反射实现多属性去重与分组功能

《Java反射实现多属性去重与分组功能》在Java开发中,​​List是一种非常常用的数据结构,通常我们会遇到这样的问题:如何处理​​List​​​中的相同字段?无论是去重还是分组,合理的操作可以提高... 目录一、开发环境与基础组件准备1.环境配置:2. 代码结构说明:二、基础反射工具:BeanUtils

Spring @RequestMapping 注解及使用技巧详解

《Spring@RequestMapping注解及使用技巧详解》@RequestMapping是SpringMVC中定义请求映射规则的核心注解,用于将HTTP请求映射到Controller处理方法... 目录一、核心作用二、关键参数说明三、快捷组合注解四、动态路径参数(@PathVariable)五、匹配请

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

详解如何使用Python从零开始构建文本统计模型

《详解如何使用Python从零开始构建文本统计模型》在自然语言处理领域,词汇表构建是文本预处理的关键环节,本文通过Python代码实践,演示如何从原始文本中提取多尺度特征,并通过动态调整机制构建更精确... 目录一、项目背景与核心思想二、核心代码解析1. 数据加载与预处理2. 多尺度字符统计3. 统计结果可

SpringCloud中的@FeignClient注解使用详解

《SpringCloud中的@FeignClient注解使用详解》在SpringCloud中使用Feign进行服务间的调用时,通常会使用@FeignClient注解来标记Feign客户端接口,这篇文章... 在Spring Cloud中使用Feign进行服务间的调用时,通常会使用@FeignClient注解

Druid连接池实现自定义数据库密码加解密功能

《Druid连接池实现自定义数据库密码加解密功能》在现代应用开发中,数据安全是至关重要的,本文将介绍如何在​​Druid​​连接池中实现自定义的数据库密码加解密功能,有需要的小伙伴可以参考一下... 目录1. 环境准备2. 密码加密算法的选择3. 自定义 ​​DruidDataSource​​ 的密码解密3

Maven项目中集成数据库文档生成工具的操作步骤

《Maven项目中集成数据库文档生成工具的操作步骤》在Maven项目中,可以通过集成数据库文档生成工具来自动生成数据库文档,本文为大家整理了使用screw-maven-plugin(推荐)的完... 目录1. 添加插件配置到 pom.XML2. 配置数据库信息3. 执行生成命令4. 高级配置选项5. 注意事