Android: nâng cấp phiên bản DB và thêm bảng mới


117

Tôi đã tạo bảng sqlite cho ứng dụng của mình, nhưng bây giờ tôi muốn thêm một bảng mới vào cơ sở dữ liệu.

Tôi đã thay đổi phiên bản DB như bên dưới

private static final int DATABASE_VERSION = 2;

và Thêm chuỗi để tạo bảng

private static final String DATABASE_CREATE_color = 
   "CREATE TABLE IF NOT EXISTS files(color text, incident_id text)";

onCreateonUpgradenhư bên dưới:

@Override
    public void onCreate(SQLiteDatabase database) {
        database.execSQL(DATABASE_CREATE_incident);
        database.execSQL(DATABASE_CREATE_audio);
        database.execSQL(DATABASE_CREATE_video);
        database.execSQL(DATABASE_CREATE_image);

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //drop table and add new tables when version 2 released.
        db.execSQL(DATABASE_CREATE_color);

    }

Nhưng vì lý do nào đó mà bảng mới không được tạo. Tôi đang làm gì sai?


Đây là một giải pháp thú vị khác, nhưng cho đến nay phiên bản mạnh mẽ nhất mà tôi đã thấy là ở đây .
Suragch

Câu trả lời:


280

1. Giới thiệu về onCreate () và onUpgrade ()

onCreate(..)được gọi bất cứ khi nào ứng dụng được cài đặt mới. onUpgradeđược gọi bất cứ khi nào ứng dụng được nâng cấp và khởi chạy và phiên bản cơ sở dữ liệu không giống nhau.

2. Tăng phiên bản db

Bạn cần một hàm tạo như:

MyOpenHelper(Context context) {
   super(context, "dbname", null, 2); // 2 is the database version
}

QUAN TRỌNG: Chỉ tăng phiên bản ứng dụng là không đủ onUpgradeđể được gọi!

3. Đừng quên những người dùng mới của bạn!

Đừng quên thêm

database.execSQL(DATABASE_CREATE_color);

đối với phương thức onCreate () của bạn hoặc các ứng dụng mới được cài đặt sẽ thiếu bảng.

4. Cách đối phó với nhiều thay đổi cơ sở dữ liệu theo thời gian

Khi bạn có các bản nâng cấp ứng dụng liên tiếp, một số trong số đó có nâng cấp cơ sở dữ liệu, bạn muốn chắc chắn kiểm tra oldVersion:

onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   switch(oldVersion) {
   case 1:
       db.execSQL(DATABASE_CREATE_color);
       // we want both updates, so no break statement here...
   case 2:
       db.execSQL(DATABASE_CREATE_someothertable); 
   }
}

Theo cách này, khi người dùng nâng cấp từ phiên bản 1 lên phiên bản 3, họ sẽ nhận được cả hai bản cập nhật. Khi người dùng nâng cấp từ phiên bản 2 lên 3, họ chỉ nhận được bản cập nhật phiên bản 3 ... Sau cùng, bạn không thể tin tưởng vào 100% cơ sở người dùng của mình để nâng cấp mỗi khi bạn phát hành bản cập nhật. Đôi khi họ bỏ qua một bản cập nhật hoặc 12 :)

5. Kiểm soát số lượng sửa đổi của bạn trong khi phát triển

Và cuối cùng ... gọi

adb uninstall <yourpackagename>

hoàn toàn gỡ cài đặt ứng dụng. Khi bạn cài đặt lại, bạn được đảm bảo sẽ gặp phải onCreateđiều này khiến bạn không phải tiếp tục tăng phiên bản cơ sở dữ liệu vào tầng bình lưu khi bạn phát triển ...


5
Về # 4: Sẽ không phải là một ý tưởng tốt hơn nếu sử dụng oldVersionđối số được truyền? Nếu bất kỳ câu lệnh nâng cấp nào có thể lặp lại, bạn có thể lặp lại chúng trên cơ sở dữ liệu gần như cập nhật. Nếu một trong các câu lệnh là cắt bớt một bảng, điều đó sẽ rất tệ.
Greyson

