Android Architecture Components 系列目录

  1. LiveData原理深入浅出,透过源码看本质
  2. Android Room数据库,用过你才知道好

Android Room数据库

  • 前言
  • 一. 初识Room
    • 1. DataBase
    • 2. Entity(实体)
    • 3. Dao(数据访问对象)
  • 二. 如何使用Room
    • 1.依赖引入
    • 2. 方法调用
  • 三、使用注意事项
    • 1. 数据库关闭异常
    • 2. SQL用Integer
    • 3. schemas的导出
  • 四、使用场景
  • 总结

前言

说到Room 不得不提一下Jetpack。

Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法、减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者可将精力集中于真正重要的编码工作。
​​
众所周知Android MVVM架构离不开LiveData和ViewModel及Lifecycles,其实Room也可以在MVVM架构扮演重要的角色。

一. 初识Room

Room是一个数据持久化库,它是 Architecture Component的一部分。它让SQLiteDatabase的使用变得简单,大大减少了重复的代码,并且把SQL查询的检查放在了编译时。

Room官方文档介绍:https://developer.android.google.cn/training/data-storage/room?

Room主要由3个重要的组件组成:DataBase、Entity、Dao。三者的关系如下:

1. DataBase

数据库持有者,并作为与应用持久关联数据的底层连接的主要访问点。在运行时,通过Room.databaseBuilder() 或者 Room.inMemoryDatabaseBuilder()获取Database实例。

DataBase需要满足下面几个条件:

  1. 必须是abstract类而且的extends RoomDatabase。
  2. 必须在类头的注释中包含与数据库关联的实体列表(Entity对应的类)。
  3. 包含一个具有0个参数的抽象方法,并返回用@Dao注解的类。
/*** 使用中 annotationProcessor 'androidx.room:room-compiler:2.2.2'* 改为kapt 'androidx.room:room-compiler:2.2.2' ,如果项目中使用了kotlin** @author 罗发新* TypeConverters({Converters.class}) TODO 类型转化 有待研究其作用*/
@Database(entities = {UpImage.class
//        , Book.class
//        , Loan.class
}, version = 3, exportSchema = false)
public abstract class AbstractAppDataBase extends RoomDatabase {public abstract UpImageDao upImageDao();//  public abstract BookDao bookDao();private static volatile AbstractAppDataBase INSTANCE;/*** 关于AppDataBase 的使用:* 1)如果database的版本号不变。app操作数据库表的时候会直接crash掉。(错误的做法)* 2)如果增加database的版本号。但是不提供Migration。app操作数据库表的时候会直接crash掉。(错误的做法)* 3)如果增加database的版本号。同时启用fallbackToDestructiveMigration。这个时候之前的数据会被清空掉。* 如下fallbackToDestructiveMigration()设置。(不推荐的做法)*/public static AbstractAppDataBase getDatabase(Context context) {if (INSTANCE == null) {synchronized (AbstractAppDataBase.class) {if (INSTANCE == null) {INSTANCE = Room.databaseBuilder(context.getApplicationContext(), AbstractAppDataBase.class, "android_room_dev.db")// 设置是否允许在主线程做查询操作.allowMainThreadQueries()// 设置数据库升级(迁移)的逻辑.addMigrations(MIGRATION_1_2, MIGRATION_2_3)// setJournalMode(@NonNull JournalMode journalMode)  设置数据库的日志模式// 设置迁移数据库如果发生错误,将会重新创建数据库,而不是发生崩溃// .fallbackToDestructiveMigration() 会清理表中的数据 ,不建议这样做//设置从某个版本开始迁移数据库如果发生错误,将会重新创建数据库,而不是发生崩溃//.fallbackToDestructiveMigrationFrom(int... startVersions);.addCallback(new RoomDatabase.Callback() {// 进行数据库的打开和创建监听@Overridepublic void onCreate(@NonNull SupportSQLiteDatabase db) {super.onCreate(db);}@Overridepublic void onOpen(@NonNull SupportSQLiteDatabase db) {super.onOpen(db);}})//默认值是FrameworkSQLiteOpenHelperFactory,设置数据库的factory。// 比如我们想改变数据库的存储路径可以通过这个函数来实现// .openHelperFactory(SupportSQLiteOpenHelper.Factory factory);.build();}}}return INSTANCE;}/*** 数据库版本 1->2 user表格新增了age列*/private final static Migration MIGRATION_1_2 = new Migration(1, 2) {@Overridepublic void migrate(SupportSQLiteDatabase database) {database.execSQL("ALTER TABLE User ADD COLUMN age integer");}};/*** 数据库版本 2->3 新增book表格*/private final static Migration MIGRATION_2_3 = new Migration(2, 3) {@Overridepublic void migrate(SupportSQLiteDatabase database) {database.execSQL("CREATE TABLE IF NOT EXISTS `book` (`uid` INTEGER PRIMARY KEY autoincrement, `name` TEXT , `userId` INTEGER, 'time' INTEGER)");}};
}

