什么是Room?
Room是google官方开发的对象关系映射(ORM)库框架,采用注解的形式。它能缓存相关数据,使用户在断网的情况下浏览相应内容,重新连接网络时,用户发起的内容更改都会同步到服务器。能将本地数据保存到数据库中,它提供了一个SQLite的抽象层(封装),能够让我们更加稳健地访问数据库,能够提升数据库的性能。
Room的三个主要组件
Entity: 表示Room数据库内的表(Table)。Database: 数据库的持有者,是与 App 持久关联数据的底层连接的主要访问点。是一个继承 RoomDatabase 的抽象类。在类前注解中包含与数据库相关联的实体列表。类中包含一个具有 0 个参数的抽象方法,并返回用 @Dao 注解的类。 用户可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取 Database 实例。Dao(Data access object): Dao是用户来使用访问数据库的主要工具,它可以是一个接口,可以是一个抽象类。如果是一个抽象类,可以有一个构造函数,其只接收一个 RoomDatabase 参数。** 当系统编译时,Room会自动为Dao创造具体实现。
如何使用Room?
Entity的使用:
@Entity ( tableName = "users" )
public class User {
@PrimaryKey
public int id;
@ColumnInfo ( name = "first_name" )
public String firstName;
@ColumnInfo ( name = "last_name" )
public String lastName;
@Ignore
Bitmap picture;
}
在以上代码中,注解@Entity表示这是一个数据库的表,其中的tableName为自定义表的名称。 注解@PrimaryKey表示主键,也就是表中的主键,每个表都要至少有一个主键,当这个主键是一个字符串时,还要加上@NonNull注解,不然会出现编译错误。 注解@ColumnInfo表示这是数据库表中的一个列。其中的name表示此对象在表中对应的类名,如果不添加此注解,Room默认会以此变量名作为其在表中的列名。 注解@Ignore表示忽略此对象,数据库中不会创建此列。
@Entity ( foreignKeys = @ForeignKey ( entity = User. class ,
parentColumns = "id" ,
childColumns = "user_id" ) )
public class Book {
@PrimaryKey
public int bookId;
public String title;
@ColumnInfo ( name = "user_id" )
public int userId;
}
注解@ForeignKey: 在SQLite数据库中,我们可以指定对象之间的关系,可以让对象引用彼此,但是Room中却禁止这样做。虽然在Room中我们不能使用直接的对象关系,但是Room提供了外键, 外键在@Entity中的foreignKey定义父表子表的关系,当父表中某条记录子表有依赖的时候,父表的这条记录是不能删除的。
public class Address {
public String street;
public String state;
public String city;
@ColumnInfo ( name = "post_code" )
public int postCode;
}
@Entity ( tableName = "users" )
public class User {
@PrimaryKey
public int id;
@ColumnInfo ( name = "first_name" )
public String firstName;
@ColumnInfo ( name = "last_name" )
public String lastName;
@Ignore
Bitmap picture;
@Embedded
public Address address;
}
注解@Enbedded: 是一个嵌套的注解。有时候,我们需要将多个对象组合成一个对象,对象与对象之间是有嵌套关系的。以上的代码中,User类中通过嵌套Address对象,现在User表中也包含了Address中的列street,state等等…
@Entity ( tableName = "users" , indices = { @Index ( "firstName" ) ,
@Index ( value = { "last_name" , "address" } ) } )
public class User {
@PrimaryKey
public int id;
@ColumnInfo ( name = "first_name" )
public String firstName;
@ColumnInfo ( name = "last_name" )
public String lastName;
@Ignore
Bitmap picture;
@Embedded
public Address address;
}
注解@indices: 为定义数据库的查找索引,用于快速查找。索引也是分两种的,唯一索引和非唯一索引。如果是唯一索引重复会报错的。可以通过@Index的unique来设置是否唯一索引。
如何使用Database
Database要继承RoomDatabase抽象类,要包含一个无参方法,返回Dao对象。并且我们尽量要把这个Database设置为单例模式,让全局只有一个数组库实例,以便于不会出现问题。
@Database ( entities = { User. class } , version = 1 , exportSchema = false )
public abstract class MyDatabase extends RoomDatabase {
private static MyDatabase sInstance;
public MyDatabase ( ) {
}
public static MyDatabase getsInstance ( Context context) {
if ( sInstance == null ) {
synchronized ( MyDatabase. class ) {
if ( sInstance == null ) {
sInstance = Room. databaseBuilder ( context. getApplicationContext ( ) ,
MyDatabase. class , "sampleDb" ) . build ( ) ;
}
}
}
return sInstance;
}
abstract UserDao userDao ( ) ;
}
如何使用Dao
Dao在Room组件中是一个非常重要的组成部分。在这个Dao类中,定义了许多操作数据库的简便方法。下面是几个方法的例子:
注解@Insert插入
@Dao
public interface MyDao {
@Insert ( onConflict = OnConflictStrategy. REPLACE )
public void insertUsers ( User... users) ;
@Insert
public void insertBothUsers ( User user1, User user2) ;
@Insert
public void insertUsersAndFriends ( User user, List< User> friends) ;
}
如何插入一个参数,那么可以返回一个Long值,来代表插入的rowId。
注解@update更新
@Dao
public interface MyDao {
@Update
public void updateUsers ( User... users) ;
}
update方法是一个更新一系列Entity的简便方法,他根据每个Entity的主键作为更新的依据。这个方法可以返回一个int值,作为影响的行数(更新的行数)。
注解@Delete删除
@Dao
public interface MyDao {
@Delete
public void deleteUsers ( User... users) ;
}
delete方法是一个可以删除一系列Entity的简便方法,他根据每个Entity的主键作为删除的依据。这个方法也可以返回一个int值,来作为被删除的行数。
注解@Query查询
@Query是DAO类中主要被使用的注解,每一个@Query方法都是在编译时检查,如果查询方法写的时候出现问题,那么将会直接出现编译错误。 简单的查询
@Dao
public interface MyDao {
@Query ( "SELECT * FROM user" )
public User[ ] loadAllUsers ( ) ;
}
这个查询是查询表User中所有的字段,加载所有的user。 向Query中传递参数
@Dao
public interface MyDao {
@Query ( "SELECT * FROM user WHERE age > :minAge" )
public User[ ] loadAllUsersOlderThan ( int minAge) ;
}
有时候查询的时候,我们要进行筛选,选出满足自己要求的数据进行查询,这个例子中,传入了minAge参数,查询结果是所有age变量大于这个minAge的user对象。
@Dao
public interface MyDao {
@Query ( "SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge" )
public User[ ] loadAllUsersBetweenAges ( int minAge, int maxAge) ;
@Query ( "SELECT * FROM user WHERE first_name LIKE :search "
+ "OR last_name LIKE :search" )
public List< User> findUserWithName ( String search) ;
}
这是传入多个参数的例子,第一个查询结果是查询age变量在minAge和maxAge之间的所有user。第二个查询是first_name和last_name中有一个等于search的所有user。 查询字段的子集 有时候我们不需要查询返回所有的数据,我们只需要表中的某几条数据,这是我们要创建一个类,这个类中包含我们想得到的对象,然后在Query的返回参数中设置成这个类的对象就可以了。
public class NameTuple {
@ColumnInfo ( name= "first_name" )
public String firstName;
@ColumnInfo ( name= "last_name" )
public String lastName;
}
@Dao
public interface MyDao {
@Query ( "SELECT first_name, last_name FROM user" )
public List< NameTuple> loadFullName ( ) ;
}
可观察的Query 这个完成很简单,只需要将返回的数据变为LiveData即可。
@Dao
public interface MyDao {
@Query ( "SELECT first_name, last_name FROM user WHERE region IN (:regions)" )
public LiveData< List< User>> loadUsersFromRegionsSync ( List< String> regions) ;
}
Room数据库的升级和迁移
当用户更新了app的最新版本,当我们新添加表单,增加新的列的时候,我们也要进行数据库的升级或者说是迁移,如果我们不正确的迁移的话,系统就会重建一个数据库,之前数据库的数据就会销毁丢失掉。实现数据库的迁移有好几个方法,但是用的最多的,最好用的就是Migration类的 migrate() 方法。 Room让我们可以自定义Migration类来保存用户的数据,每一个自定义的这个类中都要指定from和to作为数据库的版本。
Room. databaseBuilder ( getApplicationContext ( ) , MyDb. class , "database-name" )
. addMigrations ( MIGRATION_1_2 , MIGRATION_2_3 ) . build ( ) ;
static final Migration MIGRATION_1_2 = new Migration ( 1 , 2 ) {
@Override
public void migrate ( SupportSQLiteDatabase database) {
database. execSQL ( "CREATE TABLE `Fruit` (`id` INTEGER, "
+ "`name` TEXT, PRIMARY KEY(`id`))" ) ;
}
} ;
static final Migration MIGRATION_2_3 = new Migration ( 2 , 3 ) {
@Override
public void migrate ( SupportSQLiteDatabase database) {
database. execSQL ( "ALTER TABLE Book "
+ " ADD COLUMN pub_year INTEGER" ) ;
}
} ;