安卓Room

943 阅读8分钟

保存数据到持久化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方法,完成数据库的结构改造。