赞
踩
Android Architecture Components 系列目录
说到Room 不得不提一下Jetpack。
Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法、减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者可将精力集中于真正重要的编码工作。
众所周知Android MVVM架构离不开LiveData和ViewModel及Lifecycles,其实Room也可以在MVVM架构扮演重要的角色。
Room是一个数据持久化库,它是 Architecture Component的一部分。它让SQLiteDatabase的使用变得简单,大大减少了重复的代码,并且把SQL查询的检查放在了编译时。
Room官方文档介绍:https://developer.android.google.cn/training/data-storage/room?
Room主要由3个重要的组件组成:DataBase、Entity、Dao。三者的关系如下:
数据库持有者,并作为与应用持久关联数据的底层连接的主要访问点。在运行时,通过Room.databaseBuilder() 或者 Room.inMemoryDatabaseBuilder()获取Database实例。
DataBase需要满足下面几个条件:
/** * 使用中 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() { // 进行数据库的打开和创建监听 @Override public void onCreate(@NonNull SupportSQLiteDatabase db) { super.onCreate(db); } @Override public 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) { @Override public 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) { @Override public void migrate(SupportSQLiteDatabase database) { database.execSQL("CREATE TABLE IF NOT EXISTS `book` (`uid` INTEGER PRIMARY KEY autoincrement, `name` TEXT , `userId` INTEGER, 'time' INTEGER)"); } }; }
事实上Database有两种生成方式:
代表数据库中某个表的实体类。默认情况下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 { @Ignore public User(String userName) { // id = UUID.randomUUID().toString(); mUserName = userName; } public User( ) { } @Ignore public 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注解不添加进入数据表中 * */ @Ignore public 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 { @PrimaryKey public 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; }
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); @Update int updateAll(List<User> user); /** * Room会把对应的参数信息指定的行删除掉 * @param users 操作参数 * @return 表示删除了多少行 */ @Delete int 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); /** * 多表联查 */ @Dao public 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(); }
在app.module中配置依赖。
implementation 'androidx.room:room-runtime:2.2.5'
// room 配合 RxJava
implementation 'androidx.room:room-rxjava2:2.2.5'
kapt 'androidx.room:room-compiler:2.2.5'
AbstractAppDataBase.getDatabase(sysApplication).upImageDao().insert(upImage);
AbstractAppDataBase.getDatabase(MyBaseApplication.getInstance()).upImageDao().queryAll();
AbstractAppDataBase.getDatabase(MyBaseApplication.getInstance()).upImageDao();
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数据库.
在进行SQL 拼写是要用Integer,不要用int ,特别是Migrating中,否则会报出现异常。
如果在AppDataBase中配置了Schema = true才能导出schemas文件,当然Schema 默认就是true。
需要在build.gradle中配置:
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
}
场景描述
常规思路
Room解决方案
@Query("SELECT COUNT(*) FROM " + TABLE_NAME + " WHERE isUp2Axe =0 ")
LiveData<Integer> queryAxeCount();
之所以推荐你用Room ,和其他sqlite相比它的优点有很多
一般Google推出的技术绝大多数还是挺好用的,我已经弃用了ormLite,根据本人的使用体验,确实用起来会流畅的多,且使用简单,它是Google Jatpack的组成之一。
相关链接:
博客书写不易,您的点赞收藏是我前进的动力,千万别忘记点赞、 收藏 ^ _ ^ !
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。