数据库

  • 背景
  • GreenDao
  • 封装
    • 创建数据库
    • 对象映射表
    • 数据库操作
    • 扩展
  • 总结

背景

目前市面上已经有比较好用的数据库框架,比如GreenDao和OrmLite,而且功能也很齐全,那为什么还要多费功夫重复造轮子呢?原因无他,就为了装个B,哈哈,开个玩笑;每个框架经过了这么多版本的迭代,可以说设计的已经很周到了,考虑到了开发者能考虑到的问题,但是这也带来了一个问题,就是每个开发者使用这些框架的需求是不同的,有的需要这些功能,有的需要那些功能,这样你就没有必要把整个框架都导入到你的项目中来,特别是一些重量级的框架,对APP造成比较大的侵入性以及增大APP的体积,造成比较大的浪费,所以你就有必要根据自己的需求封装一套符合自己产品的框架,同时在开发过程中也能提高你自己的开发能力,于是就有了这篇文章

既然上面说到了GreenDao和OrmLite,那就对其做个简单的介绍吧

GreenDao

GreenDao官网
GreenDao-Github

至于GreenDao是什么,先看看官网介绍

简介:greendao是一个开源的Android ORM,这里的ORM全称是Object Relational Mapping,也就是对象关系映射(做java开发的使用mybatis的伙计肯定熟悉mapper),所以这里的意思就是它是一个开源的对象关系映射库;它使Sqlite数据库开发变得有趣,不知道你们感到有趣没,我是没感到有趣,哈哈,生活如此艰难,老婆孩子,还有房贷,没有趣了啊

继续上面的说,它减轻了开发人员处理低级数据库开发的负担,同时节省了时间,这一点我们需要承认,确实是的,因为不需要你再吭哧吭哧的写sql了,也不用关注数据库怎么创建,怎么创表了;尽管Sqlite是一个非常棒的嵌入式关系型数据库,但是编写Sql以及解析查询结果仍然是一个繁杂且耗时的工作,而Greendao通过将java对象映射到数据库表(也就是orm),将开发者从中解放出来,只需要通过简单的面向对象api进行增删改查就可以了,简单点说就是你可以傻瓜式的对数据库进行增删改查了,这样你会变得有趣吗?

简单翻译结束,再看看官网列举出的一些(吹)优(牛)点(B)

  • 最高性能表现:可能是Android中最快的ORM,官网也给出了例子证明,毕竟没有实践证明的吹牛比那是耍流氓啊,下面是官方给出的关于GreenDao,OrmLite和ActiveAndroid三种ORM解决方案的数据统计图:
  • 易于使用的涵盖关系和连接的强大API
  • 最小内存消耗
  • 小于100kb的库体积以保持较低水平的构建时间,同时避免64k方法数限制
  • 支持数据库加密以保障数据安全
  • 强大的社区支持

至于使用也很简单,这里简单叙述下:

第一步是配置:

