Di chuyển cơ sở dữ liệu phòng nếu chỉ thêm bảng mới


99

Đừng giả sử, tôi có một cơ sở dữ liệu Phòng đơn giản:

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

Bây giờ, tôi đang thêm một thực thể mới: Pet và chuyển phiên bản lên 2:

@Database(entities = {User.class, Pet.class}, version = 2)
abstract class AppDatabase extends RoomDatabase {
    public abstract Dao getDao();
}

Tất nhiên, Room ném ra một ngoại lệ: java.lang.IllegalStateException: A migration from 1 to 2 is necessary.

Giả sử, tôi chưa thay đổi Userlớp (vì vậy tất cả dữ liệu đều an toàn), tôi phải cung cấp chuyển đổi chỉ tạo một bảng mới. Vì vậy, tôi đang xem xét các lớp được tạo bởi Room, tìm kiếm truy vấn đã tạo để tạo bảng mới của tôi, sao chép nó và dán vào quá trình di chuyển:

final Migration MIGRATION_1_2 =
        new Migration(1, 2) {
            @Override
            public void migrate(@NonNull final SupportSQLiteDatabase database) {
                database.execSQL("CREATE TABLE IF NOT EXISTS `Pet` (`name` TEXT NOT NULL, PRIMARY KEY(`name`))");
            }
        };

Tuy nhiên tôi thấy bất tiện khi làm thủ công. Có cách nào để nói với Room: Tôi không chạm vào bất kỳ bảng nào hiện có, vì vậy dữ liệu được an toàn. Vui lòng tạo di chuyển cho tôi?


bạn đã tìm thấy giải pháp chưa?
Mikkel Larsen

3
Tôi đã gặp vấn đề tương tự và đã khắc phục nó giống như cách bạn đã làm, và cũng không tìm thấy giải pháp. Rất vui khi tôi không đơn độc. :)
Mikkel Larsen

3
Ở đây cũng vậy. Tôi thấy nó rất bất tiện căn phòng đó có khả năng tạo ra các truy vấn tạo bên trong của database_impl, nhưng không thể chỉ tạo bảng, một khi bắt đầu di cư ....
JacksOnF1re

1
Tôi sẽ cống hiến rất nhiều cho một tính năng như vậy ... Sẽ rất tuyệt khi kết hợp giữa di chuyển và cơ chế dự phòng ...
Appyx 26/02

3
Tôi không chắc liệu điều này có hữu ích hay không nhưng Room có ​​tùy chọn xuất lược đồ cơ sở dữ liệu thành tệp JSON. developer.android.com/training/data-storage/room/… Rõ ràng điều này vẫn có nghĩa là thêm tập lệnh di chuyển theo cách thủ công nhưng bạn sẽ không cần phải định tuyến qua các lớp được tạo tự động để lấy câu lệnh SQL của mình.
James Lendrem

Câu trả lời:


76

Phòng không không có một tốt Hệ thống di cư, ít nhất là không cho đến khi2.1.0-alpha03 .

Vì vậy, cho đến khi chúng tôi có Hệ thống di chuyển tốt hơn, có một số cách giải quyết để có thể di chuyển dễ dàng trong phòng.

Vì không có phương pháp nào như @Database(createNewTables = true) hoặc MigrationSystem.createTable(User::class), phải có phương pháp này hoặc phương pháp khác, cách duy nhất có thể là chạy

CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))

bên trong migratephương pháp của bạn .

val MIGRATION_1_2 = object : Migration(1, 2){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL("CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))")
    }
}

Để đạt được trên SQL script , bạn có 4 cách

1. Viết bởi chính bạn

Về cơ bản, bạn phải viết script ở trên sẽ khớp với script mà Room tạo ra. Cách này khả thi, không khả thi. (Coi như bạn có 50 trường)

2. Lược đồ xuất khẩu

Nếu bạn bao gồm exportSchema = truebên trong @Databasechú thích của mình , Room sẽ tạo lược đồ cơ sở dữ liệu trong / các lược đồ của thư mục dự án của bạn. Cách sử dụng là

@Database(entities = [User::class], version = 2, exportSchema = true)
abstract class AppDatabase : RoomDatabase {
   //...
}

