android官方架构room,Android 官方架构组件介绍之 Room(翻译)

持久库Room

Room在SQLite上提供了一个抽象层,以便在利用SQLite的全部功能的同时使流畅的数据库访问。

需要处理一些重要的结构化数据的App通常会从本地的持久数据中受益匪浅。最常见的就是使用本地缓存,这样的话下次如果设备无法联网用户也能浏览本地数据并进行更改。等下次联网后再和服务器进行同步。

Android的Framework为了支持处理原始SQL而提供了SQLite这一强大的API,当时SQLite的API还是相对比较低级,在使用的时候需要花费大量的经历:

没有对原始SQL语句的编译时验证,随着数据库表格的更改,你需要更新相关SQL操作,而这个过程可能耗时且容易出错。

你需要使用大量的样板代码在SQL查询和Java数据对象之间进行转换。

Room在为SQL提供抽象层的同时也会考虑到上述的问题。

下面是Room中三个主要组件:

Database:此组件用于创建数据库的持有者,同时在类层级上使用注解来定义一系列的Entity,这些Entity对应着数据库中的表格。Database类中的方法则用来获取对应的DAO列表。Database是App层与底层SQLite之间的连接点。

在应用中要使用此组件的话需要继承RoomDatabase。然后通过Room.databaseBuilder()或者Room.inMemoryDatabaseBuilder().获得该类的实例。(讲到这里其实读者可以发现,这不就是GreenDao吗?😂)。

Entity:此组件的一个实例表示数据库的一行数据,对于每个Entity类来说,都会有对应的table被创建。想要这些Entity被创建,就需要写在上面Database的注解参数entities列表中。默认Entity中的所有字段都会拿来创建表,除非在该字段上加上@Ignore注解。

注意:Entity默认都只有空的构造方法(如果DAO类可以访问每个持久化字段),或者构造方法的参数与Entity中的字段的类型和名字相匹配。Room可以使用全字段构造方法,也可以使用部分字段构造方法。

DAO:这个组件用来表示具有Data Access Object(DAO)功能的类或接口。DAO类是Room的重要组件,负责定义访问数据库的方法。继承RoomDatabase的类必须包含一个0参数且返回DAO类的方法。当在编译期生成代码的时候,Room会创建实现此DAO的类。

注意:通过使用DAO类而不是传统的查询接口来访问数据库,可以做到数据库组件的分离。同时DAO可以在测试APP时支持Mock数据。

下面是其三者和数据库的关系图:

room_architecture.png

下面看一下简单的实例,其包含一个Entity,一个Dao以及一个Database。

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

@Query("SELECT * FROM user WHERE uid IN (:userIds)")

List 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.java

@Database(entities = {User.class}, version = 1)

public abstract class AppDatabase extends RoomDatabase{

public abstract UserDao userDao();

}

当创建完这些文件后,你就可以使用下面的方法来获得被创建的AppDatabase实例:

AppDatabase db = Room.databaseBuilder(getApplicationContext(),

AppDatabase.class, "database-name").build();

注意:实例化AppDatabase对象时,应遵循单例设计模式,因为每个数据库实例都相当昂贵,而且很少需要访问多个实例。

Entity

当一个类被添加了@Entity注解并且在Database的@entities被引用,Room就会为其创建对应的数据库。

默认情况Room会为Entity的每个字段创建对应的数据库列,如果某个字段不想被创建的话可以使用@Ignore注解:

@Entity

class User{

@PrimaryKey

public int id;

public String firstName;

public String lastName;

@Ignore

Bitmap picture;

}

为了Room可以访问到Entity的字段,你可以将这些字段声明为public,或者可以给这些字段提供setter和getter方法。如果使用setter和getter的话,需要注意命名规则。具体参照Java Beans。

Primary key

每个Entity至少定义一个主键,即使你的Entity只有一个字段也是如此。定义主键使用@PrimaryKey。如果你想让Room给你的Entity自动生成ID的话,可以使用@Primary的autoGenerate属性。如果Entity具有复合主键的话,可以使用@Entity的primaryKeys属性,参照下方代码:

@Entity(primaryKeys = {"firstName", "lastName"})

class User{

public String firstName;

public String lastName;

@Ignore

Bitmap picture;

}

默认情况Room使用Entity的类名来作为数据库的表名。如果想自定义表名,可以使用@Entity的tableName属性,如下:

@Entity(tableName = "users")

class User{

...

}

注意:SQLite中的表名是大小写不敏感的。

