Android arch - Room 的使用

概述 : 

Room 有3个主要组成部分:

  • 数据库:包含数据库持有者,并作为应用程序持久化的关系数据的底层连接的主要访问点。

用 @Database 注解的类应满足以下条件:

        1.是一个继承 RoomDatabase 的抽象类。

        2.在注解中包含与数据库相关联的实体列表。

        3.包含一个具有0个参数的抽象方法,并返回用 @Dao 注解的类。

  • 实体:表示数据库内的表。
  • DAO:包含用于访问数据库的方法。

这些组件,以及它们与应用程序的其余部分的关系,如图所示:

 

基本使用:

添加依赖:

dependencies {
    def room_version = "1.1.1"

    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version" // use kapt for Kotlin

    // optional - RxJava support for Room
    implementation "android.arch.persistence.room:rxjava2:$room_version"

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "android.arch.persistence.room:guava:$room_version"

    // Test helpers
    testImplementation "android.arch.persistence.room:testing:$room_version"
}

AppDatabase.java

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

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.
}

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")
    User findByName(String first, String last);

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

    @Delete
    void delete(User user);
}

获得创建数据库的实例:

AppDatabase db = Room.databaseBuilder(getApplicationContext(),
        AppDatabase.class, "database-name").build();
List<User> users = db.userDao().getAll();

使用实体定义数据:

@Ignore注解不想保留的字段:

@Entity
public class User {
    @PrimaryKey
    public int id;

    public String firstName;
    public String lastName;

    @Ignore
    Bitmap picture;
}

使用主键:

每个实体必须至少定义一个字段作为主键。即使只有一个字段,您仍需要使用 @PrimaryKey  注解该字段 。另外,如果你想Room 自动分配ID,您可以设置 @PrimaryKey 的 autoGenerate 属性。如果实体具有复合主键,则可以使用 primaryKeys 属性,如以下代码段所示:

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

    @Ignore
    Bitmap picture;
}

设置表名称:

注意 : SQLite中的表名称不区分大小写

默认情况下,Room 使用类名作为数据库表名。如果希望表具有不同的名称,请设置 @Entity 的 tableName 属性 

@Entity(tableName = "users")
public class User {
    ...
}

设置列名称:

Room使用字段名称作为数据库中的列名称。如果希望列具有不同的名称,请将 @ColumnInfo 注解添加到字段中,如以下代码段所示:

@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 注解中包含 indices 属性,列出要包含在索引或复合索引中的列的名称。下面的代码片段演示了这个过程:

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

    public String firstName;
    public String address;

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

    @Ignore
    Bitmap picture;
}

有时,数据库中的某些字段或字段组必须是唯一的。可以通过将  @Index 注解的 unique 属性设置为true来强制执行此唯一性属性。下面的代码示例防止表中包含两个行,它们包含 firstName 和 lastName 列的相同值集:

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

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

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

    @Ignore
    Bitmap picture;
}

定义外键约束:

例如,如果有另一个实体称为Book,则可以使用 @ForeignKey 注解定义其与User实体的关系,如下面的代码片段所示:

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

创建嵌套对象:

例如,我们的 User 类可以包括一个 Address 字段,该字段表示名为“street”、“city”、“state”和“postCode”的字段的组合。若要将组合列单独存储在表中,请在 User 类中使用 @Embedded 注解的 Address 字段,如下面的代码片段所示:

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

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

@Entity
public class User {
    @PrimaryKey
    public int id;

    public String firstName;

    @Embedded
    public Address address;
}

如果实体具有相同类型的多个嵌入字段,则可以通过设置  prefix 属性来保持每个列的唯一性。然后,Room 会将所提供的值添加到嵌入对象中的每个列名称的开头。

  @Embedded(prefix = "foo_")
   Coordinates coordinates;

使用Room DAO访问数据:

注意:除非在 builder 上调用了 allowMainThreadQueries(),否则 Room 不支持主线程上的数据库访问,因为它可能会长时间锁定UI。

增:

@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 只接收1个参数,则可以返回一个 long 类型的新插入 item 的 rowId。如果参数是数组或集合,则应该返回long[] 或者 List<Long>

删:

删除方法从数据库中移除一组作为参数的实体。它使用 primary keys 来查找要删除的实体。

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

虽然通常不需要,但可以使用此方法返回 INT 值,以指示从数据库中删除的行数。

改:

更新方法在数据库中修改作为参数的一组实体。它使用与每个实体的 primary keys 匹配的查询。

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

虽然通常不需要,但可以使用此方法返回 INT 值,以指示数据库中更新的行数。

查:

@Query 是 DAO 中使用的主要注解。它允许您在数据库上执行读/写操作。每个 @Query 方法在编译时被验证,因此,如果存在查询问题,则会发生编译错误而不是运行时故障
Room 还验证查询的返回值,这样如果返回对象中字段的名称与查询响应中的相应列名不匹配,则 Room 将以以下两种方式之一提醒您:
1.如果只有一些字段名匹配,则发出警告。
2.如果没有字段名匹配,则会出错。

传递参数查询:

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

传递一组参数查询:

@Dao
public interface MyDao {
    @Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
    public List<NameTuple> loadUsersFromRegions(List<String> regions);
}

RxJava2:

@Dao
public interface MyDao {
    @Query("SELECT * from user where id = :id LIMIT 1")
    public Flowable<User> loadUserById(int id);
}

多表查询:

下面的代码片段展示了如何执行表连接来合并包含借书用户的表和包含当前借书数据的表之间的信息:

@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数据库:

警告:要使迁移逻辑按预期运行,请使用完整查询,而不是引用表示查询的常量。

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 会将数据库的架构信息导出到 JSON 文件中。要导出架构,请room.schemaLocationbuild.gradle文件中设置注释处理器属性,如以下代码段所示:

android {
    ...
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation":
                             "$projectDir/schemas".toString()]
            }
        }
    }
}

使用Room引用复杂数据:

使用类型转换器:

有时,需要使用您希望将其值存储在单个数据库列中的自定义数据类型。要为自定义类型添加此类支持,请提供 TypeConverter,它将自定义类转换为Room可以保留的已知类型。

例如,如果我们想要持久化实例Date,我们可以编写以下内容 TypeConverter 来在数据库中存储等效的Unix时间戳:

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

接下来,添加 @TypeConverters 注释到 AppDatabase 类:

@Database(entities = {User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值