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

相关文章

在Android平台上实现消息推送功能

《在Android平台上实现消息推送功能》随着移动互联网应用的飞速发展,消息推送已成为移动应用中不可或缺的功能,在Android平台上,实现消息推送涉及到服务端的消息发送、客户端的消息接收、通知渠道(... 目录一、项目概述二、相关知识介绍2.1 消息推送的基本原理2.2 Firebase Cloud Me

SpringBoot利用@Validated注解优雅实现参数校验

《SpringBoot利用@Validated注解优雅实现参数校验》在开发Web应用时,用户输入的合法性校验是保障系统稳定性的基础,​SpringBoot的@Validated注解提供了一种更优雅的解... 目录​一、为什么需要参数校验二、Validated 的核心用法​1. 基础校验2. php分组校验3

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

Spring Security方法级安全控制@PreAuthorize注解的灵活运用小结

《SpringSecurity方法级安全控制@PreAuthorize注解的灵活运用小结》本文将带着大家讲解@PreAuthorize注解的核心原理、SpEL表达式机制,并通过的示例代码演示如... 目录1. 前言2. @PreAuthorize 注解简介3. @PreAuthorize 核心原理解析拦截与

Ubuntu中远程连接Mysql数据库的详细图文教程

《Ubuntu中远程连接Mysql数据库的详细图文教程》Ubuntu是一个以桌面应用为主的Linux发行版操作系统,这篇文章主要为大家详细介绍了Ubuntu中远程连接Mysql数据库的详细图文教程,有... 目录1、版本2、检查有没有mysql2.1 查询是否安装了Mysql包2.2 查看Mysql版本2.

Oracle数据库常见字段类型大全以及超详细解析

《Oracle数据库常见字段类型大全以及超详细解析》在Oracle数据库中查询特定表的字段个数通常需要使用SQL语句来完成,:本文主要介绍Oracle数据库常见字段类型大全以及超详细解析,文中通过... 目录前言一、字符类型(Character)1、CHAR:定长字符数据类型2、VARCHAR2:变长字符数

Win11安装PostgreSQL数据库的两种方式详细步骤

《Win11安装PostgreSQL数据库的两种方式详细步骤》PostgreSQL是备受业界青睐的关系型数据库,尤其是在地理空间和移动领域,:本文主要介绍Win11安装PostgreSQL数据库的... 目录一、exe文件安装 (推荐)下载安装包1. 选择操作系统2. 跳转到EDB(PostgreSQL 的

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

SpringCloud动态配置注解@RefreshScope与@Component的深度解析

《SpringCloud动态配置注解@RefreshScope与@Component的深度解析》在现代微服务架构中,动态配置管理是一个关键需求,本文将为大家介绍SpringCloud中相关的注解@Re... 目录引言1. @RefreshScope 的作用与原理1.1 什么是 @RefreshScope1.