在项目根目录的build.gradle中添加greendao插件

    dependencies {classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'}

在moudle的build.gradle中添加如下配置

......
apply plugin: 'org.greenrobot.greendao'android {......}greendao {schemaVersion 1 //指定数据库schema版本号,迁移等操作会用到daoPackage 'com.mango.datasave.dao'//通过gradle插件生成的数据库相关文件的包名targetGenDir 'src/main/java'//生成数据库文件的目录,比如DaoMaster、DaoSession、Dao目录
//    generateTests false //设置true将自动生成单元测试。
//    targetGenDirTests 'src/main/java' //设置存储生成的单元测试的基本目录。默认为 src / androidTest / java。
}dependencies {......implementation 'org.greenrobot:greendao:3.2.2'
}

第二步是创建实体类:

package com.mango.datasave.entity;import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.NotNull;
import org.greenrobot.greendao.annotation.Property;
import org.greenrobot.greendao.annotation.Transient;
import org.greenrobot.greendao.annotation.Unique;
import org.greenrobot.greendao.annotation.Generated;/*** Author: mango* Time: 2019/8/15 16:56* Version:* Desc:* @Entity:告诉GreenDao该对象为实体,只有被@Entity注解的Bean类才能被dao类操作** @Id:对象的Id,也是主键,必须使用Long类型作为Entity的Id,否则会报错。(autoincrement=true)表示主键会自增,如果false就会使用旧值 。** @Property:可以自定义字段名,默认是使用字段名,注意外键不能使用该属性** @NotNull:表当前列不能为空** @Unique:该属性值必须在数据库中是唯一值** @Generated:编译后自动生成的构造函数、方法等的注解,提示构造函数、方法等不能被修改** @Transient:使用该注解的属性不会被存入数据库的字段中,只是作为一个普通的java类字段*/
@Entity
public class User {@Id(autoincrement = true)private Long id;@NotNull   @Unique  private String name;@Property(nameInDb = "userage")private int age;@Transientprivate String work;
}

构造方法和get/set方法不需要手动创建,直接build工程,greendao插件会帮我们生成

这时候会生成这样三个类

  • DaoMaster::DaoMaster保存数据库对象(SQLiteDatabase)并管理特定模式的DAO类(而不是对象),它有静态方法来创建或删除表,它的内部类OpenHelper和DevOpenHelper是SQLiteOpenHelper实现,它们在SQLite数据库中创建模式。

  • DaoSession:管理特定模式的所有可用DAO对象,您可以使用其中一个getter方法获取该对象。DaoSession还提供了一些通用的持久性方法,如实体的插入,加载,更新,刷新和删除。

  • XXXDao:数据访问对象(DAO)持久存在并查询实体。对于每个实体,greenDAO生成对应的DAO,它具有比DaoSession更多的持久性方法,例如:count,loadAll和insertInTx。

第三步是初始化Greendao:

public class MyApplication extends Application {public static final String DB_NAME = "mango.db";private static DaoSession mDaoSession;@Overridepublic void onCreate() {super.onCreate();initGreenDao();}private void initGreenDao() {DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, DB_NAME);SQLiteDatabase db = helper.getWritableDatabase();DaoMaster daoMaster = new DaoMaster(db);mDaoSession = daoMaster.newSession();}public static DaoSession getDaoSession() {return mDaoSession;}
}

简单使用如下

MyApplication.getDaoSession().getUserDao().insert(new User());

至于其它的就不在这里介绍了,太多了

封装

由于一时没控制住篇幅,这里就不在介绍其它数据库框架了,来讲讲如何封装一个好用的符合自己产品需求的数据库框架

想封装框架肯定对基本的Sqlite的api要会使用,这个可以参考Android开发-教你玩转Android数据存储SQLite 如何加载SD卡数据库

其实不光平时开发中有这个需求,记得之前的头头让我封装一个数据库框架,不要使用第三方的,原因是自己封装的方便调试,加日志记录方便,错误好定位修改,随时都能迭代框架功能,说的这些也确实是真的;不仅仅是这个,在面试一些中大型公司的时候,面试官也基本都会问到数据库这块,比如:如果让你自己设计一个数据框框架,你会怎么做?还有一个原因说起来可能就比较鸡汤了,虽然说现在各个功能都有现成的轮子供开发者使用,但是如果你只是用轮子,而从来不会造轮子,那真的很难提高自己的技术水平,往高级开发方向转

那问题来了,怎么做一个好用的数据库框架呢?要知道开发出来的这个框架是给开发者用的,那你在使用原生Sqlite的api进行数据库开发的时候有哪些痛点呢?是不是要自己去创建数据库和表,以及db文件存放的位置,需要编写繁杂的且容易出错的sql,解析查询出来的数据等,那这些问题就是设计这个框架的需求了

  • 自动创建数据库及表
  • 配置数据库的存放位置
  • 开发者只关注与表映射的对象,而不关注实际sql的编写
  • 完成对数据库的各种操作

整个项目结构如下图,对象映射到表通过一系列的注解完成

创建数据库

