持久庫Room
Room在SQLite上提供了一個抽象層钦听,以便在利用SQLite的全部功能的同時使流暢的數(shù)據(jù)庫訪問鲫剿。
需要處理一些重要的結(jié)構(gòu)化數(shù)據(jù)的App通常會從本地的持久數(shù)據(jù)中受益匪淺备韧。最常見的就是使用本地緩存,這樣的話下次如果設備無法聯(lián)網(wǎng)用戶也能瀏覽本地數(shù)據(jù)并進行更改被济。等下次聯(lián)網(wǎng)后再和服務器進行同步拍冠。
Android的Framework為了支持處理原始SQL而提供了SQLite這一強大的API,當時SQLite的API還是相對比較低級缴阎,在使用的時候需要花費大量的經(jīng)歷:
- 沒有對原始SQL語句的編譯時驗證允瞧,隨著數(shù)據(jù)庫表格的更改,你需要更新相關SQL操作蛮拔,而這個過程可能耗時且容易出錯述暂。
- 你需要使用大量的樣板代碼在SQL查詢和Java數(shù)據(jù)對象之間進行轉(zhuǎn)換。
Room
在為SQL提供抽象層的同時也會考慮到上述的問題建炫。
下面是Room中三個主要組件:
Database:此組件用于創(chuàng)建數(shù)據(jù)庫的持有者畦韭,同時在類層級上使用注解來定義一系列的
Entity
,這些Entity對應著數(shù)據(jù)庫中的表格肛跌。Database類中的方法則用來獲取對應的DAO列表艺配。Database是App層與底層SQLite之間的連接點。
在應用中要使用此組件的話需要繼承RoomDatabase
惋砂。然后通過Room.databaseBuilder()
或者Room.inMemoryDatabaseBuilder().
獲得該類的實例妒挎。(講到這里其實讀者可以發(fā)現(xiàn),這不就是GreenDao嗎西饵???)酝掩。Entity:此組件的一個實例表示數(shù)據(jù)庫的一行數(shù)據(jù),對于每個Entity類來說眷柔,都會有對應的
table
被創(chuàng)建期虾。想要這些Entity被創(chuàng)建遏匆,就需要寫在上面Database的注解參數(shù)entities
列表中绘面。默認Entity中的所有字段都會拿來創(chuàng)建表,除非在該字段上加上@Ignore
注解。
注意:Entity默認都只有空的構(gòu)造方法(如果DAO類可以訪問每個持久化字段)含末,或者構(gòu)造方法的參數(shù)與Entity中的字段的類型和名字相匹配。Room可以使用全字段構(gòu)造方法胸懈,也可以使用部分字段構(gòu)造方法垄开。
-
DAO:這個組件用來表示具有
Data Access Object(DAO)
功能的類或接口。DAO類是Room的重要組件聋涨,負責定義訪問數(shù)據(jù)庫的方法晾浴。繼承RoomDatabase
的類必須包含一個0參數(shù)且返回DAO類的方法。當在編譯期生成代碼的時候牍白,Room會創(chuàng)建實現(xiàn)此DAO的類脊凰。
注意:通過使用DAO類而不是傳統(tǒng)的查詢接口來訪問數(shù)據(jù)庫,可以做到數(shù)據(jù)庫組件的分離茂腥。同時DAO可以在測試APP時支持Mock數(shù)據(jù)狸涌。
下面是其三者和數(shù)據(jù)庫的關系圖:
下面看一下簡單的實例,其包含一個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<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.java
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
當創(chuàng)建完這些文件后,你就可以使用下面的方法來獲得被創(chuàng)建的AppDatabase實例:
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
注意:實例化AppDatabase對象時仑性,應遵循單例設計模式惶楼,因為每個數(shù)據(jù)庫實例都相當昂貴,而且很少需要訪問多個實例诊杆。
Entity
當一個類被添加了@Entity
注解并且在Database的@entities
被引用歼捐,Room就會為其創(chuàng)建對應的數(shù)據(jù)庫。
默認情況Room會為Entity的每個字段創(chuàng)建對應的數(shù)據(jù)庫列晨汹,如果某個字段不想被創(chuàng)建的話可以使用@Ignore
注解:
@Entity
class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
@Ignore
Bitmap picture;
}
為了Room可以訪問到Entity的字段豹储,你可以將這些字段聲明為public
,或者可以給這些字段提供setter
和getter
方法淘这。如果使用setter和getter的話剥扣,需要注意命名規(guī)則。具體參照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的類名來作為數(shù)據(jù)庫的表名贤姆。如果想自定義表名,可以使用@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;
}
索引及唯一性
在適當?shù)淖侄紊咸砑铀饕梢约涌鞌?shù)據(jù)庫的訪問速度,要在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;
}
有些情況下音婶,數(shù)據(jù)庫中的某個字段或字段組合必須是唯一的慨畸,可以通過將@Index的屬性unique
設置為ture來實現(xiàn)這一唯一性莱坎。以下代碼用于放置User表中出現(xiàn)姓名組合相同的數(shù)據(jù)。
@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是關系型數(shù)據(jù)庫寸士,所以你可以指定對象之間的關系檐什,但在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;
}
外鍵是十分強大的瓮具,它允許你指定引用實體發(fā)生更新是發(fā)生的行為,比如凡人,當需要刪除一個用戶的時候刪除其下所有的圖書名党,只需要為Book的@ForeignKey的屬性onDelete
設置為CASCADE
。
注意:SQLite在處理
@Insert(onConflict=REPLACE)
的時候挠轴,其實是進行了REMOVE
和REPLACE
兩個操作传睹,而不是單單的UPDATE
。此時這里的REMOVE操作可能會影響到對應的外鍵岸晦,
嵌套對象
有時你需要在數(shù)據(jù)庫邏輯中表達一個實體或者Java類欧啤,你可以使用@Embedded
注解來實現(xiàn)。具體看例子启上。
例如上面的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的時候?qū)refix的值加在列名前。
Data Access Objects (DAOs)
Room中的主要組件就是Dao
系宜,DAO以簡潔的方式抽象訪問數(shù)據(jù)庫照激。
Intert
當你創(chuàng)建了一個DAO的方法并加上@Insert
注解,Room就會生成一個這個方法是實現(xiàn)盹牧,用于完成此次插入操作:
@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);
}
如果插入方法只接受一個參數(shù)的話俩垃,表示僅僅插入一條數(shù)據(jù),這是這個方法可以返回一個long型值汰寓,為新行的id口柳。如果參數(shù)為數(shù)組或集合,則需要返回對應的long[]
或者List<Long>
有滑。
Update
Update是一個用于更新批量數(shù)據(jù)的實用方法跃闹,它通過主鍵來匹配需要更改數(shù)據(jù)庫數(shù)據(jù):
@Dao
public interface MyDao {
@Update
public void updateUsers(User... users);
}
此方法可以返回一個int型數(shù)據(jù),表示此次修改影響到的行數(shù)毛好。
DELETE
Delete用于批量刪除數(shù)據(jù)庫中的數(shù)據(jù)望艺,它也是通過主鍵來匹配需要刪除的數(shù)據(jù):
@Dao
public interface MyDao {
@Delete
public void deleteUsers(User... users);
}
此方法可以返回一個int型數(shù)據(jù),表示此次刪除的行數(shù)肌访。
QUERY
@Query
是DAO中的一個重要注解找默,它允許你對數(shù)據(jù)庫進行讀寫操作。每一個@Query方法都會在編譯期做校驗吼驶,所以如果query存在問題的話惩激,你的App編譯將無法通過。
Room同時也會校驗query的返回值蟹演,如果返回結(jié)果和查詢語句中的結(jié)果不匹配风钻,Room將會以一下兩種方式提醒你:
- 如果有部分字段匹配的話會給出警告。
- 如果沒有字段匹配轨帜,則給出錯誤提示魄咕。
簡單的查詢
@Dao
public interface MyDao {
@Query("SELECT * FROM user")
public User[] loadAllUsers();
}
這是一個加載所有用戶的查詢,寫法比較簡單蚌父。在編譯期哮兰,Room知道需要查詢User的所有列的值。如果查詢語句包含語法錯誤或者沒有user這個表苟弛,則Room會在編譯時期報錯并給出錯誤信息喝滞。
查詢的參數(shù)傳遞
大部分情況,你需要給查詢語句傳遞特定的參數(shù)膏秫,比如查詢特定年齡段的User右遭,如下:
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}
在編譯器處理這個查詢操作的時候,Room會將參數(shù)minAge與:minAge
進行綁定。如果此時無法匹配窘哈,則會出現(xiàn)編譯錯誤吹榴。
當然也可以傳遞多個參數(shù),如下:
@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的一部分字段滚婉,例如你的UI只需要先死User的姓名图筹,而不是所有信息。這是為了保證UI的更新速度让腹,你會選擇只查詢姓名這個兩個數(shù)據(jù)远剩。
只要可以將查詢的結(jié)果集映射到返回對象的字段,你就可以返回任何對象骇窍,如下:
public class NameTuple {
@ColumnInfo(name="first_name")
public String firstName;
@ColumnInfo(name="last_name")
public String lastName;
}
現(xiàn)在你可以在DAO中使用NameTuple了瓜晤。
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user")
public List<NameTuple> loadFullName();
}
Room能夠返回的first_name
和last_name
能夠映射到NameTuple,所以Room會生成相應的賦值代碼腹纳。如果返回字段太多或者字段不存在于NameTuple中痢掠,則會發(fā)生編譯出錯。
注意:這里的NameTuple也可以使用@Embedded注解只估。
將集合作為參數(shù)傳遞
有些情況當你查詢時需要傳遞較多的變量志群,例如想要查詢某一地區(qū)集合下的所有用戶,這個集合可能包含幾十個地區(qū)蛔钙,如果用上述簡單的參數(shù)傳遞恐怕夠嗆,現(xiàn)在看看怎么用集合傳遞:
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public List<NameTuple> loadUsersFromRegions(List<String> regions);
}
Room可以判斷你傳遞的是集合荠医,并在SQL語句中將你的參數(shù)進行展開并填充吁脱。
可監(jiān)聽的查詢
在進行查詢的時候,你希望UI會在查詢結(jié)束后自動更新UI彬向,為了滿足這一點兼贡,這里可以使用前面講到的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);
}
如果你比較熟悉RxJava娃胆,那么很高興告訴你遍希,Room同樣支持返回ExJava2中的Publisher
和Flowable
對象,如下:
@Dao
public interface MyDao {
@Query("SELECT * from user where id = :id LIMIT 1")
public Flowable<User> loadUserById(int id);
}
直接返回Cursor
如果你的App中有部分邏輯需要直接用Cursor的話里烦,可以將DAO的返回值設置為Curso凿蒜,如下:
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge LIMIT 5")
public Cursor loadRawUsersOlderThan(int minAge);
}
注意:Room很不推薦使用以上Cursor的方法,應為你并不知道Cursor有無數(shù)據(jù)或者包含哪些列胁黑。
多表聯(lián)查
Room支持多表聯(lián)查废封,如果返回數(shù)據(jù)是可監(jiān)聽的,那么Room會監(jiān)聽所有查詢中涉及到的表并及時更新數(shù)據(jù)丧蘸。
下面這個例子是通過內(nèi)聯(lián)查詢某個名字下借閱的圖書:
@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);
}
你也可以通過查詢返回純java對象漂洋,如下:
@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;
}
}
類型轉(zhuǎn)換
Room中的類型轉(zhuǎn)換支持你將某個類的值存儲到某一列中,為此Room提供了TypeConverter
這個類用于將自定義類轉(zhuǎn)換成Room所支持的類型。
例如我們想要將Date
對象進行存儲刽漂,我們可以這么寫:
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();
}
}
這樣定義完以后演训,下次Room遇到Date,就能將其轉(zhuǎn)換成Room所支持的Long了贝咙。
下面看看AppDatabase要怎么寫:
@Database(entities = {User.java}, version = 1)
@TypeConverters({Converter.class})
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
在AppDatabase上添加TypeConverters注解
,并將Converter作為其參數(shù)仇祭。
接著User實體:
@Entity
public class User {
...
private Date birthday;
}
然后是DAO:
@Dao
public interface UserDao {
...
@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
List<User> findUsersBornBetweenDates(Date from, Date to);
}
這里你可以對@TypeConverter做一些范圍限制,比如限制只能在某個Entity颈畸,某個DAO或某個DAO方法中使用乌奇。詳細說明可見@TypeConverter文檔。
數(shù)據(jù)庫迭代升級
當你的App迭代升級的時候眯娱,也需要給你的Entity做迭代升級礁苗,為此你將修改Entity的代碼。當你的用戶升級到最新的App版本的時候徙缴,你可不希望他們丟失老版本的所有數(shù)據(jù)试伙,尤其是在沒有服務器備份的情況下。
Room支持通過寫Migration
類來保留用戶數(shù)據(jù)于样。每個Migration都需要指定上一個版本和現(xiàn)在的版本疏叨,在App運行的時候,Room會運行每一個Migration的migrate
方法穿剖,并使用正確順序?qū)?shù)據(jù)庫升級到最新版本蚤蔓。
注意:如果你不提供Migration的話,Room會重建數(shù)據(jù)庫而不是升級數(shù)據(jù)庫糊余,這樣的后果就是用戶數(shù)據(jù)會全部都是秀又。
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會進行schema的驗證昏苏,如驗證有誤,則會拋出異常威沫。
測試升級
Migration并不是簡單的數(shù)據(jù)庫寫入操作贤惯,一旦升級失敗,會對App致命的Crash壹甥。為了保證應用的穩(wěn)定性救巷,應該事先測試Migration,Room提供了一套測試框架句柠,下面我們來簡單學習下浦译。
導出Schema文件
Room需要將你數(shù)據(jù)庫的Schema已Json格式的文件導出棒假,為了導出Schema,需要在build.gradle
中做如下配置:
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
}
你需要將導出的Json文件保存起來精盅,以便Room通過schema文件創(chuàng)建老版數(shù)據(jù)庫進行升級測試帽哑。
為了進行升級測試,需要將android.arch.persistence.room:testing
添加到你的測試依賴當中叹俏,然后添加如下配置:
android {
...
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
}
測試框架提供了名為MigrationTestHelper
的類妻枕,它可以讀取schema文件,這也是一個遵循Junit4
測試原則的類粘驰。具體測試代碼如下:
@RunWith(AndroidJUnit4.class)
public class MigrationTest {
private static final String TEST_DB = "migration-test";
@Rule
public MigrationTestHelper helper;
public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getContext(),
MigrationDb.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}
@Test
public void migrate1To2() throws IOException {
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
// db has schema version 1. insert some data using SQL queries.
// You cannot use DAO classes because they expect the latest schema.
db.execSQL(...);
// Prepare for the next version.
db.close();
// Re-open the database with version 2 and provide
// MIGRATION_1_2 as the migration process.
db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);
// MigrationTestHelper automatically verifies the schema changes,
// but you need to validate that the data was migrated properly.
}
}
測試數(shù)據(jù)庫
當你的應用程序運行測試時屡谐,如果你沒有測試數(shù)據(jù)庫本身,則不需要創(chuàng)建完整的數(shù)據(jù)庫蝌数。Room允許你輕松地模擬測試中的數(shù)據(jù)訪問層愕掏。這個過程是可能的,因為您的DAO不會泄露您的數(shù)據(jù)庫的任何細節(jié)顶伞。測試其余的應用程序時饵撑,應該創(chuàng)建DAO類的模擬或假的實例。
這里推薦在Android設備上編寫JUnit測試唆貌,因為這些測試并不需要UI的支持滑潘,所以這些測試會比UI測試速度更快。
測試代碼如下:
@RunWith(AndroidJUnit4.class)
public class SimpleEntityReadWriteTest {
private UserDao mUserDao;
private TestDatabase mDb;
@Before
public void createDb() {
Context context = InstrumentationRegistry.getTargetContext();
//將數(shù)據(jù)庫建在內(nèi)存中锨咙,可以讓你的測試整體更加一體化语卤,更密閉。
mDb = Room.inMemoryDatabaseBuilder(context, TestDatabase.class).build();
mUserDao = mDb.getUserDao();
}
@After
public void closeDb() throws IOException {
mDb.close();
}
@Test
public void writeUserAndReadInList() throws Exception {
User user = TestUtil.createUser(3);
user.setName("george");
mUserDao.insert(user);
List<User> byName = mUserDao.findUsersByName("george");
assertThat(byName.get(0), equalTo(user));
}
}
補充:禁止Entity之間的相互引用
將數(shù)據(jù)庫中的關系映射到相應的對象模型是一個常見的做法蓖租,在服務器端可以很好地運行粱侣,在訪問它們時,它們可以很方便地加載字段蓖宦。
然而,在客戶端油猫,延遲加載是不可行的稠茂,因為它可能發(fā)生在UI線程上,并且在UI線程中查詢磁盤上的信息會產(chǎn)生顯著的性能問題情妖。UI線程有大約16ms的時間來計算和繪制Activity的更新的布局睬关,所以即使一個查詢只需要5 ms,你的應用程序仍然可能耗盡用于繪制的時間毡证,引起明顯的卡頓电爹。更糟糕的是,如果并行運行單獨的事務料睛,或者設備忙于其他磁盤重的任務丐箩,則查詢可能需要更多時間才能完成摇邦。但是,如果不使用延遲加載屎勘,則應用程序?qū)@取比其需要的更多數(shù)據(jù)施籍,從而產(chǎn)生內(nèi)存消耗問題。
ORM通常將此決定留給開發(fā)人員概漱,以便他們可以為應用程序的用例做最好的事情丑慎。不幸的是,開發(fā)人員不會在他們的應用程序和UI之間共享模型瓤摧。UI隨著時間的推移而變化竿裂,難以預料和調(diào)試的問題會不斷出現(xiàn)。
例如照弥,使用加載Book對象列表的UI為例腻异,每本書都有一個Author對象。你可能最初設計你的查詢時使用延遲加載产喉,以便Book的實例使用getAuthor()方法來返回作者捂掰。一段時間后,你意識到需要在應用中顯示作者姓名曾沈。你可以輕松添加方法調(diào)用这嚣,如以下代碼片段所示:
authorNameTextView.setText(user.getAuthor().getName());
就這么一個簡單的操作,導致了在主線程中訪問數(shù)據(jù)庫塞俱。如果Author用引用了另一張表姐帚,那情況可能更糟糕。如果需求變化障涯,這個界面不在需要作者姓名罐旗,那么你的代碼可能會做無畏的延遲加載。
基于以上原因唯蝶,Room禁止Entity之間的引用九秀,如果需要加載相關數(shù)據(jù),可以使用顯示的方法去加載粘我。