事实上Database有两种生成方式:

  1. Room.databaseBuilder():生成Database对象,并且创建一个存在文件系统中的数据库。
  2. Room.inMemoryDatabaseBuilder():生成Database对象并且创建一个存在内存中的数据库。当应用退出的时候(应用进程关闭)数据库也消失。

2. Entity(实体)

代表数据库中某个表的实体类。默认情况下Room会把Entity里面所有的字段对应到表上的每一列。

Entity类中需要映射到表中的字段需要保证外部能访问到,所以每个字段要么Public,要么实现getter和setter方法。

/*** 作者:罗发新* 时间:2019/12/2 0002    星期一* 邮件:424533553@qq.com* 说明:Room 测试类* Entity注解包含的属性有:* tableName:设置表名字。默认是类的名字,不区分大小写。* indices:设置索引。* inheritSuperIndices:父类的索引是否会自动被当前类继承。* primaryKeys:设置主键。* foreignKeys:设置外键。*/
//@Entity(primaryKeys = {"firstName", "lastName"}) 也可以这样设置多个主键
//@Entity(indices = {@Index("firstName"), @Index(value = {"last_name", "address"},unique = true)}) unique 设置是否唯一索引
@Entity(tableName = "UserBean")
public class User {@Ignorepublic User(String userName) {
//        id = UUID.randomUUID().toString();mUserName = userName;}public User( ) {}@Ignorepublic User(int id, String userName) {this.id = id;this.mUserName = userName;}//设置主键自增, 每个类需要一个主键@PrimaryKey(autoGenerate = true)public int id;@ColumnInfo(name = "username")public String mUserName;//自定义设置列名@ColumnInfo(name = "first_name")public String firstName;@ColumnInfo(name = "last_name")public String lastName;public int age;/*** 如果有多个相同类型的嵌入字段,则可以设置前缀属性来保持每个列的唯一性。* 然后Room会将提供的值添加到嵌入对象中每个列名的开头。*/@Embedded(prefix = "first")public Address address;@Embedded(prefix = "second")public Address address2;public String school;/*** @Ignore 默认每个字段队列数据库中的一列,除非用 @Ignore注解不添加进入数据表中* */@Ignorepublic Bitmap picture;
}/*** onDelete即当parent中有删除操作时,onUpdate即当parent中有更新操作时,* <p>* child对应响应的动作有四种:* 1. NO_ACTION:当parent中的key有变化的时候child不做任何动作,默认动作* 2. RESTRICT:当parent中的key有依赖的时候禁止对parent做动作,做动作就会报错。* 3. SET_NULL:当paren中的key有变化的时候child中依赖的key会设置为NULL。* 4. SET_DEFAULT:当parent中的key有变化的时候child中依赖的key会设置为默认值。* 5. CASCADE:当parent中的key有变化的时候child中依赖的key会跟着变化** deferred:默认值false,在事务完成之前,是否应该推迟外键约束。* 比如当我们启动一个事务插入很多数据的时候,事务还没完成之前,parent引起key变化的时候。* 可以设置deferred为ture,让key立即改变。为false时,事务完成后child的key才会统一进行相应处理**/
@Entity(foreignKeys = @ForeignKey(entity = User.class,parentColumns = "id",childColumns = "user_id",onDelete = CASCADE,onUpdate = NO_ACTION,deferred = false
))
class Book {@PrimaryKeypublic int bookId;public String title;@ColumnInfo(name = "user_id")public int userId;
}public class Address {public String street;public String state;public String city;@ColumnInfo(name = "post_code")public int postCode;
}

3. Dao(数据访问对象)

Data Access Objects 是Room的主要组件,负责定义访问数据库的方法,Room在编译时创建每个DAO实例。

DAO抽象地以一种干净的方式去访问数据库,它可以是一个接口也可以是一个抽象类。如果它是一个抽象类,它可以有一个构造函数,它将RoomDatabase作为其唯一参数。

/*** dao  在编译期就会自动报错,强大的一匹* Data Access Object for the users table.** @author 罗发新*/
@Dao
public interface UserDao {/*** 当DAO里面的某个方法添加了@Insert注解。Room会生成一个实现,将所有参数插入到数据库中的一个单个事务。* <p>* onConflict:表示当插入有冲突的时候的处理策略。OnConflictStrategy封装了Room解决冲突的相关策略:* OnConflictStrategy.REPLACE:冲突策略是取代旧数据同时继续事务。* OnConflictStrategy.ROLLBACK:冲突策略是回滚事务。* OnConflictStrategy.ABORT:冲突策略是终止事务。默认策略* OnConflictStrategy.FAIL:冲突策略是事务失败。* OnConflictStrategy.IGNORE:冲突策略是忽略冲突。*/@Insert(onConflict = OnConflictStrategy.REPLACE)long[] insertUsers(User... users);/*** 当参数只有一个时,返回值只可以是long** @param user 参数* @return 表示插入的rowId*/@Insert(onConflict = OnConflictStrategy.REPLACE)long insertUser(User user);/*** 有多个参数时,返回值可以是long[]或者List<long>,** @param users 参数* @return long表示插入的rowId*/@Insert(onConflict = OnConflictStrategy.REPLACE)List<Long> insertUser(User... users);@Update(onConflict = OnConflictStrategy.REPLACE)int updateUsers(User... users);/*** Room会把对应的参数信息更新到数据库里面* @param users  操作参数* @return    表示更新成功了多少行*/@Update()int updateAll(User... users);@Updateint updateAll(List<User> user);/*** Room会把对应的参数信息指定的行删除掉* @param users  操作参数* @return   表示删除了多少行*/@Deleteint deleteUsers(User... users);/*** Delete all users.*/@Query("DELETE FROM " + TABLE_NAME)void deleteAll();//所有的CURD根据primary key进行匹配String TABLE_NAME = "UserBean";//------------------------query------------------------// 简单sql语句,查询user表所有的column@Query("SELECT * FROM " + TABLE_NAME)List<User> loadAllUsers();/*** 它允许您对数据库执行读/写操作。@Query在编译的时候会验证准确性,* 所以如果查询出现问题在编译的时候就会报错。比如字段名称不匹配,没有该字段* @param firstName  条件插叙* @return  查询返回的列表*/@Query("SELECT * FROM UserBean WHERE first_name == :firstName")User[] loadAllUsersByFirstName(String firstName);@Query("SELECT * FROM UserBean WHERE age BETWEEN :minAge AND :maxAge")List<User> loadAllUsersBetweenAges(int minAge, int maxAge);@Query("SELECT * FROM UserBean WHERE first_name LIKE :search " + "OR last_name LIKE :search")List<User> findUserWithName(String search);//    /**
//     * 只查询特定列信息
//     */
//    @Query("SELECT first_name, last_name FROM UserBean")
//    List<NameTuple> loadFullName();/*** 传递一组的参数,返回的对象可以用单独的对象接收*/@Query("SELECT first_name, last_name FROM UserBean WHERE school IN (:regions)")List<NameTuple> loadUsersFromRegions(List<String> regions);/*** LiveData*/@Query("SELECT first_name, last_name FROM UserBean WHERE school IN (:regions)")LiveData<List<NameTuple>> loadUsersFromRegionsSync(List<String> regions);/*** Rxjava2 中的 对象*/@Query("SELECT * from UserBean")Flowable<List<User>> loadUser();/*** 直接返回cursor*/@Query("SELECT * FROM userbean WHERE age > :minAge LIMIT 5")Cursor loadRawUsersOlderThan(int minAge);/** 多表联查*/
//    @Query("SELECT UserBean.* FROM book "
//            + "INNER JOIN loan ON loan.bookId = book.bookId "
//            + "INNER JOIN UserBean ON userbean.id = loan.userId "
//            + "WHERE userbean.last_name LIKE :lastName")
//    List<Book> findBooksBorrowedByNameSync(String lastName);//    @Query("SELECT userbean.last_name AS userName, book.name AS bookName "
//            + "FROM userbean, book "
//            + "WHERE :userId = book.user_id")
//    LiveData<List<LendingBook>> loadUserAndPetNames(int userId);/*** 返回第一个用户** @return the user from the table*/@Query("SELECT * FROM " + TABLE_NAME + " LIMIT 1")Flowable<User> getUser();/*** Insert a user in the database. If the user already exists, replace it.** @param user the user to be inserted.*/@Insert(onConflict = OnConflictStrategy.REPLACE)Completable insertUserSingle(User user);/*** 多表联查*/@Daopublic interface MyDao {@Query("SELECT * FROM book "+ "INNER JOIN loan ON loan.book_id = book.id "+ "INNER JOIN user ON user.id = loan.user_id "+ "WHERE user.name LIKE :userName")public List<Book> findBooksBorrowedByNameSync(String userName);}/*** 查询指定列的 多表联查* @return*/@Query("SELECT user.name AS userName, pet.name AS petName "+ "FROM user, pet "+ "WHERE user.id = pet.user_id")public LiveData<List<UserPet>> loadUserAndPetNames();/*** 对于单列的合并查询*/@Query("SELECT DetectionBean.wavelength FROM DetectionBean group by wavelength order by wavelength ASC")List<Integer> queryWavelength();}

二. 如何使用Room

1.依赖引入

在app.module中配置依赖。