在Sqlite中,SQLiteDatabase代表一个数据库对象,通过其中的静态方法openOrCreateDatabase实例化一个数据库对象

    /*** 创建/打开数据库* @param dbFile 数据库文件保存位置*/public void initDataBase(File dbFile){mDatabase = SQLiteDatabase.openOrCreateDatabase(dbFile,null);}

其中的File就是数据库文件,你想放在内部存储还是外部存储由这个文件路径决定,为了安全性考虑建议放到内部存储,如果数据库保存的数据偏大,可以放到外部存储的应用私有目录中,如下

    private void initMangoDao() {/*内部存储*/
//        File databasePath = getDatabasePath("mango.db");/*外部存储私有目录*/File databasePath = FileStorageTools.getInstance(this).getExternalStoragePrivateCache();File dbFile = new File(databasePath,"mango.db");MangoDaoFactory.getDefault().initDataBase(dbFile);}

对象映射表

这里主要通过注解+反射的方式将对象映射到数据库的表中,避免开发者手动编写sql创建表,其定义的注解如下

/*** Author: mango* Time: 2019/8/16 21:10* Version:* Desc: 将对象映射到某个表 只有被@Table注解的Bean类才能被dao类操作*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {/*** 声明表名* @return*/String value();
}

该注解作用于类,声明实体映射的表名

/*** Author: mango* Time: 2019/8/16 21:10* Version:* Desc: 将对象属性映射到表字段*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldName {/*** 声明字段名* @return*/String value();
}

该注解作用于变量,将实体属性映射到表字段

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Key {boolean autoincrement() default true;}

该注解作用于变量,声明实体某个变量作为主键

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {}

该注解作用于变量,声明实体中某个变量映射到表里的值不能为空

还有其它注解,这里就不一一说明,想了解的可以看源码

