JetPack 之 Room

栏目: 数据库 · 发布时间: 5年前

内容简介:Room包含以下三个重要组成部分:

Room 持久库提供了一个 SQLite 抽象层,让你访问数据库更加稳健,提升数据库性能。App把经常需要访问的数据存储在本地将会大大改善用户的体验。这样用户在网络不好时仍然可以浏览内容。当用户网络可用时,可以更新用户的数据。

Room包含以下三个重要组成部分:

详细的结构关系可以看下图:

JetPack 之 Room

实例

Room 和传统写数据库创建访问的代码大概形式差不多的。以存储User信息为例,看一下下面的代码:

// User.java
@Entity
public class User{
    @PrimaryKey
    private int uid;

    @ColumnInfo(name = "first_name")
    private String firstName;

    @ColumnInfo(name = "last_name")
    private String lastName;

    // Getters and setters are ignored for brevity,
    // but they're required for Room to work.
    //Getters和setters为了简单起见就省略了,但是对Room来说是必须的
}


// UserDao.java
@Dao
public interface UserDao{
    @Query("SELECT * FROM user")
    List<User>getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User>loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
           + "last_name LIKE :last LIMIT 1")
    UserfindByName(String first, String last);

    @Insert
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

// AppDatabase.java
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabaseextends RoomDatabase{
    public abstract UserDao userDao();
}

在创建上面的文件之后,使用以下代码获得创建数据库的实例:

AppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();

使用 @Entity 实体定义数据

Room持久化一个类的field必须要求这个field是可以访问的。可以把这个field设为public或者设置setter和getter。如果上面的User类中包含一个字段是不希望存放到数据库中的,那么可以用 @Ignore 注解这个字段:

@Entity
class User{
    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;

    //不需要被存放到数据库中
    @Ignore
    Bitmap picture;
}

Primary Key 主键

每个Entity都必须定义一个field为主键,即使是这个Entity只有一个field。如果想要Room生成自动的primary key,可以使用 @PrimaryKeyautoGenerate 属性。如果Entity的primary key是多个Field的复合Key,可以向下面这样设置:

@Entity(primaryKeys = {"firstName", "lastName"})
class User{
    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

在默认情况下 Room 使用类名作为数据库表的名称。如果想要设置不同的名称,可以设置表名tableName为users,如果想要使用不同的名称,可以通过 @ColumnInfo(name = "first_name") 设置,代码如下:

@Entity(tableName = "users")
class User{
    @PrimaryKey
    public int id;

    @ColumnInfo(name = "first_name")
    public String firstName;

    @ColumnInfo(name = "last_name")
    public String lastName;

    @Ignore
    Bitmap picture;
}

索引和唯一性

根据访问数据库的方式,你可能想对特定的field建立索引来加速你的访问。下面这段代码展示了如何在Entity中添加索引或者复合索引:

@Entity(indices = {@Index("name"),
        @Index(value = {"last_name", "address"})})
class User{
    @PrimaryKey
    public int id;

    public String firstName;
    public String address;

    @ColumnInfo(name = "last_name")
    public String lastName;

    @Ignore
    Bitmap picture;
}

对象之间的关系

SQLite是关系型数据库,那么就可以在两个对象之间建立联系。大多数ORM库允许Entity对象互相引用,但Room明确禁止了这样做。详细的原因,可以参考 这里 。既然不允许建立直接的关系,Room提供以外键的方式在两个Entity之间建立联系。

例如,有一个Pet类需要和User类建立关系,可以通过 @ForeignKey 来达到这个目的,代码如下:

@Entity(foreignKeys = @ForeignKey(entity = User.class,
                                  parentColumns = "id",
                                  childColumns = "user_id"))
class Pet{
    @PrimaryKey
    public int petId;

    public String name;

    @ColumnInfo(name = "user_id")
    public int userId;
}

外键可以允许你定义被引用的Entity更新时发生的行为。例如,你可以定义当删除 User 时对应的 Pet 类也被删除。可以在 @ForeignKey 中添加 onDelete = CASCADE )实现。

有时候需要在类里面把另一个类作为field,这时就需要使用 @Embedded 。这样就可以像查询其他列一样查询这个field。例如,User类可以包含一个field Address ,代表User的地址包括所在街道、城市、州和邮编。代码如下:

class Address{
    public String street;
    public String state;
    public String city;

    @ColumnInfo(name = "post_code")
    public int postCode;
}

@Entity
class User{
    @PrimaryKey
    public int id;

    public String firstName;

    @Embedded
    public Address address;
}

