# Android Jetpack-Room入门
[TOC]
## 一.Room介绍
Room 持久性库在 SQLite 的基础上提供了一个抽象层,让用户能够在充分利用 SQLite 的强大功能的同时,获享更强健的数据库访问机制。
| 在 Android 中直接使用 SQLite 数据库存在多个缺点: | Room 框架具有如下特点: ? |
| ------------------------------------------------------------ | ------------------------------------------------------------ |
| ●必须编写大量的样板代码;<br/>●必须为编写的每一个查询实现对象映射;<br/>●很难实施数据库迁移;<br/>●很难测试数据库;<br/>●如果不小心,很容易在主线程上执行长时间运行的操作。 | ●由于使用了动态代理,减少了样板代码;<br/>●在 Room 框架中使用了编译时注解,在编译过程中就完成了对 SQL 的语法检验;<br/>●相对方便的数据库迁移;<br/>●方便的可测试性;<br/>●保持数据库的操作远离了主线程。<br/>●此外 Room 还支持 RxJava2 和 LiveData |
### 1.1基本使用
#### 1.1.1引用
```java
/**声明依赖**///build.gradle:app
def room_version = "2.4.2"
// Room components
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
//可选 - kotlin扩展和协程支持
//可选 -RxJava 2支持库
//可选 -RxJava 3支持库
// 可选 - Guava 的支持库
// 可选 - 测试辅助库
// 可选 - Jetpack-Paging 3
```
```java
/**配置编译器选项**///build.gradle:app
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments += [
"room.schemaLocation":"$projectDir/schemas".toString(),//配置并启用导出数据库架构.Json##版本迁移
"room.incremental":"true",//启用Gradle增量注解处理器
"room.expandProjection":"true"]//配置 Room 以重写查询,使其顶部星形投影在展开后仅包含DAO 方法返回类型中定义的列。
}
}
}
}
```
#### 1.1.2Room 的三个主要组件
- Database: 包含数据库持有者,作为与应用持久化相关数据的底层连接的主要接入点。这个类需要用 @Database 注解,并满足下面条件:
? - 必须是继承 RoomDatabase 的抽象类
? - 注解中包含该数据库相关的实体类列表
? - 包含的抽象方法不能有参数,且返回值必须是被 @Dao 注解的类
- Entity: 数据库中的一张表
- DAO: 包含了访问数据库的一系列方法
<img src="\room_1.png" style="zoom: 67%;" />
## 二.Room DataBase注解说明
**@Database注解的属性**:
| entities = ? | 数据表数组 |
| --------------- | ------------------ |
| version = ? | 版本号 |
| views= ? | 视图数组 ? |
| exportSchema=? | 是否导出数据库Json |
| autoMigrations= | 自动迁移数组 ? |
**Demo:**
```java
@Database(entities = {User.class}, version = 1)
public abstract class TestDatabase extends RoomDatabase {
private final static String DB_NAME = "test_room.db";
private volatile static TestDatabase instance;
private static final ExecutorService mThreadPool = Executors.newSingleThreadExecutor();
//数据库加密
private static String PASSWORD = "123456";
private static SupportSQLiteOpenHelper.Factory mSafeFactory = new SafeHelperFactory(PASSWORD.getBytes());
public abstract UserDao UserDao();
public static TestDatabase buildHealthDb(Context context) {
if (instance == null) {
synchronized (TestDatabase.class) {
if (instance == null) {
instance = Room.databaseBuilder(context, TestDatabase.class, DB_NAME)
.allowMainThreadQueries() //允许主线程查询
// .createFromAsset("databases/jl_health.db")// 预存数据
// .addMigrations()//版本迁移
// .addTypeConverter()//类型转换
// .enableMultiInstanceInvalidation()//应用在多个进程中运行启用,
// .fallbackToDestructiveMigration()//迁移时找不到Migration,破坏性迁移,删除数据库重建
// .openHelperFactory(mSafeFactory)// 它是用来创建 supportsqliteopenhelper 可以利用它实现自定义的 sqliteOpenHelper,来实现数据库的加密存储,默认是不加密的
// .addCallback()// 数据库创建和打开的事件会回调到这里,可以再次操作数据库
// .setTransactionExecutor()//指定事务的线程池
// .setQueryCallback()//指定查询回调 指定数据查询数据时候的线程池
// .setAutoCloseTimeout()//自动关闭数据库以释放未使用资源
// .setJournalMode()//设置数据库的日志模式
.setQueryExecutor(mThreadPool) // 指定数据查询数据时候的线程池
.build();
}
}
}
return instance;
}
public ExecutorService getThreadPool() {
return mThreadPool;
}
public void destroy() {
if (!mThreadPool.isShutdown()) {
mThreadPool.shutdownNow();
}
instance = null;
}
}
```
## 三.Room Entity注解说明
### 3.1@Entity(实体)
每个Entity代表数据库中某个表的实体类。默认情况下Room会把Entity里面所有的字段对应到表上的每一列。如果需要制定某个字段不作为表中的一列需要添加@Ignore注解。
Entity的实体类都需要添加@Entity注解。而且Entity类中需要映射到表中的字段需要保证外部能访问到这些字段(你要么把字段设为public、要么实现字段的getter和setter方法)。
**@Entity注解的属性:**
| tableName= ? | 数据表名称(默认情况下Entity类的名字就是表的名字) ? |
| -------------------- | ------------------------------------------------------------ |
| primaryKeys= | 主键数组(至少一个字段设置为主键,可以在这里设,也可以用@PrimaryKey注解设置) |
| indices= | [@Index](#index_tab)数组 |
| foreignKeys= | [@ForeignKey](#foreignkey_tab)数组 ? |
| inheritSuperIndices= | 父类的索引是否会自动被当前类继承。 ? |
**Demo:**
```java
@Entity(tableName = "user_info", primaryKeys = {"cardId"}, indices = {@Index(value = "city"},foreignKeys = {@ForeignKey(entity = User.class,parentColumns = "id",childColumns = "cageId")})
public class UserInfoEntity {
@NonNull
public String cardId;
public String city;
public String county;
public String specificAddress;
}
```
### 3.2@PrimaryKey(主键)
1. 单一主键,如果想创建多个主键,那么可以通过**@Entity(primaryKeys= [])**在类上定义。
2. 每个bean类都必须要声明一个主键,除非父类声明了。
3. 如果这个属性被**@Embedded** 定义,在定义**@PrimaryKey** 那么就会成为复合主键
**@PrimaryKey注解的属性:**
| autoGenerate= | 自动增长,唯一id |
| ------------- | ---------------- |
**Demo:**
```java
@Entity()
public class Book {
@PrimaryKey(autoGenerate = true)
public String id;
}
```
### 3.3@ColumnInfo(列信息)
默认情况下,Entity中的每一个字段都是对应表的一列。如果需要制定某个字段的列信息则需要使用@ColumnInfo注解。
特殊情况:如果字段是 **transient**,则@Entity实体会自动忽略它**,除非**用 **ColumnInfo**、**Embedded**或 **relationship**注释它。
**@ColumnInfo注解的属性:**
| name= | 设置列名字,默认为字段的名字? |
| ------------- | ----------------------------- |
| typeAffinity= | 设置列的SQLite“类型相像” ? |
| index= | 是否作为索引字段,默认为false |
| collate= ? | 设置列的排序规则 ? |
| defaultValue= | 列的默认值(默认为空) |
**Demo:**
```java
@Entity()
public class User {
@PrimaryKey(autoGenerate = true)
public String id;
/**************************************
* ColumnInfo 列信息
**************************************/
@ColumnInfo(name = "all_name", typeAffinity = ColumnInfo.TEXT, index = true, collate = ColumnInfo.BINARY, defaultValue = "")
//重命名 | 类型关联 | 是否索引 | 列排序规则 | 默认值
public String firstNameAndLastName;
}
```
### 3.4@Ignore(忽略字段)
当一个类被@Entity标记为一个实体时,它的所有字段都被持久化。如果您想排除它的一些字段,您可以用 **Ignore**标记它们。
**Demo:**
```java
@Entity()
public class Book {
@PrimaryKey(autoGenerate = true)
public String id;
/**************************************
* Ignore 忽略字段
**************************************/
@Ignore()
public String booksType;
}
```
### 3.5@Embedded(嵌套字段)
有些情况下,会需要将多个对象组合成一个对象。对象和对象之间是有嵌套关系的。Room中可以使用@Embedded注解来表示嵌入。然后可以像查看其他单个列一样查询嵌入字段。
**@Embedded注解的属性:**
| prefix= | 指定前缀,以在嵌入字段中的字段列名前加前缀。 |
| ------- | -------------------------------------------- |
**Demo:**
```java
public class Coordinates {
double latitude;
double longitude;
}
public class Address {
String street;
@Embedded(prefix = "addr_")
Coordinates coordinates;
}
```
### 3.6@Relation(关系)
设计一个关系型数据库很重要的一部分是将数据拆分成具有相关关系的数据表,然后将数据以符合这种关系的逻辑方式整合到一起。从 Room 2.2 的稳定版开始,可利用一个 @Relation 注解来支持表之间所有可能出现的关系: 一对一、一对多和多对多。
**@Relation注解的属性:**
| parentColumn= | 父Entity的列。 |
| ------------- | -------------------------------------- |
| entityColumn= | 关联的Entity的对应匹配列。 |
| entity= ? | 指定关联的Entity,默认从返回类型继承。 |
| projection=? | 指定只提取部分列,默认从返回类型推断? |
| associateBy=? | 连接表([@Junction](#junction_tab))? |
假如我们是一家宠物医院现在需要存储狗狗和主人信息,应建立基本数据表Entity两张。
**Entity结构:**
```java
/**************************************
* 狗狗信息
**************************************/
@Entity
public class Dog {
@PrimaryKey
long dogId;
long dogOwnerId;
String name;
int cuteness;
int barkVolume;
String breed;
}
/**************************************
* 主人信息
**************************************/
@Entity
public class Owner {
@PrimaryKey
long ownerId;
String name;
}
```
#### 3.6.1关系:一对一
一对一关系:假如每人只能拥有一只狗狗,且每只狗狗只能有一个主人。
现在有一个需求:需要在列表中显示所有的主人和他们的狗狗信息。
**Object结构:**
```java
/**************************************
* 主人和狗狗绑定
**************************************/
public class DogAndOwner {
Owner owner;
Dog dog;
}
```
SQL查询逻辑: 1) 运行两个查询: 一个获取所有的主人数据,一个获取所有的狗狗数据,2) 根据 owner id 来进行数据的关系映射。
```sqlite
第一步:查出所有的主人(Result 1)
SELECT * FROM Owner
第二步:查询所有的狗当dogOwnerId 对应 Reslut 1中的 ownerId
SELECT * FROM Dog WHERE dogOwnerId IN (ownerId1, ownerId2, …)
```
**@Relation实现:**
```java
//映射SQL查询关系
public class DogAndOwner {
@Embedded
Owner owner;
@Relation(parentColumn = "ownerId", entityColumn = "dogOwnerId")
Dog dog;
}
//DAO查询
@Dao
public interface DogDao {
@Transaction
@Query("SELECT * FROM Owner")
List<DogAndOwner> getOwnerAndDog();//由于 Room 会默默的帮我们运行两个查询请求,因此需要增加 @Transaction 注解来确保这个行为是原子性的。
}
```
#### 3.6.2关系:一对多
一对多关系:假如每人能拥有多只狗狗,但每只狗狗只能有一个主人。
现在的需求改变为:需要在列表中显示所有的主人和他们的多只狗狗信息。
**Object结构:**
```java
/**************************************
* 主人和多只狗狗绑定
**************************************/
public class OwnerWithDogs {
Owner owner;
List<Dog> dogs;
}
```
SQL查询逻辑: 1) 运行两个查询: 一个获取所有的主人数据,一个获取所有的狗狗数据,2) 根据 owner id 来进行数据的关系映射。
```sqlite
第一步:查出所有的主人(Result 1)
SELECT * FROM Owner
第二步:查询所有的狗当dogOwnerId 对应 Reslut 1中的 ownerId
SELECT * FROM Dog WHERE dogOwnerId IN (ownerId1, ownerId2, …)
第三步:在查询结果中将同一个owner id的Dog放在一个List中
```
**@Relation实现:**
```java
//映射SQL查询关系
public class OwnerWithDogs {
@Embedded()
Owner owner;
@Relation(parentColumn = "ownerId", entityColumn = "dogOwnerId")
List<Dog> dogs;
}
//DAO查询
@Dao
public interface DogDao {
@Transaction
@Query("SELECT * FROM Owner")
List<OwnerWithDogs> getOwnerAndDogs();
}
```
#### 3.6.3关系:多对多
一对多关系:假如每人能拥有多只狗狗,且每只狗狗能有多个主人。
现在的需求改变为:需要在列表中显示所有的主人和狗狗信息。
**当前的数据库结构不能满足需求,需要增加一个狗狗和主人的连接表:**
```java
@Entity(primaryKeys = {"dogId", "ownerId"})
public class DogOwnerCrossRef {
long dogId;
long ownerId;
}
```
**@Junction是Room中的连接表使用注解**。
<strong id="junction_tab" name="junction_tab">@Junction注解的属性:</strong>
| value= | 设置连接表 |
| ------------- | ------------------ |
| parentColumn= | 指定父Entity的列? |
| entityColumn= | 指定关联Entity的列 |
SQL查询逻辑: 1) 运行两个查询: 一个获取所有的主人数据,一个获取所有的狗狗 和 DogOwnerCrossRef 表的连接数据,2) 根据 owner id 来进行数据的关系映射。
```sqlite
第一步:查出所有的主人(Result 1)
SELECT * FROM Owner
第二步:查询所有的Dog表和DogOwnerCrossRe连接表,当连接表中的ownerId 对应 Reslut 1中的 ownerId,连接表中的dogId对应Dog表的dogId。
SELECT
Dog.dogId AS dogId,
Dog.dogOwnerId AS dogOwnerId,
Dog.name AS name,
_junction.ownerId
FROM
DogOwnerCrossRef AS _junction
INNER JOIN Dog ON (_junction.dogId = Dog.dogId)
WHERE _junction.ownerId IN (ownerId1, ownerId2, …)
```
**@Relation实现:**
```java
//映射SQL查询关系
public class OwnerWithDogs {
@Embedded()
Owner owner;
@Relation(parentColumn = "ownerId", entityColumn = "dogOwnerId",associateBy = @Junction(value = DogOwnerCrossRef.class))
List<Dog> dogs;
}
//DAO查询
@Dao
public interface DogDao {
@Transaction
@Query("SELECT * FROM Owner")
List<OwnerWithDogs> getOwnersAndDogs();
}
```
#### 3.6.4高阶数据库关系使用
**@Relation(entity= ):##指定关联的Entity##**
在关系查询时,想让返回类型不是关联Entity,而是只包含一些Entity字段的对象。所以我们需要使用另外一种方式指明关联Entity。
```java
public class Pup {
String name;
int cuteness = 11;
}
public class OwnerWithPups {
@Embedded()
Owner owner;
@Relation(
parentColumn = "ownerId",
entityColumn = "dogOwnerId",
entity = Dog.class //指定要使用的数据库实体
)
List<Pup> dogs;//该查询返回一个不同的类,比如 Pup 这样不是一个数据库实体但是包含了一些字段的对象
}
```
**@Relation(projection= ):##指定只提取部分列##**
在关系查询时,想让返回类型不是关联Entity,而是部分Entity字段。所以我们需要使用@Relation(projection= )指明返回的字段。
```java
public class OwnerWithDogNames {
@Embedded
Owner owner;
@Relation(
parentColumn = "ownerId",
entity = Dog.class,
entityColumn = "dogOwnerId",
projection = {"name"}
)
List<String> dogNames;//只想从数据库实体中返回特定的列
}
```
### 3.7@ForeignKey(外键)
<strong id="foreignkey_tab" name="foreignkey_tab">@ForeignKey注解的属性:</strong>
| entity= | parent实体类(引用外键的表的实体)。 ? |
| -------------- | ------------------------------------------------------------ |
| parentColumns= | parent外键列(要引用的外键列)。 ? |
| childColumns=? | child外键列(要关联的列)。 |
| onDelete= ? | 当parent里面有删除操作的时候,child表做的[Action](#action_tab)动作。 |
| onUpdate= ? | 当parent里面有更新操作的时候,child表做的[Action](#action_tab)动作。 |
| deferred= ? | 是否应将外键约束延迟到事务完成。 |
<strong id="action_tab" name="action_tab">Action动作:</strong>
| NO_ACTION? | 当parent中的key发生变化,child不做任何动作 ? |
| ----------- | ----------------------------------------------------------- |
| RESTRICT | 当parent中的key有依赖时,禁止对parent做动作,做动作就会报错 |
| SET_NULL | 当parent中的key发生变化,child中依赖的key会设置为NULL ? |
| SET_DEFAULT | 当parent中的key发生变化,child中依赖的key会设置为默认值 |
| CASCADE | 当parent中的key发生变化,child中依赖的key跟着变化 ? |
**Demo:**
```java
//Parent 实体
@Entity
public class Cage {
@PrimaryKey(autoGenerate = true)
public int id;
}
//Child 实体
@Entity(foreignKeys = {@ForeignKey(entity = Cage.class,
parentColumns = "id",
childColumns = "cageId")})
public class Bird {
@NonNull
@PrimaryKey(autoGenerate = true)
public int id;
public int cageId;
}
```
### 3.8@Index(索引)
数据库索引用于提高数据库表的数据访问速度的。数据库里面的索引有单列索引和组合索引。Room里面可以通过@Entity的indices属性来给表格添加索引。
添加索引通常会加快SELECT查询的速度,但会减慢INSERT或UPDATE等其他查询的速度。在添加索引时,您应该小心,以确保这些额外的成本是值得的。
<strong id="index_tab" name="index_tab">@Index注解的属性:</strong>
| value=? | 索引中列名的列表。 |
| ------- | -------------------------------------- |
| name=? | 索引的名称 |
| unique= | 是否是惟一的索引,任何副本都将被拒绝。 |
**Demo:**
```java
@Entity(indices = {@Index(value = "id", name = "cat", unique = true),
@Index(value = {"last_name", "address"})})
public class Cat {
//单列索引 - 唯一索引
@PrimaryKey
public int id;
//单列索引-ColumnInfo
@ColumnInfo(index = true)
public String firstName;
//组合索引
public String address;
@ColumnInfo(name = "last_name")
public String lastName;
/*
* *索引的名称。如果未设置,Room会将其设置为以“u”连接并以“index_${tableName}”为前缀的列列表。
* 因此,如果您有一个名为“Foo”的表,并且索引为{“bar”,“baz”},
* 那么生成的索引名将是“index_Foo_bar_baz”。
* 如果需要在查询中指定索引,则永远不应依赖此名称,而应为索引指定一个名称。
* */
}
```
## 四.Room Dao注解说明
**@Dao注解**
@Dao 将类标记为数据访问对象。主要有以下操作符:
- **Query**
- **Delete**
- **Insert**
- **Update**
### 4.1sql语句与Java函数参数使用
通过 `:value` 匹配单参数
```java
@Query("SELECT * FROM Users WHERE userid = :userId")
public abstract List<User> findUserById(int userId);
```
通过`IN(:value)`来匹配数组
```java
@Query("SELECT * FROM Users WHERE userid IN(?, ?, ?)")
public abstract List<User> findUserById(int userId1,int userId2,int userId3);
```
通过`IN(?, ?, ?)`来匹配多个参数
```java
class SongDuration {
String name;
@ColumnInfo(name = "duration")
String length;
}
@Query("SELECT name, duration FROM song WHERE id = :songId LIMIT 1")
public abstract SongDuration findSongDuration(int songId);
```
### 4.2四种SQL操作符使用
**Query**方法支持4种类型的语句:**SELECT**、**INSERT**、**UPDATE**和**DELETE**。
返回值说明:
1. 对于**SELECT**查询,Room将从方法的返回类型推断结果内容,并生成代码,该代码将自动将查询结果转换为方法的返回类型。对于单个结果查询,返回类型可以是任何数据对象(也称为pojo)。对于返回多个值的查询,可以使用**list** 或 **Array**。此外,任何查询都可能返回 **Cursor** 或任何查询结果都可以封装在 **LiveData**。
2. **INSERT**查询可以返回 **void** 或 **long**。如果它是一个 **long**,那么它的值就是这个查询插入的行的**SQLite rowid**。注意,插入多行的查询不能返回一个以上的**rowid**,所以如果返回**long**,请避免这样的语句。
3. **UPDATE**或**DELETE**查询可以返回 **void** 或 **int**。如果是 **int**,则值是**受此查询影响的行数**。
4. 如果使用**RxJava2**,还可以从查询方法返回**Flowable**或**Publisher**。由于反应性流不允许 **null**,如果查询返回一个可空类型,那么如果值为 **null** 它将不会分派任何内容(比如获取一个不存在的**Entity**行)。您可以返回**Flowable<T[]>**或**Flowable<List>**来解决这个限制。**Flowable \**和\**Publisher**都将观察数据库的更改,如果数据发生更改,则重新调度。如果希望查询数据库而不观察更改,可以使用**Maybe**或**Single**。如果 **Single**查询返回 **null**, Room将抛出 **EmptyResultSetException**。此外,如果语句是插入、更新或删除语句,则支持返回类型 **Single**、 **Maybe**和 **Completable**。只要POJO的字段与查询结果中的列名匹配,就可以从查询方法返回任意POJO。
```java
@Dao
public interface UserDao {
/**************************************
* 支持四种SQL语句:增删改查
**************************************/
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertUser(User... user);
@Delete()
void deleteUsers(User... user);
@Update(onConflict = OnConflictStrategy.REPLACE)
void updateUser(User user);
@Query("SELECT * FROM users")
List<User> findAllUser();
}
```
#### 4.2.1冲突处理策略
当插入和更新数据库数据时,如果发生冲突,数据库应怎样处理
| ABORT? | 回滚冲突事务(默认) ? |
| ------- | -------------------------- |
| REPLACE | 插入新数据,覆盖旧数据 |
| IGNORE? | 保存现有数据,忽略这次操作 |
### 4.3@Transaction(事务)
| 1.Room一次最多只能执行一个事务,其他事务将排队并按先到先得的顺序执行 |
| ------------------------------------------------------------ |
| 2.Insert ,Update ,Delete 方法默认在事务中运行 ? |
| 3.什么时候应该在Query查询使用事务: 1)如果查询结果相当大,最好在事务中运行,来接收一致的结果。否则, 如果查询结果不适合单个数据操作,由于游标窗口交换之间数据库中的更改,查询结果可能被损坏。 2)如果查询的结果是一个带有@Relation字段的POJO,则会分别查询这些字段。 要在这些查询之间获得一致的结果,还需要在单个事务中运行它们。 |
| 4.如果查询是异步的,例如,返回lifecycle。或者RxJava Flowable,在运行查询时正确处理事务,而不是在调用方法时。 |
## 五.数据库版本迁移
数据库版本迁移有两种方式:一.手动迁移,二.自动迁移。
Room 允许将迁移与自动迁移结合起来使用。比如说,从版本 1 迁移到版本 2 可以通过 Migration 来完成,版本 2 迁移到 3 则可以使用自动迁移。如果您在同一个版本上同时定义了 Migration 和自动迁移,那么只有 Migration 会生效。
在底层实现上,自动迁移会构建一个 Migration 类。
### 5.1手动迁移
第一步:提高数据库版本号
@Database( version = 2)
第二步:添加 Migration迁移
private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL(? SQL处理语句? )
}
}
第三步:将Migration添加给databaseBuilder
Room.databaseBuilder(context,Database::class.java,DATABASE_NAME).
addMigrations(MIGRATION_1_2).build()
**Demo:**
```java
@Database(entities = {UsersNew.class}, version = 2)
public abstract class AppDatabase extends RoomDatabase {
private final static String DB_NAME = "test_room.db";
private volatile static TestDatabase instance;
public abstract UserDao UserDao();
public static TestDatabase buildHealthDb(Context context) {
if (instance == null) {
synchronized (TestDatabase.class) {
if (instance == null) {
instance = Room.databaseBuilder(context, TestDatabase.class, DB_NAME)
.addMigrations(MIGRATION_1_2)
.build();
}
}
}
return instance;
}
private static final Migration MIGRATION_1_2 = new Migration() {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// 创建临时表
database.execSQL(
"CREATE TABLE users_new (userid TEXT, username TEXT, last_update INTEGER, PRIMARY KEY(userid))");
// 拷贝数据
database.execSQL(
"INSERT INTO users_new (userid, username, last_update) SELECT userid, username, last_update FROM users");
// 删除老的表
database.execSQL("DROP TABLE users");
// 改名
database.execSQL("ALTER TABLE users_new RENAME TO users");
}
};
}
```
### 5.2自动迁移
版本要求:Room版本>2.4.0-alpha01。
Room 可以针对简单的情况自动生成迁移程序,例如添加或删除列、创建新的数据库表。但是在模棱两可的场景下,Room 则需要一些帮助。
@Database( version = 3,autoMigrations = [
? AutoMigration (from = 1, to = 2),AutoMigration (from = 2, to = 3/*,spec =*/)
])
?请注意: 从实现层面来说,Room 的自动迁移依赖于所生成的数据库 schema,因此在使用 autoMigrations 时,请确保 @Database 中的 exportSchema 选项为 true。否则将导致错误: Cannot create auto-migrations when export schema is OFF。
**Demo:**
```java
@Database(entities = {User.class}, version = 2, autoMigrations = {@AutoMigration(from = 1, to = 2, spec =AppAutoMigration_1_2.class)})
public abstract class AppDatabase extends RoomDatabase {
private final static String DB_NAME = "test_room.db";
private volatile static TestDatabase instance;
public abstract UserDao UserDao();
public static TestDatabase buildHealthDb(Context context) {
if (instance == null) {
synchronized (TestDatabase.class) {
if (instance == null) {
instance = Room.databaseBuilder(context, TestDatabase.class, DB_NAME)
.build();
}
}
}
return instance;
}
}
//重命名数据表
@RenameTable(fromTableName = "Doggos", toTableName = "GoodDoggos")
class AppAutoMigration_1_2 implements AutoMigrationSpec {
}
```
**当自动迁移需要帮助时**
Room 的自动迁移无法检测到数据库上执行的所有可能的变化,因此有时候它们需要一些帮助。举一个常见的例子,Room 没办法检测到一个数据库表或列是否被重命名或者被删除。在这种情况下,Room 会抛出一个编译错误,并要求您实现 AutoMigrationSpec。此类允许您指定做出更改的类型,实现一个 AutoMigrationSpec 并使用以下一项或多项来注解:
- @DeleteTable(tableName)
- @RenameTable(fromTableName, toTableName)
- @DeleteColumn(tableName, columnName)
- @RenameColumn(tableName, fromColumnName, toColumnName)