    implementation 'androidx.room:room-runtime:2.2.5'// room 配合 RxJavaimplementation 'androidx.room:room-rxjava2:2.2.5'kapt 'androidx.room:room-compiler:2.2.5'

2. 方法调用

AbstractAppDataBase.getDatabase(sysApplication).upImageDao().insert(upImage);AbstractAppDataBase.getDatabase(MyBaseApplication.getInstance()).upImageDao().queryAll();AbstractAppDataBase.getDatabase(MyBaseApplication.getInstance()).upImageDao();

三、使用注意事项

1. 数据库关闭异常

E/SQLiteLog: (283) recovered 9 frames from WAL file /data/data/com.*/databases/android_room_dev.db-wal

出现此消息表明数据库在退出之前尚未关闭,因此未正确清理WAL文件.当应用程序启动时,如果发现数据库的连接还未关闭。当程序重新打开时,它会想办法清理WAL文件,此时会发出错误信息提示出现了严重问题。

 if (AbstractAppDataBase.getDatabase(MyBaseApplication.getInstance()).isOpen()) {AbstractAppDataBase.getDatabase(MyBaseApplication.getInstance()).close();}

要解决此问题,您需要在数据库使用完毕后关闭Room数据库.

2. SQL用Integer

在进行SQL 拼写是要用Integer,不要用int ,特别是Migrating中,否则会报出现异常。

3. schemas的导出

如果在AppDataBase中配置了Schema = true才能导出schemas文件,当然Schema 默认就是true。

需要在build.gradle中配置:

android {...defaultConfig {...javaCompileOptions {annotationProcessorOptions {arguments = ["room.schemaLocation":"$projectDir/schemas".toString()]}}}
}

四、使用场景

场景描述