Đảm bảo rằng bạn đã bao gồm các dòng dưới đây trong build.grademô-đun ứng dụng của mình

kapt {
    arguments {
        arg("room.schemaLocation", "$projectDir/schemas".toString())
    }
} 

Khi bạn chạy hoặc xây dựng dự án, bạn sẽ nhận được tệp JSON 2.json, tệp này có tất cả các truy vấn trong cơ sở dữ liệu Phòng của bạn.

  "formatVersion": 1,
  "database": {
    "version": 2,
    "identityHash": "325bd539353db508c5248423a1c88c03",
    "entities": [
      {
        "tableName": "User",
        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, PRIMARY KEY(`id`))",
        "fields": [
          {
            "fieldPath": "id",
            "columnName": "id",
            "affinity": "INTEGER",
            "notNull": true
          },

Vì vậy, bạn có thể bao gồm những điều trên createSql trong migratephương pháp của bạn .

3. Nhận truy vấn từ AppDatabase_Impl

Nếu bạn không muốn xuất lược đồ, bạn vẫn có thể nhận được truy vấn bằng cách chạy hoặc xây dựng dự án sẽ tạo AppDatabase_Impl.javatệp. và trong tệp được chỉ định mà bạn có thể có.

@Override
public void createAllTables(SupportSQLiteDatabase _db) {
  _db.execSQL("CREATE TABLE IF NOT EXISTS `User` (`id` INTEGER, PRIMARY KEY(`id`))");

Trong createAllTablesphương thức, sẽ có các kịch bản tạo của tất cả các thực thể. Bạn có thể lấy nó và đưa vào bên trong bạnmigrate phương pháp của .

4. Xử lý chú thích.

Như bạn có thể đoán, Room tạo tất cả các tệp được đề cập ở trên schemaAppDatabase_Implcác tệp trong thời gian biên dịch và với Xử lý chú thích mà bạn thêm vào

kapt "androidx.room:room-compiler:$room_version"

Điều đó có nghĩa là bạn cũng có thể làm như vậy và tạo thư viện xử lý chú thích của riêng bạn để tạo tất cả các truy vấn tạo cần thiết cho bạn.

Ý tưởng là tạo một thư viện xử lý chú thích cho các chú thích của Room @Entity@Database. Lấy @Entityví dụ về một lớp được chú thích . Đây là các bước bạn sẽ phải làm theo

  1. Tạo mới StringBuildervà thêm "TẠO BẢNG NẾU KHÔNG TỒN TẠI"
  2. Lấy tên bảng từ class.simplenamehoặc theo tableNametrường của @Entity. Thêm nó vào của bạnStringBuilder
  3. Sau đó, đối với mỗi trường của lớp bạn tạo các cột SQL. Lấy tên, kiểu, khả năng vô hiệu của trường hoặc bằng chính trường hoặc bằng @ColumnInfochú thích. Đối với mọi trường, bạn phải thêm id INTEGER NOT NULLkiểu cột vàoStringBuilder .
  4. Thêm khóa chính bằng @PrimaryKey
  5. Thêm ForeignKeyIndicesnếu tồn tại.
  6. Sau khi hoàn tất chuyển đổi nó thành chuỗi và lưu nó trong một số lớp mới mà bạn muốn sử dụng. Ví dụ, lưu nó như dưới đây
public final class UserSqlUtils {
  public String createTable = "CREATE TABLE IF NOT EXISTS User (id INTEGER, PRIMARY KEY(id))";
}

Sau đó, bạn có thể sử dụng nó như

val MIGRATION_1_2 = object : Migration(1, 2){
    override fun migrate(database: SupportSQLiteDatabase) {
        database.execSQL(UserSqlUtils().createTable)
    }
}

Tôi đã tạo một thư viện như vậy cho chính mình mà bạn có thể kiểm tra, và thậm chí sử dụng nó trong dự án của bạn. Lưu ý rằng thư viện mà tôi đã tạo chưa đầy và nó chỉ đáp ứng các yêu cầu của tôi để tạo bảng.

RoomExtension để di chuyển tốt hơn

Ứng dụng sử dụng RoomExtension

Hy vọng nó hữu ích.

CẬP NHẬT

Vào thời điểm viết câu trả lời này, phiên bản phòng là 2.1.0-alpha03và khi tôi gửi email cho các nhà phát triển, tôi đã nhận được phản hồi

Dự kiến ​​sẽ có Hệ thống di chuyển tốt hơn trong 2.2.0

Thật không may, chúng tôi vẫn thiếu Hệ thống di chuyển tốt hơn.


3
Bạn có thể chỉ nơi bạn đọc rằng Phòng 2.2.x sẽ có sự di chuyển tốt hơn không? Tôi không thể tìm thấy bất kỳ điều gì đưa ra tuyên bố đó và vì chúng tôi hiện đang làm việc trên bản beta 2.1.0, những gì trong bản 2.2.0 dường như vẫn chưa được biết vào lúc này.
jkane001

1
@ jkane001 Tôi đã gửi email cho một trong những nhà phát triển phòng và nhận được phản hồi như vậy. Không có thông báo công khai nào như vậy về 2.2.x (chưa?)
musooff

4
Hiện nay trên phiên bản 2.2.2 và vẫn không tốt hơn di cư :( Tuy nhiên, đây là một câu trả lời tuyệt vời và đã cứu tôi một núi công việc nên 1 cho điều đó.
smitty1

@androiddeveloper Tất cả ngoại trừ số 4, đó là Xử lý chú thích
musooff

1
@musooff Tôi thực sự nghĩ rằng việc thêm tạo bảng cũng được. Đó là cách an toàn nhất để sao chép mã từ hàm "createAllTables".
nhà phát triển android

3

Rất tiếc, Room không hỗ trợ tính năng tự động tạo bảng mà không làm mất dữ liệu.

Nó là bắt buộc để viết di chuyển. Nếu không, nó sẽ xóa tất cả dữ liệu và tạo cấu trúc bảng mới.


0

Bạn có thể làm theo cách này-

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

abstract class AppDatabase extends RoomDatabase {
public abstract Dao getDao();
public abstract Dao getPetDao();
}

Còn lại sẽ giống như bạn đã đề cập ở trên-

 db = Room.databaseBuilder(this, AppDatabase::class.java, "your_db")
        .addMigrations(MIGRATION_1_2).build()

Tham khảo - Để biết thêm


0

Bạn có thể thêm lệnh gradle sau vào defaultConfig trong app.gradle của bạn:

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

Khi bạn chạy nó, nó sẽ biên dịch một danh sách các tên bảng với các câu lệnh CREATE TABLE có liên quan mà từ đó bạn có thể sao chép và dán vào các đối tượng di chuyển của mình. Bạn có thể phải thay đổi tên bảng.

Ví dụ: đây là từ giản đồ đã tạo của tôi:

"tableName": "assets",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`asset_id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `base` TEXT NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`asset_id`))"

Và vì vậy, tôi sao chép, dán câu lệnh createSql và thay đổi '$ {TABLE_NAME}' thành 'tài sản' tên bảng và thì đấy là câu lệnh Room tạo tự động được tạo.


-1

Trong trường hợp này, bạn không cần phải di chuyển, bạn có thể gọi .fallbackToDestructiveMigration () khi bạn đang tạo phiên bản cơ sở dữ liệu.

Thí dụ:

    instance = Room.databaseBuilder(context, AppDatabase.class, "database name").fallbackToDestructiveMigration().build();

Và đừng quên thay đổi phiên bản cơ sở dữ liệu.


Giải pháp này sẽ xóa tất cả dữ liệu của tôi khỏi các bảng hiện có.
Piotr Aleksander Chmielowski, 26/09/18

Không may là đúng vậy. "Bạn có thể gọi phương thức này để thay đổi hành vi này nhằm tạo lại cơ sở dữ liệu thay vì bị lỗi. Lưu ý rằng thao tác này sẽ xóa tất cả dữ liệu trong các bảng cơ sở dữ liệu do Room quản lý."
rudicjovan

-2

Có thể trong trường hợp này (nếu bạn chỉ tạo bảng mới mà không thay đổi bảng khác), bạn có thể thực hiện việc này mà không tạo ra bất kỳ di chuyển nào?


1
Không, trong trường hợp này phòng ném trong nhật ký: java.lang.IllegalStateException: Một di cư từ {OLD_VERSION} để {} NEW_VERSION là cần thiết
Max Makeichik
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.