在第一节中主要介绍了Licycle、LiveData以及ViewModel的简单使用,这一节记录一下Room的学习。
什么是Room?
附上官方文档:https://developer.android.google.cn/training/data-storage/room/index.html
Room持久库提供了一个SQLite抽象层,使得我们访问数据更加的稳健,提升数据库性能。能够让用户在无网络连接的情况下继续使用APP。连接网络后对服务器的数据进行更新。
引用官方的一张框架图,更好的解释Room的作用以及意义:
Room数据持久层框架只要由三个组件构成:
RoomDatabase
包含数据库持有者,充当与应用程序持久化的、关系型的数据的底层连接的主要访问点
获取实例时,必须使用@Database注解
,指定entities以及version
;继承至RoomDatabase
且使用abstract
修饰;内部必须包含一个无参且返回Dao层(使用@Dao注解)
的抽象方法;
运行时通过调用Room.databaseBuilder()
或Room.inMemoryDatabaseBilder()
获取数据库实例。Entity
:表示数据库内的表,@Entity
注解Dao
:包含访问数据库的方法,被@Dao
所注解
简单的使用:
导入库:https://developer.android.google.cn/jetpack/androidx/releases/room
def room_version = "2.1.0-alpha06"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" // use kapt for Kotlin
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
- Book–Entity层:
import android.arch.persistence.room.ColumnInfo;
import android.arch.persistence.room.Entity;
import android.arch.persistence.room.Ignore;
import android.arch.persistence.room.PrimaryKey;
/**
* 创建时间: 2019/3/26 16:58
* 描述: TODO
*/
@SuppressWarnings("unused")
@Entity
public class Book {
@PrimaryKey(autoGenerate = true)
public long id;
@ColumnInfo(name = "book_name")
public String bookName;
@ColumnInfo(name = "book_price")
public double price;
@Ignore
public String ISBN;
@Override
public String toString() {
return "Book{" +
"id=" + id +
", bookName='" + bookName + '\'' +
", price=" + price +
", ISBN='" + ISBN + '\'' +
'}';
}
}
- BookDao–Dao层:
import android.arch.persistence.room.Dao;
import android.arch.persistence.room.Insert;
import android.arch.persistence.room.Query;
import java.util.List;
@Dao
public interface BookDao {
@Insert
void insert(Book book);
@Query("select * from book")
List<Book> getBooks();
@Delete
void delete(long id);
@Query("SELECT * FROM book WHERE id IN (:bookIds)")
List<Book> loadAllByIds(long[] bookIds);
}
- Database:
import android.arch.persistence.room.Database;
import android.arch.persistence.room.RoomDatabase;
/**
* 创建时间: 2019/3/26 16:54
* 描述: TODO
*/
@SuppressWarnings("unused")
@Database(entities = {Book.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract BookDao getBookDao();
}
- 获取数据库实例且使用:
单进程中,尽量使用单例来获取数据库实例,因为获取数据库实例耗时且浪费资源。
多进程中,在build的过程中添加enableMultiInstanceInvalidation(),每个进程都持有一个数据库实例的引用。
mAppDatabase = Room.databaseBuilder(this, AppDatabase.class, "book.db")
.allowMainThreadQueries() // 允许在主线程中访问数据库
.addMigrations(MIGRATION_1_2, MIGRATION_2_3) // 数据库升级的逻辑
.build();
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE Book ADD COLUMN count integer");
}
};
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("");
}
};
// 使用
App.getInstance().getAppDatabase().getBookDao().insert(book);
List<Book> books = App.getInstance().getAppDatabase().getBookDao().getBooks();
Entity的详细介绍
Room库会将使用了@Entity注解的实体类自动映射到Database中转换成表。
常用的注解有:
注解 | 作用 |
---|---|
@PrimaryKey | 定义主键 |
@ColumnInfo | 定义列 |
@Ignore | 过滤,不想持久化的字段 |
@Embedded | 嵌入实体 |
@Index | 索引相关 |
- 使用@Entity定义实体
注意:默认使用类名当作表名,可通过tableName指定表名,不区分大小写。
@Entity(tableName = "address")
public class Address {
@PrimaryKey
public long addId;
public String add;
}
- 使用@PrimaryKey定义主键
每个实体类至少包含一个主键,即使只有1个字段,仍然需要用@PrimaryKey
注解字段;想要自动分配ID给主键,在注解中加入
autoGenerate = true
即可;如果实体具有复合组件使用primaryKeys = {"firstName","lastName"}
即可。
@Entity(tableName = "user",primaryKeys = {"firstName","lastName"})
public class User {
@PrimaryKey(autoGenerate = true)
public long userId;
public String firstName;
public String lastName;
}
- 默认使用属性名作为表的列名,可使用
@ColumnInfo(name="xx")
自定义列名
@Entity
public class Book {
@PrimaryKey(autoGenerate = true)
public long id;
@ColumnInfo(name = "book_name")
public String bookName;
@ColumnInfo(name = "book_price")
public double price;
}
- 忽略字段
让某个字段不被加入到数据库中,使用@Ignore
注解即可。
@Entity
public class Book {
@PrimaryKey(autoGenerate = true)
public long id;
@ColumnInfo(name = "book_name")
public String bookName;
@ColumnInfo(name = "book_price")
public double price;
@Ignore
public String ISBN;
}
实体类继承自某个实体类,要忽略父类的属性如何实现?使用ignoredColumns
即可
@Entity(ignoredColumns = "ISBN")
public class PaperBook extends Book{
@PrimaryKey
public int paper;
public boolean hasPaper;
}
- 支持全文搜索(Full-text Search 简称FTS),即创建一张虚拟表用来查询
需要在Room 2.1.0以上且在实体上添加@Fts3
或者@Fts4
注解即可。
仅当您的应用程序具有严格的磁盘空间要求或者您有时,才使用
@ Fts3
需要与较旧的SQLite版本兼容。
FTS表支持的实体指定主键是可选的,但是如果包含一个,则必须使用此类型和列名称。
@Fts4
@Entity(ignoredColumns = "ISBN")
public class PaperBook extends Book{
@PrimaryKey
//FTS表支持的实体指定主键是可选的,但是如果包含一个,则必须使用此类型和列名称。
@ColumnInfo(name = "rowid")
public int paper;
public boolean hasPaper;
}
注意:启用FTS的表始终使用INTEGER类型的主键和列名“rowid”。 如果由FTS表支持的实体定义主键,则必须使用该类型和列名称。
- 索引
可以索引数据库中的某些列以加快查询速度。 要向实体添加索引,请在@Entity批注中包含indices
属性,列出要包含在索引或复合索引中的列的名称。
@Entity(indices = {@Index("name"), @Index(value = {"firstName", "address"})})
public class User {
@PrimaryKey(autoGenerate = true)
public long userId;
public String firstName;
public String lastName;
public String address;
}
数据库中的某些字段或字段组必须是唯一的。可以通过将@Index注解的唯一属性设置为true来强制执行此唯一性属性。下面的代码示例防止表中包含两个行,它们包含firstName和address列的相同值集:
@Entity(indices = {@Index(value = {"firstName", "address"},unique = true)})
public class User {
@PrimaryKey(autoGenerate = true)
public long userId;
public String firstName;
public String lastName;
public String address;
}
- AutoValue-based objects
在2.1.0及更高版本的房间中,您可以使用基于Java的不可变值类(使用@AutoValue进行注释)作为应用程序数据库中的实体。 如果实体的两个实例的列包含相同的值,则认为这两个实例是相等的。如何使用?
@AutoValue
@Entity
public abstract class User {
// Supported annotations must include `@CopyAnnotations`.
@CopyAnnotations
@PrimaryKey
public abstract long getId();
public abstract String getFirstName();
public abstract String getLastName();
// Room uses this factory method to create User objects.
public static User create(long id, String firstName, String lastName) {
return new AutoValue_User(id, firstName, lastName);
}
}
- 实体间的关系
- 由于SQLite是关系数据库,因此您可以指定对象之间的关系。 尽管大多数对象关系映射库允许实体对象相互引用,但Room明确禁止这样做。即使您不能使用直接关系,Room仍允许您在实体之间定义外键约束。
在@Entity
中加入foreignKeys = @ForeignKey(entity = User.class,
parentColumns = “userId”,childColumns = “user_id”)
entity 为关联的实体类;parentColumns 在关联对象中的列名;childColumns在此对象中的列名
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "userId",childColumns = "user_id"))
public class Book {
@PrimaryKey(autoGenerate = true)
public long id;
@ColumnInfo(name = "book_name")
public String bookName;
@ColumnInfo(name = "book_price")
public double price;
@Ignore
public String ISBN;
@ColumnInfo(name = "user_id")
public int uId;
}
注意:外键非常强大,因为它们允许您指定更新引用实体时发生的情况。 例如,如果通过在@ForeignKey批注中包含onDelete = CASCADE来删除相应的User实例,则可以告诉SQLite删除用户的所有书籍。
- 创建嵌套对象
有时,您希望将实体或普通旧Java对象(POJO)表达为数据库逻辑中的一个整体,即使该对象包含多个字段。您可以像查询其他单个列一样查询嵌入字段。这种情况下我们可以使用@Embedded
注解来实现。
public class Address {
public long addId;
public String city;
public String province;
}
@Entity(indices = {@Index(value = {"firstName"},unique = true)})
public class User {
@PrimaryKey(autoGenerate = true)
public long userId;
public String firstName;
public String lastName;
@Embedded
public Address address;
}
这样我们就可以在User表中正常的使用Address中的属性。
注意:嵌入字段还可以包含其他嵌入字段。
如果实体具有多个相同类型的嵌入字段,则可以通过设置prefix属性使每个列保持唯一。然后,Room将提供的值添加到嵌入对象中每个列名称的开头。
Dao层的使用
如果使用过MySql以及SSM/Spring Boot框架的同学可以略过,因为基本方法的类似。以下是官网的关于Dao层的使用介绍:
https://developer.android.google.cn/training/data-storage/room/accessing-data