  1. 我现有个项目,可以在有网和无网状态下运行,进行的一种操作需要记录相关信息并上传至后台,操作信息不能遗漏,无网络时要保存在有网状态时上传。
  2. 网络环境差,网络处于无网和有网、极度弱网(请求基本超时,有时成功)状态下

常规思路

  • 软件启动时,上传离线数据
  • 定时上传离线数据
  • 检测网络切换时触发上传离线数据

Room解决方案

  • 使用Livadata获取满足上传条件的数据,一旦数据库中的该查询结果有变化,立马执行上传操作。 当然要判断有网,在弱网状态下失败后,因为Count未变化,也不会导致程序循环的进行上传操作。
    @Query("SELECT COUNT(*) FROM " + TABLE_NAME + " WHERE isUp2Axe =0 ")LiveData<Integer> queryAxeCount();

总结

之所以推荐你用Room ,和其他sqlite相比它的优点有很多

  1. 使用原生sql来表达对数据库的操作,会在编译时会验证SQL的正误,可扩展性非常强大
  2. 分层清晰,上手简单,代码相比于第三方也更加可靠
  3. 存储对象里嵌套对象时,可使用@Embedded注解进行自动拆分存储。
  4. 通过注解生成代码,减少了代码量

一般Google推出的技术绝大多数还是挺好用的,我已经弃用了ormLite,根据本人的使用体验,确实用起来会流畅的多,且使用简单,它是Google Jatpack的组成之一。


相关链接

  1. LiveData原理深入浅出,透过源码看本质
  2. Android Room数据库,用过你才知道好

博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !

Android Room数据库,不会你就Out了相关推荐

  1. android专题-数据库Room

    android专题-数据库Room Room介绍 room是Google官方推荐的ORM数据库框架,抽象出sqlite访问的数据库. 包含三大组件: Entity 定义 表结构,每个entity类对一 ...