3
@Greyson: Điểm tuyệt vời! Thành thật mà nói, tôi cảm thấy hơi ngớ người vì chưa bao giờ thực sự nghĩ về điều đó. Đôi khi tôi nghĩ rằng chúng ta có thói quen sử dụng các lý lẽ mà chúng ta muốn và bỏ qua phần còn lại!
jkschneider

1
Bạn kiểm soát cơ sở dữ liệu, tại sao bạn lại đổi tên?
jkschneider

3
newVersionhơi vô dụng, vì bạn vẫn luôn đặt phiên bản cơ sở dữ liệu hiện tại trong hàm tạo (xem phần 2) và nó sẽ luôn khớp. Ý tưởng chính ở đây là bạn không muốn chỉ nâng cấp từ bất kỳ nơi nào mà người dùng đến thẳng newVersionmà không cần thực hiện từng lần nâng cấp gia tăng khác ở giữa.
jkschneider

2
@kai CREATE_READINGSLogic không bao giờ có trong onUpgrade, vì nó nằm trong onCreatephương thức của phiên bản đầu tiên của bạn. Hãy nghĩ về các trường hợp trong công onUpgradetắc là "Tôi đang nâng cấp FROM oldVersion". Bạn sẽ không tạo bảng đọc nếu bạn đang nâng cấp từ phiên bản 1, vì nó đã tồn tại. Hy vọng rằng điều này làm cho cảm giác ...
jkschneider

9

Mã của bạn có vẻ chính xác. Đề xuất của tôi là cơ sở dữ liệu đã nghĩ rằng nó đã được nâng cấp. Nếu bạn đã thực thi dự án sau khi tăng số phiên bản, nhưng trước khi thêm lệnh execSQLgọi, cơ sở dữ liệu trên thiết bị thử nghiệm / trình mô phỏng của bạn có thể đã tin rằng nó ở phiên bản 2.

Một cách nhanh chóng để xác minh điều này là thay đổi số phiên bản thành 3 - nếu nó nâng cấp sau đó, bạn biết đó chỉ là vì thiết bị của bạn tin rằng nó đã được nâng cấp.


Sau đó, như mong đợi, mã của bạn đã ổn; chỉ không khi nó được chạy tăng dần. Nhớ thêm phần tạo bảng onCreate()như jkschneider đã chỉ ra.
Greyson

2

Bạn có thể sử dụng onUpgradephương pháp của SQLiteOpenHelper . Trong phương thức onUpgrade, bạn lấy oldVersion làm một trong các tham số.

Trong việc onUpgradesử dụng a switchvà trong mỗi cases sử dụng số phiên bản để theo dõi phiên bản hiện tại của cơ sở dữ liệu.

Tốt nhất là bạn lặp lại từ oldVersionđến newVersion, tăng dần từng bước versionmột rồi nâng cấp cơ sở dữ liệu từng bước. Điều này rất hữu ích khi ai đó có phiên bản cơ sở dữ liệu 1 nâng cấp ứng dụng sau một thời gian dài, lên phiên bản sử dụng cơ sở dữ liệu phiên bản 7 và ứng dụng bắt đầu gặp sự cố do một số thay đổi không tương thích nhất định.

Sau đó, các cập nhật trong cơ sở dữ liệu sẽ được thực hiện từng bước, bao gồm tất cả các trường hợp có thể xảy ra, tức là kết hợp các thay đổi trong cơ sở dữ liệu được thực hiện cho mỗi phiên bản mới và do đó ngăn ứng dụng của bạn bị treo.

Ví dụ:

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    switch (oldVersion) {
    case 1:
        String sql = "ALTER TABLE " + TABLE_SECRET + " ADD COLUMN " + "name_of_column_to_be_added" + " INTEGER";
        db.execSQL(sql);
        break;

    case 2:
        String sql = "SOME_QUERY";
        db.execSQL(sql);
        break;
    }

}

Nếu bạn loại bỏ những tuyên bố phá vỡ bạn sẽ không cần một vòng lặp
Tash Pemhiwa