在存放User的表中,包含的列名如下: id , firstName , street , state , city , post_code 。Embedded 的field中也可以包含其他Embedded的field。如果多个Embedded的field是类型相同的,可以通过设置 prefix ) 来保证列的唯一性。

使用 DAOs 访问数据

DAOs 是数据库访问的抽象层。 Dao 可以是一个接口也可以是一个抽象类。如果是抽象类,那么它可以接受一个 RoomDatabase 作为构造器的唯一参数。

注意:除非在建造器上调用了 allowMainThreadQueries() ,否则Room不支持主线程上的数据库访问,因为访问数据库是耗时的,可能阻塞主线程,引起UI卡顿。返回 LiveDataFlowable 实例的异步查询可免除此规则,因为它们在需要时异步地在后台线程上运行查询。

Insert

使用 @Insert 注解的方法,Room将会生成插入的代码。

@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);
}

如果 @Insert 方法只接受一个参数,那么将返回一个long,对应着插入的 rowId 。如果接受多个参数,或者数组,或者集合,那么就会返回一个long的数组或者list。有关详细信息,请参阅@Insert注解的参考文档,以及 SQLite documentation for rowid tables

Update

Update 方法在数据库中用于修改一组实体的字段。它使用每个实体的主键来匹配查询。下面的代码片段演示如何定义此方法:

@Dao
public interface MyDao{
    @Update
    public void updateUsers(User... users);
}

也可以让update方法返回一个int型的整数,代表被update的行号。

Delete

使用 @Insert 注解的方法,Room将会生成插入的代码。

@Dao
public interface MyDao{
    @Delete
    public void deleteUsers(User... users);
}

和update方法一样,也可以返回一个int型的整数,代表被delete的行号。

Query

@Query 是DAO类中使用的主要注解。它允许您在数据库上执行读/写操作。每个 @Query 方法在编译时被验证,因此,如果存在查询问题,则会发生编译错误而不是运行时故障。

Room也会检查查询返回值的类型,如果返回类型的字段和数据路列名存在不一致,会收到警告。如果两者完全不一致,就会产生错误。下面代码示例了普通查询和带参数查询:

@Dao
public interface MyDao{
    @Query("SELECT * FROM user")
    public User[] loadAllUsers();

   @Query("SELECT * FROM user WHERE age > :minAge")
    public User[] loadAllUsersOlderThan(int minAge);
}

这里也会在编译时做类型检查,如果表中没有age这个列,那么就会抛出错误。

也可以穿入多个参数或一个参数作为多个约束条件查询用户:

@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);
}

返回列的子集

有时可能只需要Entity的几个field,例如只需要获取User的姓名就行了。通过只获取这两列的数据不仅能够节省宝贵的资源,还能加快查询速度。

Room也提供了这样的功能。

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();
}

可被观察的查询

通过和 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也定义这样的功能。下面这段代码演示了如何从一个包含借阅用户信息的表和一个包含已经被借阅的书的表中获取信息:

@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);
}

也可以从查询中返回POJO类。代码如下:

@Dao
public interface MyDao{
   @Query("SELECT user.name AS userName, pet.name AS petName "
          + "FROM user, pet "
          + "WHERE user.id = pet.user_id")
   public LiveData<List<UserPet>> loadUserAndPetNames();

   // You can also define this class in a separate file, as long as you add the
   // "public" access modifier.
   static class UserPet{
       public String userName;
       public String petName;
   }
}

数据库迁移

随着业务的扩展有时候需要对数据库调整一些字段。当数据库升级时,需要保存已有的数据。

Room 使用 Migration 来实现数据库的迁移。每个 Migration 都指定了 startVersionendVersion 。在运行的时候Room运行每个 Migrationmigrate() 方法,按正确的顺序来迁移数据库到下个版本。如果没有提供足够的迁移信息,Room会重新创建数据库,这意味着将会失去原来保存的信息。

迁移是很重要的,可能会导致应用程序崩溃。为了保持应用程序的稳定性,您应该事先测试迁移。

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");
    }
};

注意:如果您不提供必要的迁移,则 Room 会重新构建数据库,这意味着您将丢失数据库中的所有数据。


以上所述就是小编给大家介绍的《JetPack 之 Room》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Algorithms Illuminated (Part 2)

Algorithms Illuminated (Part 2)

Tim Roughgarden / Soundlikeyourself Publishing, LLC / 2018-8-5 / USD 17.99

Algorithms are the heart and soul of computer science. Their applications range from network routing and computational genomics to public-key cryptography and machine learning. Studying algorithms can......一起来看看 《Algorithms Illuminated (Part 2)》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具