与上面的tableName类似,Room使用Entity的字段名来作为对应的列名,如果想要自定义类名,可以使用@ColumnInfo注解的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;

}

索引及唯一性

在适当的字段上添加索引可以加快数据库的访问速度,要在Entity上添加索引可以使用@Entity的indices属性,可以添加索引或组合索引:

@Entity(indices = {@Index("firstName"), @Index("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;

}

有些情况下,数据库中的某个字段或字段组合必须是唯一的,可以通过将@Index的属性unique设置为ture来实现这一唯一性。以下代码用于放置User表中出现姓名组合相同的数据。

@Entity(indices = {@Index(value = {"first_name", "last_name"},

unique = true)})

class User{

@PrimaryKey

public int id;

@ColumnInfo(name = "first_name")

public String firstName;

@ColumnInfo(name = "last_name")

public String lastName;

@Ignore

Bitmap picture;

}

表间关系

由于SQLite是关系型数据库,所以你可以指定对象之间的关系,但在Room中这是命令禁止的。

虽然在Room中的Entity不能有直接的引用关系,但Room任然支持在Entity间定义Foreign Key。

例如有个另一个Entity叫做Book,你可以使用@ForeignKey来定义它和User之间的关系,如下:

@Entity(foreignKeys = @ForeignKey(entity = User.class,

parentColumns = "id",

childColumns = "user_id"))

class Book{

@PrimaryKey

public int bookId;

public String title;

@ColumnInfo(name = "user_id")

public int userId;

}

外键是十分强大的,它允许你指定引用实体发生更新是发生的行为,比如,当需要删除一个用户的时候删除其下所有的图书,只需要为Book的@ForeignKey的属性onDelete设置为CASCADE。

注意:SQLite在处理@Insert(onConflict=REPLACE)的时候,其实是进行了REMOVE和REPLACE两个操作,而不是单单的UPDATE。此时这里的REMOVE操作可能会影响到对应的外键,

嵌套对象

有时你需要在数据库逻辑中表达一个实体或者Java类,你可以使用@Embedded注解来实现。具体看例子。

例如上面的User实体有一个Address类型的字段,Address包含了street,city,state和postCode这几个字段。当生成表格时,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。

注意:以上是可以多重嵌套的。

如果User中嵌套的A和B中存在相同字段,可以使用@Embedded的prefix属性,Room会在生成table的时候将prefix的值加在列名前。

Data Access Objects (DAOs)

Room中的主要组件就是Dao,DAO以简洁的方式抽象访问数据库。

Intert

当你创建了一个DAO的方法并加上@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 friends);

}

如果插入方法只接受一个参数的话,表示仅仅插入一条数据,这是这个方法可以返回一个long型值,为新行的id。如果参数为数组或集合,则需要返回对应的long[]或者List。

Update

Update是一个用于更新批量数据的实用方法,它通过主键来匹配需要更改数据库数据:

@Dao

public interface MyDao{

@Update

public void updateUsers(User... users);

}

此方法可以返回一个int型数据,表示此次修改影响到的行数。

DELETE

Delete用于批量删除数据库中的数据,它也是通过主键来匹配需要删除的数据:

@Dao

public interface MyDao{

@Delete

public void deleteUsers(User... users);

}

此方法可以返回一个int型数据,表示此次删除的行数。

QUERY

@Query是DAO中的一个重要注解,它允许你对数据库进行读写操作。每一个@Query方法都会在编译期做校验,所以如果query存在问题的话,你的App编译将无法通过。

Room同时也会校验query的返回值,如果返回结果和查询语句中的结果不匹配,Room将会以一下两种方式提醒你:

如果有部分字段匹配的话会给出警告。

如果没有字段匹配,则给出错误提示。

简单的查询

@Dao

public interface MyDao{

@Query("SELECT * FROM user")

public User[] loadAllUsers();

}

这是一个加载所有用户的查询,写法比较简单。在编译期,Room知道需要查询User的所有列的值。如果查询语句包含语法错误或者没有user这个表,则Room会在编译时期报错并给出错误信息。

查询的参数传递

大部分情况,你需要给查询语句传递特定的参数,比如查询特定年龄段的User,如下:

@Dao

public interface MyDao{

@Query("SELECT * FROM user WHERE age > :minAge")

public User[] loadAllUsersOlderThan(int minAge);

}

在编译器处理这个查询操作的时候,Room会将参数minAge与:minAge进行绑定。如果此时无法匹配,则会出现编译错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值