  2. android与mysql数据库同步_android开发 如何通过web服务器访问MYSQL数据库并且使其数据同步到android SQLite数据库?...

    通过web服务器访问MYSQL数据库有以下几个过程: 1.在MySql下创建自己的数据库和自己的表单 2.连接数据库. 3.访问数据库 1.创建web工程 (服务器端) 在Myeclipse下新建一个 ...

  3. android 建数据库 SQLite 存储sd 卡或者内存

    android 创建数据库调用SQLiteOpenHelper,一般不直接操作SQLiteDatabase . 是通过SQLiteOpenHelper来获取 public class DBOpenHe ...

  4. android 数据库表格数据库数据库中,Android SQLite数据库中的表详解

    Android SQLite数据库 前言 以前写PHP的时候,内置了print_r()和var_dump()两个函数用于打印输出任意类型的数据内部结构,现在做Android的开发,发现并没有这种类似的 ...

  5. android面向数据库的的编程工具-OrmLite

    数据库操作框架OrmLite ORMlite是类似hibernate的对象映射框架,主要面向java语言,同时,是时下最流行的android面向数据库的的编程工具. 对象关系映射(Object Rel ...

  6. 使用浏览器查看Android SQLite数据库-Android Debug Database用法

    本文转载自[http://blog.csdn.net/o279642707/article/details/68946230] 前言 Android客户端查看sqlite数据库是很繁琐的事情,需要DD ...

  7. 利用SQLChiper对Android SQLite数据库加密

    利用SQLChiper对Android SQLite数据库加密 前言: 上篇文章讲了Android studio+SQLCipher加密SQLite数据库的几个坑,跳过这几个坑,那么SQLCipher ...

  8. android SQLite数据库的使用

    今天,简单讲讲android如何使用SQLite数据库.  最近,自己在做一个功能时又用到了SQLite数据库,发现自己还是掌握的不很全面.其实之前的app里面也一直用到了数据库,但是自己没有花时 ...

  9. Android SQLite 数据库详细介绍

    Android SQLite 数据库详细介绍我们在编写数据库应用软件时,需要考虑这样的问题:因为我们开发的软件可能会安装在很多用户的手机上,如果应用使用到了SQLite数据库,我们必须在用户初次使用软 ...

  10. android 获取位置数据库,尝试从webview获取位置时,Android“SQLite数据库无法从/CachedGeoposition.db加载”错误...

    我正在创建一个使用webkit和chrome客户端的android应用程序.我希望能够在网页请求时获取当前位置.我设置的网页适用于普通浏览器就好了.然而,当我尝试访问该网页WebKit中,我不断收到此 ...

最新文章

  1. 建一所希望小学需要600万!
  2. javascript常用函数和技巧
  3. postman关闭ssl验证_【第5期】springboot:苹果内购服务端验证
  4. arm 架构_Arm架构之Arm内核解析
  5. 0074 几道面试题
  6. 马逊s3云存储接口_当对象存储“湖”有了强一致性
  7. 你的第一个 iOS 应用 – 2.开始上手
  8. winform textbox提示历史记录
  9. php强制时间,php如何强制转成字符串
  10. yum install mysql-server 指定版本_mysql 指定版本安装
  11. SCI论文写作--科研其实远没有那么难
  12. 最简单、最傻瓜化的虚拟主机管理软件-LuManager主机管理系统
  13. python画蝴蝶结_Shapely用户手册
  14. HIT2020春软件构造lab1
  15. 1060 5G/1065 版显卡安装TensorFlow/CUDA
  16. English trip 自习内容 句子结构和成分
  17. 点石成金:“硅业报国”不仅是理念
  18. Linux KVM环境搭建,以及创建kvm虚拟机
  19. 【匿名网络综述】匿名分布式网络之匿名网络综述
  20. 快租车app——需求分析心得

热门文章

  1. PAT1166 Summit(25)
  2. 希尔顿惠庭“旅居”品类酒店打造酒店界投资行业标杆
  3. 酷家乐x极盾科技:“智能安全决策平台”助力日均十亿级日志分析
  4. 基于MBD的BMS电池管理系统应用层软件,策略说明
  5. 详解JS中的原型与继承
  6. duang!京东瞬间成了一只受伤的狗!
  7. 聚焦健康零食?趣拿带你玩转营销!
  8. 身份证号码***展示
  9. matebook14装鸿蒙系统,华为将推出H6鸿蒙系统子母路由系统、MateBook 16 笔记本 和 MateView 显示器...
  10. 政府12345热线呼叫中心建设的几点总结