nhưng oldversion đã để tăng lên trong từng trường hợp để vượt qua @TashPemhiwa trường hợp tiếp theo
Beulah Ana

Lý do một tuyên bố chuyển đổi đòi hỏi nghỉ ngơi là nó có thể chạy nhiều trường hợp cùng một lúc - và đây sẽ là trường hợp ngay cả khi điều kiện trường hợp không được đáp ứng, @BeulahAna
Tash Pemhiwa

Nếu bạn thêm dấu ngắt và một số db có phiên bản cũ hoặc gần đây thì truy vấn của bạn có thể không thành công, vì vậy không cần ngắt, ví dụ thay đổi bảng nếu một số cột đã thay đổi trong một số phiên bản db thì truy vấn của bạn có thể bị lỗi theo chuỗi mất mát của phiên bản db
Neeraj Singh

2

Câu trả lời của @ jkschneider là đúng. Tuy nhiên có một cách tiếp cận tốt hơn.

Viết các thay đổi cần thiết vào tệp sql cho mỗi bản cập nhật như được mô tả trong liên kết https://riggaroo.co.za/android-sqlite-database-use-onupgrade-correctly/

from_1_to_2.sql

ALTER TABLE books ADD COLUMN book_rating INTEGER;

from_2_to_3.sql

ALTER TABLE books RENAME TO book_information;

from_3_to_4.sql

ALTER TABLE book_information ADD COLUMN calculated_pages_times_rating INTEGER;
UPDATE book_information SET calculated_pages_times_rating = (book_pages * book_rating) ;

Các tệp .sql này sẽ được thực thi trong phương thức onUpgrade () theo phiên bản của cơ sở dữ liệu.

DatabaseHelper.java

public class DatabaseHelper extends SQLiteOpenHelper {

    private static final int DATABASE_VERSION = 4;

    private static final String DATABASE_NAME = "database.db";
    private static final String TAG = DatabaseHelper.class.getName();

    private static DatabaseHelper mInstance = null;
    private final Context context;

    private DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context;
    }

    public static synchronized DatabaseHelper getInstance(Context ctx) {
        if (mInstance == null) {
            mInstance = new DatabaseHelper(ctx.getApplicationContext());
        }
        return mInstance;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(BookEntry.SQL_CREATE_BOOK_ENTRY_TABLE);
        // The rest of your create scripts go here.

    }


    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.e(TAG, "Updating table from " + oldVersion + " to " + newVersion);
        // You will not need to modify this unless you need to do some android specific things.
        // When upgrading the database, all you need to do is add a file to the assets folder and name it:
        // from_1_to_2.sql with the version that you are upgrading to as the last version.
        try {
            for (int i = oldVersion; i < newVersion; ++i) {
                String migrationName = String.format("from_%d_to_%d.sql", i, (i + 1));
                Log.d(TAG, "Looking for migration file: " + migrationName);
                readAndExecuteSQLScript(db, context, migrationName);
            }
        } catch (Exception exception) {
            Log.e(TAG, "Exception running upgrade script:", exception);
        }

    }

    @Override
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    private void readAndExecuteSQLScript(SQLiteDatabase db, Context ctx, String fileName) {
        if (TextUtils.isEmpty(fileName)) {
            Log.d(TAG, "SQL script file name is empty");
            return;
        }

        Log.d(TAG, "Script found. Executing...");
        AssetManager assetManager = ctx.getAssets();
        BufferedReader reader = null;

        try {
            InputStream is = assetManager.open(fileName);
            InputStreamReader isr = new InputStreamReader(is);
            reader = new BufferedReader(isr);
            executeSQLScript(db, reader);
        } catch (IOException e) {
            Log.e(TAG, "IOException:", e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    Log.e(TAG, "IOException:", e);
                }
            }
        }

    }

    private void executeSQLScript(SQLiteDatabase db, BufferedReader reader) throws IOException {
        String line;
        StringBuilder statement = new StringBuilder();
        while ((line = reader.readLine()) != null) {
            statement.append(line);
            statement.append("\n");
            if (line.endsWith(";")) {
                db.execSQL(statement.toString());
                statement = new StringBuilder();
            }
        }
    }
}

