保存数据到持久化room库中
Room是google提供的一个ORM库。Room主要提供三个主要组件
- @Database: @Database用来注解类,并且注解的类必须是继承自RoomDatabase的抽象类。该类主要作用是创建数据库和创建DAO
- @Entity:@Entity用来注解实体类
- @Dao:提供访问数据库的方法
使用Room
- 添加repositories
allprojects {
repositories {
google()
}
}
- 添加依赖
//Room数据库
implementation "android.arch.persistence.room:runtime:1.1.0-beta1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.0-beta1"
至此Room依赖添加完毕,我们需要重新build一下,就可以使用room了。
定义Entity
- 创建Java类并用Entity注解
定义Entity只需要在类上添加@Entity注解
@Entity
public class User{
}
- 指定表的别名
@Entity(tableName = "TableName_User")
public class User{
}
- 创建主键
通常情况下bean类只用Entity是不行的,我们还需要为他设置主键每个实体必须至少定义1个字段作为主键,即使只有一个成员变量
@Entity
public class User{
@PrimaryKey
public long userId;
}
- 创建自增主键
每一个实体至少定义一个字段作为主键。可以将@PrimaryKey的autoGenerate属性设置为true来设置自动id
@Entity
public class User{
@PrimaryKey(autoGenerate = true)
public int id;
}
- 创建复合主键
如果实体有一个复合的主键,可以使用 @Entity的primaryKeys属性来指定主键。
@Entity(primaryKeys = {"firstName", "lastName"})
public class User{
public String firstName;
public String lastName;
}
- 忽略成员属性。
默认情况 Room会为每一个成员属性在数据库中创建对应的字段,如果不需要保存,则用Ignore注解
@Ignore
public String otherInfo;
- 指定成员属性别名
如果我们需要重命名成员数据存储在数据库中的字段名称,则需要用到@ColumnInfo注解
@ColumnInfo(name = "user_name")
public String name;
- 添加索引
添加索引可以加快数据的查询。在Room中可以通过@Entity的indices属性添加索引。
@Entity(indices = {@Index("firstName"),@Index(value = {"lastName", "address"})})
class User {
@PrimaryKey
public int id;
public String firstName;
public String address;
public String lastName;
}
- 确保唯一性
我们可以通过将@Index的unique属性设置为true,来确保某个字段的唯一性,或者多个字段形成的组的唯一性。
@Entity(indices = {@Index(value = {"firstName", "lastName"},unique = true)})
class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
}
- 嵌套对象
Room允许我们用注解@Embedded在一个实体中嵌套另外一个实体,这样创建的表使用的是当前实体和创建实体的所有字段。
@Entity
class User {
@PrimaryKey
public int id;
public String firstName;
@Embedded
public Address address;
}
class Address {
public String state;
public String city;
public String street;
public int postCode;
}
- 嵌套多个对象
当一个类中嵌套多个对象时,假如他们有相同的字段,那么我们需要在调用@Embedded注解时提供prefix属性给此对象存到数据库中的所有字段添加一个前缀,生成的列名为 前缀+列名
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
@Embedded(prefix = "first")
Address1 address;
@Embedded(prefix = "second")
Address2 address2;
}
DAO(data access Object 数据访问对象)的使用
- 创建Dao
Dao在这里是一个interface,我们只需要在自己的接口上加上@Dao注解即可
@Dao
public interface UserDao {
}
- 插入数据
使用@Insert注解的方法可以帮助我们完成数据的插入,下面三个方法是标识我们可以传三种不同的类型进去。 onConflict被我们用来指定当发生冲突时的策略,比如将@Index的unique属性设置为true时,我们插入新数据与老数据重复就会出现问题,这里我们可以自己定制此问题发生时的策略。详情见:OnConflictStrategy
@Dao
public interface UserDao {
@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);
}
- 插入数据返回rowId
上面例子中我们插入数据时返回的时void,其实我们可以返回一个long类型的rowId,用以标识插入后的id
@Dao
public interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public Long insertUsers(User... users);
@Insert
public Long[] insertBothUsers(User user1, User user2);
@Insert
public List<Long> insertUsersAndFriends(User user, List<User> friends);
}
- 更新数据
我们可以采用@Update注解方法来更新数据库中的与参数实体主键相同的列,如果数据库中不存在与参数相同的主键,则不对数据库做任何操作 eg:
@Dao
public interface UserDao {
@Update
public int updateUsers(User... users);
}
@Update冲突解决策略同Insert,同样它可以反回int值表示受影响的行数
- 删除数据
删除数据操作和插入操作基本类似.他同样支持三个方法,同样可以返回void或int.使用delete可以删除主键与参数实体中相同的数据库字段
@Dao
public interface UserDao {
@Delete(onConflict = OnConflictStrategy.REPLACE)
public void deleteUsers(User... users);
@Delete
public void deleteBothUsers(User user1, User user2);
@Delete
public void deleteUsersAndFriends(User user, List<User> friends);
}
- 查询
@Query的值为sql语句,可以被sqlit执行,@Query支持查询删除和更新,不支持插入
Room会在编译时进行检查,当代码中包含语法错误,或者表不存在,Room将在编译时出现错误信息。
eg:查询user表中所有字段
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
public User[] loadAllUsers();
}
- 根据条件查询
@Query可以根据条件查询出相应的结果
eg:从user表中查询出所有age = ageNum 的人。
@Dao
public interface UserDao {
//传入单个参数
@Query("SELECT * FROM user WHERE age = :ageNum")
public User[] loadAllUsersOlderThan(int ageNum);
}
根据多个条件查询,或者多次引用相同参数
@Dao
public interface UserDao {
//查询出所有大于minAge 并且小于 maxAge的用户
@Query("SELECT * FROM user WHERE age BETWEEN :minAge AND :maxAge")
public User[] loadAllUsersBetweenAges(int minAge, int maxAge);
//查询出所有所有firstName是search或者lastName是search的人
@Query("SELECT * FROM user WHERE firstName LIKE :search " + "OR lastName LIKE :search")
public List<User> findUserWithName(String search);
}
Room还允许传入一个参数集合
@Dao
public interface MyDao {
//查询出所有住在regions列表中的人
@Query("SELECT firstName, last_name FROM user WHERE region IN (:regions)")
public List<NameTuple> loadUsersFromRegions(List<String> regions);
}
- @Query查询返回部分列的值
通常情况下我们并不希望返回数据库中的所有字段,我们只关心我们所需要的列,这样可以节省资源。因为Room允许返回任何java对象,此时我们可以自己定义一个返回对象。
public class NamePart {
public String firstName;
public String lastName;
}
在查询方法中使用该对象
@Dao
public interface User {
@Query("SELECT firstName, lastName FROM user")
public List<NamePart> loadFullName();
}
如上所示:这样我们就可以得到一个包含数据库中name部分字段的一个list。
- RXjava2支持
Room也可以把结果返回到RXJava 的Publisher 和Flowable中,使我们可以方便的进行后续处理。要使用这个功能需要我们在Gradle中进行如下配置。
//RXjava2支持 添加此项可直接返回rxjava中的Flowable 或者 publisher 官方 RxJava support for Room (use 1.1.0-beta1 for latest alpha)
implementation "android.arch.persistence.room:rxjava2:1.1.0-beta1"
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
返回Rxjava其他结果的情况
- 返回Cursor
当我们不想让Room内部对返回的结果做任何处理时,我们可以让程序返回一个Cursor对象
@Dao
public interface User {
@Query("SELECT * FROM user WHERE userName = :userName")
public Cursor loadRawUsersByUserName(String userName);
}
- 多表关联查询
同样由于我们可以在@Query()注解中可以执行任意的sql语句,所以说room可以支持基本上所有的查询,我们可以根据表之间建立的关系来进行多种查询操作。 eg:左连接,右连接,内连接,全连接。等所有多表关联查询。
数据库定义
Room数据库的定义非常简单,只需要我们定义一个由@database注解的RoomDatabase的抽象子类即可。 其中entities让我们在此数据库中所有用到的entity,version是当前数据库的版本号。 同样我们需要在此数据库中把所有的 数据库访问对象Dao 以下面抽象方法的形式返回回来。以供程序使用。
@Database(entities = {User.java}, version = 1)
@TypeConverters({Converter.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
- Room类型转换器@TypeConverter
Room支持字符串和基本数据类型,以及其他Entity注解类型作为主类的属性的存储。假如说我们遇到不是以上这些类型的存储时需要用到类型转换器。
//一个简单的类型转换器
public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
我们在转换器中定义了两个方法,一个是把data转换成时间戳来存储,一个是把时间戳拿出来转换成data供我们使用。那么我们如何把他应用到room当中呢?
- 应用数据类型转换器
Room转换器中TypeConverter的使用是通过将@TypeConverters({})注解到RoomDatabase的抽象子类上。这样Room内部就可以使用我们自定义的Converter了。
@Database(entities = {User.java}, version = 1)
@TypeConverters({Converter.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
ROOM数据库升级
当我们的App发布后我们可能有时候会新增或着删除原来表的结构,这时候就需要对我们的数据库进行升级操作。每一次数据库结构的变更都需要升级数据库版本。Room为我们提供了Migration类来帮助我们完成数据库的升级操作。我们可以在Migration的构造方法中提供数据库的开始版本和结束版本。
Room.databaseBuilder(getApplicationContext(), Database.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 `Address` (`id` INTEGER, "
+ "`address` 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");
}
};
其中AppDatabase是RoomDatabase抽象子类的类型,database-name是数据库名称。后面addMigration是添加不同版本升级的Migration对象,程序在升级时会从当前版本检测,并一级一级的执行Migration类中migrate的migrate方法,完成数据库的结构改造。