接下来就是根据实体来创建表了,主要是帮开发者编写对应的sql,通过mSqLiteDatabase.execSQL方法执行sql语句,生成对应的表

    protected boolean init(SQLiteDatabase sqLiteDatabase,Class<T> entityClz) {this.mSqLiteDatabase = sqLiteDatabase;this.mEntityClz = entityClz;//拿到表名mTableName = mEntityClz.getAnnotation(Table.class).value();if (TextUtils.isEmpty(mTableName)) {return false;}//如果数据库没创建或者没打开 直接返回if (mSqLiteDatabase == null || !mSqLiteDatabase.isOpen()) {return false;}/*** 接下来创建表 * 只知道表名 不知道字段名,需要通过注解获取*/mSqLiteDatabase.execSQL(getCreateTableSql(entityClz));return true;}
    /*** 获取创表语句* @param entityClz* @return*/private String getCreateTableSql(Class<T> entityClz){StringBuilder sb = new StringBuilder();sb.append("create table if not exists ").append(mTableName).append(" (");StringBuilder sbUnique = new StringBuilder();Field[] declaredFields = entityClz.getDeclaredFields();if (declaredFields.length == 0) {throw new TableException("该映射对象无有效字段");}for (Field field : declaredFields) {//如果该字段只是作为普通属性,那就跳过Irrelevant irr = field.getAnnotation(Irrelevant.class);if (irr != null) {continue;}/*** 获取 表字段名称* 如果没有添加该字段,默认以属性名作为表字段名*/String fieldName ;FieldName  name = field.getAnnotation(FieldName.class);if (name == null) {fieldName = field.getName();} else {fieldName = name.value();}//获取成员变量类型Class<?> type = field.getType();if (type == String.class) {sb.append(fieldName);sb.append(" TEXT");mFieldName.put(fieldName,field);} else if (type == int.class) {sb.append(fieldName);sb.append(" INTEGER");mFieldName.put(fieldName,field);} else if (type == long.class) {sb.append(fieldName);sb.append(" LONG");mFieldName.put(fieldName,field);} else if (type == double.class) {sb.append(fieldName);sb.append(" DOUBLE");mFieldName.put(fieldName,field);} else if (type == byte[].class) {sb.append(fieldName);//存放字节数组,比如将图片转成byte数组保存到数据库sb.append(" BLOB");mFieldName.put(fieldName,field);} else {continue;}//是否添加了主键注解Key key = field.getAnnotation(Key.class);if (key != null) {if (sb.toString().contains("primary")) {throw new TableException("请勿添加多个主键");}if (type != int.class) {throw new TableException("AUTOINCREMENT is only allowed on an INTEGER PRIMARY KEY");}sb.append(" primary key");if (key.autoincrement()) {sb.append(" autoincrement");}}//该字段不允许为nullNotNull notNull = field.getAnnotation(NotNull.class);if (notNull != null) {sb.append(" not null");}//字段要求唯一性Unique unique = field.getAnnotation(Unique.class);if (unique != null) {sbUnique.append(fieldName+",");}sb.append(",");}//去掉最后的逗号if (",".equals(String.valueOf(sbUnique.charAt(sbUnique.length() - 1)))) {sbUnique.deleteCharAt(sbUnique.length() - 1);}sb.append(" UNIQUE ("+ sbUnique.toString() + ")");sb.append(")");Log.i("MangoDao",""+sb.toString());return sb.toString();}

在这里拿到实体的所有变量,然后通过拿到它们的注解一一过滤,比如

  • 是否有Irrelevant注解修饰,如果有,说明该字段开发者只是将其作为一个普通java bean的属性,不需要映射到表
  • 是否有FieldName注解修饰,如果有,就获取该注解值作为其映射到表中的字段值,如果没有,就使用该变量名作为字段值
  • 是否有Key注解修饰,如果有,就说明该变量映射到表中是作为唯一主键
  • 是否有NotNull 注解修饰,如果有,就说明该变量映射到表中的该字段值不能为null
  • 是否有Unique 注解修饰,如果有,说明该变量映射到表中的该字段值要求唯一

最后拼成一个创表语句,执行后就可以在数据库生成一个与该实体映射的表了

最后提供一个方法让开发者获取该dao对象

    /*** 根据实体对象映射表* @param entity* @param <T>* @return*/public<T> MangoDao<T> getEntityDao(Class<T> entity) {MangoDao mangoDao = mCacheEntityDao.get(entity);if (mangoDao != null) {return mangoDao;}try {mangoDao = MangoDao.class.newInstance();if(mangoDao.init(mDatabase, entity)){mCacheEntityDao.put(entity,mangoDao);}}catch (Exception e) {e.printStackTrace();}return mangoDao;}

实体注解的使用如下

@Table("mango_user")
public class User {@FieldName("uid")@Keyprivate int uid;@NotNull@Uniqueprivate String name;private String sex;@Irrelevantprivate String temp;}

想创建不同的表,只需要在每个实体类中使用这些注解,然后调用getEntityDao方法就可以了,到这里数据库和表的创建就完成了,接下来就是增删改查操作了

数据库操作

这里以插入操作为例进行讲解

这里以SqLiteDatabase的insert(String table, String nullColumnHack, ContentValues values) 方法为例,当然了也可以拼接sql语句执行

    /*** 插入数据* @param entity 插入对象* @return*/@Overridepublic long insert(T entity) {if (mSqLiteDatabase == null) {return -1;}Map<String,String> value = bindValue(entity);ContentValues values = getContentValues(value);long result = mSqLiteDatabase.insert(mTableName, null, values);return result;}

这里第一步就是要将这个实体对象的属性及对应的属性值获取到

    private Map<String, String> bindValue(T entity) {Map<String,String> valueMap = new HashMap<>();Iterator<Field> iterator = mFieldName.values().iterator();while (iterator.hasNext()) {//遍历每个成员变量Field field = iterator.next();Irrelevant irr = field.getAnnotation(Irrelevant.class);if (irr != null) {continue;}field.setAccessible(true);try {//拿到变量值Object o = field.get(entity);if (o == null) {continue;}String value = o.toString();NotNull notNull = field.getAnnotation(NotNull.class);if (notNull != null && TextUtils.isEmpty(value)) {throw new TableException("非空字段必须要赋值");}String fieldName ;FieldName  name = field.getAnnotation(FieldName.class);if (name == null) {fieldName = field.getName();} else {fieldName = name.value();}valueMap.put(fieldName,value);} catch (IllegalAccessException e) {e.printStackTrace();}}return valueMap;}

然后就是遍历map将其key、value放到ContentValues中

接下来就可以通过如下方法插入一条数据

MangoDaoFactory.getDefault().getEntityDao(User.class).insert(new User(++uid,"tom"+uid,"1"));

可以看到整个封装过后,开发者只需要简单的两句代码就可以完成创建数据库、创建表、插入数据的操作了,很方便有没有

再以删除为例说明下,要知道一般删除肯定是以表的主键为条件进行删除一行记录,那我们就需要拿到实体中与主键映射的属性及对应的值来删除记录

@Overridepublic long delete(T entity) {if (mSqLiteDatabase == null) {return -1;}String key = getKey();if (TextUtils.isEmpty(key)) {throw new TableException("未找到对应的主键");}String value = getValue(entity,key);if (TextUtils.isEmpty(value)) {throw new TableException("主键对应的值不能为null");}return mSqLiteDatabase.delete(mTableName,key + " = ?",new String[]{value});}private String getKey(){String keyValue = null;Iterator<Field> iterator = mFieldName.values().iterator();while (iterator.hasNext()) {Field field = iterator.next();Key key = field.getAnnotation(Key.class);if (key != null) {FieldName  name = field.getAnnotation(FieldName.class);if (name == null) {keyValue = field.getName();} else {keyValue = name.value();}break;}}return keyValue;}private String getValue(T entity,String key){Field field = mFieldName.get(key);field.setAccessible(true);try {Object o = field.get(entity);String value = o.toString();return value;} catch (IllegalAccessException e) {e.printStackTrace();}return null;}

至于其它操作由于篇幅原因就不在这里叙述了,感兴趣的可以查看源码

扩展

当然了,如果你有其它操作在MangoDao中没有,你也可以通过继承它扩展功能,比如

public class MyUserDao extends MangoDao {public long count(){String sql = "select count(1) from " + mTableName;Cursor cursor = mSqLiteDatabase.rawQuery(sql, null);cursor.moveToFirst();//获得某一列的长度long count = cursor.getLong(0);cursor.close();return count;}
}
MyUserDao myUserDao = (MyUserDao) MangoDaoFactory.getDefault().getEntityDao(MyUserDao.class, User.class);
Log.e("MainActivity","myUserDao="+myUserDao.count());

这样在不需要开发者创表以及其它繁杂操作的情况下扩展自己的功能

源码:MangoDataSave

总结

通过封装后,同样可以通过简单的调用实现相同的操作,同时更加精简,因为有的时候你在引入第三方框架的时候还是需要考虑下它的方法数的,毕竟有个64k的限制;同时也能提高你的代码能力,架构设计能力等

相比于GreenDao,我这里使用的是运行时注解,就必须在运行时结合反射才能实现功能,不像它是使用编译时注解在编译期生成相应的Java源代码;这样会导致效率会有一些牺牲,编译时注解的开发稍微麻烦点,如果大家有兴趣可以参考仿写ButterKnife框架核心功能 掌握编译时注解+注解处理器APT生成Java代码的技术,看看编译时注解如何工作

使用反射+注解封装一个基于Sqlite极简的android数据库框架相关推荐

  1. 极简系列|Makefile工程框架(可执行程序、静态库、动态库)

    作为一名Linux系统下的开发人员,平时工作中会经常用Makefile来构建应用开发框架.如果是特别简单的应用,几行makefile代码就可以搞定.但对于稍微上点规模的应用,其可能会包含若干个模块,复 ...

  2. matlab程序:储能电池参与调峰调频优化 基于多能量集成的优点,本文建立了一个基于交互控制的双级两阶段框架,以实现互联多能量系统(MESs)之间的最佳能量供应

    matlab程序:储能电池参与调峰调频优化 摘要:基于多能量集成的优点,本文建立了一个基于交互控制的双级两阶段框架,以实现互联多能量系统(MESs)之间的最佳能量供应. 在较低的水平上,每个MES通过 ...

  3. 基于RxJava2+Retrofit2精心打造的Android基础框架

    代码地址如下: http://www.demodashi.com/demo/12132.html XSnow 基于RxJava2+Retrofit2精心打造的Android基础框架,包含网络.上传.下 ...

  4. CherryPy: 一个极简主义Python Web框架

    目录 1 前言 2 SWGI(Web Server Gateway Interface) 3 特色与优势 4 Hello, World! 5 不同URL调用不同函数 6 带有参数的URL 7 处理表单 ...

  5. 推荐一个好看的极简主义博客网站程序-liveblog

    以前呢,一直想搭建一个属于自己的博客系统,喜欢网站的所有的内容,所有的权限都可以归属于自己,不是很喜欢微博这种开放性平台,只想把自己的想法.心情记录在某个安静的角落. 后来,尝试过很多的网站程序或者主 ...

  6. 【编程实践】SQLite 极简教程

    1 SQLite 教程 SQLite 简介 本教程帮助您了解什么是 SQLite,它与 SQL 之间的不同,为什么需要它,以及它的应用程序数据库处理方式. SQLite 是一个软件库,实现了自给自足的 ...

  7. koa-grace:一个基于koa的node多应用MVC框架

    春节期间没回家留在北京写了一个基于koa的node MVC框架:koa-grace ,大家有兴趣可以star & fork下,谢谢支持啦!! 项目地址: https://github.com/ ...

  8. 亲手搭建一个基于Asp.Net WebApi的项目基础框架1

    目标:教大家搭建一个简易的前后端分离的项目框架. 目录: 1:关于项目架构的概念 2:前后端分离的开发模式 3:搭建框架的各个部分 这段时间比较闲,所以想把之前项目里用到的一些技术写到博客里来,分享给 ...

  9. eShopOnContainers 是一个基于微服务的.NET Core示例框架

    找到一个好的示例框架很难,但不是不可能.大多数是小型Todo风格的应用程序,通常基于SimpleCRUD.值得庆幸的是,Microsoft已经为eShopOnContainers创建了一个基于微服务的 ...

最新文章

  1. 链接器(linker)的作用——CSAPP第7章读书笔记
  2. CUBA Platform 7.0.4 发布,企业级应用开发平台
  3. 简单粗暴无需拼接下载 blob (ts)视频文件
  4. 获取列表中包含的元素数 在C#中
  5. 即时通讯飞鸽传书民意soft需求
  6. EventThread线程对VSync的接收
  7. 为什么学习嵌入式会搞单片机以及如何学习C51单片机
  8. 刚刚!2022年ABC中国大学排行榜发布
  9. 用PS调出二次元风格水彩漫画风景图片
  10. linux——20线程池
  11. 解决vs编译后运行提示“系统找不到指定的文件”的问题
  12. 『Java CVE』CVE-2022-33980: Apache Commons Configuration 读文件RCE
  13. Unirech:阿里云国际版怎么获得免费试用的机会以及注册流程
  14. 飞腾2000/4C调试问题记录
  15. STOpen硬件设计4-周边模块设计二(CAN+RS485+UART+IO扩展等)
  16. PAP认证和CHAP认证原理解析
  17. 基础IO(上)——Linux
  18. java 图像特效之放大镜与哈哈镜
  19. DPDK性能影响因素分析
  20. python直播课哪个好_python模块一直播课随记

热门文章

  1. 纯CSS3制作逼真的iphone 6手机模型
  2. 使用github制作简历
  3. 如何配置crontab每天早上6点和7点执行脚本
  4. 【云栖大会】云栖大会盛大召开 已成全球一流科技盛会
  5. Vmware虚拟机安装黑群辉提示系统可恢复,无限还原,解决办法。
  6. 7-29 二分法求多项式单根
  7. selenium、you-get下载B站视频、监控播放量和弹幕
  8. 在每天一个故事中......
  9. 计算机科学与技术论文摘要范文,计算机科学与技术论文范文两篇(2)
  10. 变量和简单数据类型(化浮为整)