Một dự án mẫu cũng được cung cấp trong cùng một liên kết: https://github.com/riggaroo/AndroidDatabaseUpgrades


1
Tôi chỉ định đến đây và viết những lời khuyên tương tự. Tôi rất vui vì bạn đã làm được. Mọi người chắc chắn nên đọc bài báo mà bạn đã liên kết. Đây cũng là những gì Android SQLiteAssetHelper đề xuất để nâng cấp. Nó cũng là những gì CL. ( Các chuyên gia SQLite đây trên Stack Overflow) khuyến cáo .
Suragch

Nhận xét này những gì tôi đang tìm kiếm. Các tập lệnh sql, +1
blueware

1

Xử lý các phiên bản cơ sở dữ liệu là một phần rất quan trọng trong quá trình phát triển ứng dụng. Tôi giả sử rằng bạn đã có lớp AppDbHelper mở rộng SQLiteOpenHelper. Khi bạn mở rộng nó, bạn sẽ cần phải thực hiện onCreateonUpgradephương pháp.

  1. Khi nào onCreateonUpgradecác phương thức được gọi

    • onCreate được gọi khi ứng dụng mới được cài đặt.
    • onUpgrade được gọi khi ứng dụng được cập nhật.
  2. Tổ chức các phiên bản Cơ sở dữ liệu Tôi quản lý các phiên bản trong một phương thức lớp. Tạo thực hiện Di chuyển giao diện. Ví dụ: Đối với phiên bản đầu tiên tạo MigrationV1lớp, phiên bản thứ hai tạo MigrationV1ToV2(đây là quy ước đặt tên của tôi)


    public interface Migration {
        void run(SQLiteDatabase db);//create tables, alter tables
    }

Ví dụ về di chuyển:

public class MigrationV1ToV2 implements Migration{
      public void run(SQLiteDatabase db){
        //create new tables
        //alter existing tables(add column, add/remove constraint)
        //etc.
     }
   }
  1. Sử dụng các lớp Di chuyển

onCreate: Vì onCreatesẽ được gọi khi ứng dụng mới được cài đặt, chúng tôi cũng cần thực hiện tất cả các quá trình di chuyển (cập nhật phiên bản cơ sở dữ liệu). Vì vậy, onCreatesẽ trông như thế này:

public void onCreate(SQLiteDatabase db){
        Migration mV1=new MigrationV1();
       //put your first database schema in this class
        mV1.run(db);
        Migration mV1ToV2=new MigrationV1ToV2();
        mV1ToV2.run(db);
        //other migration if any
  }

onUpgrade: Phương thức này sẽ được gọi khi ứng dụng đã được cài đặt và nó được cập nhật lên phiên bản ứng dụng mới. Nếu ứng dụng có bất kỳ thay đổi cơ sở dữ liệu nào thì hãy đặt tất cả các thay đổi cơ sở dữ liệu trong lớp Di chuyển mới và phiên bản cơ sở dữ liệu gia tăng.

Ví dụ: giả sử người dùng đã cài đặt ứng dụng có phiên bản cơ sở dữ liệu 1 và bây giờ phiên bản cơ sở dữ liệu được cập nhật lên 2 (tất cả các bản cập nhật lược đồ được lưu trong MigrationV1ToV2). Bây giờ khi ứng dụng được nâng cấp, chúng ta cần phải nâng cấp cơ sở dữ liệu bằng cách áp dụng các thay đổi lược đồ cơ sở dữ liệu MigrationV1ToV2như sau:

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (oldVersion < 2) {
        //means old version is 1
        Migration migration = new MigrationV1ToV2();
        migration.run(db);
    }
    if (oldVersion < 3) {
        //means old version is 2
    }
}

Lưu ý: Tất cả các nâng cấp (được đề cập trong onUpgrade) trong lược đồ cơ sở dữ liệu phải được thực hiện trongonCreate

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.