Cách nâng cấp phiên bản database (upgrade database) android đúng nhất – không lỗi

4
Dịch vụ dạy kèm gia sư lập trình

Trong bất kể dự án phần mềm nào thì Database luôn là một phần đặc biệt quan trọng. Trong quá trình phát triển ứng dụng, đôi khi chúng ta cần thay đổi cấu trúc database, ví dụ như thêm column, thêm table.v.v… Đây là lúc bạn bắt buộc phải nâng cấp phiên bản database hay còn gọi là upgrade database android.

Trên internet có rất nhiều hướng dẫn nói về cách làm việc với SQLite và bản thân mình cũng như bạn cũng đã tham khảo một vài cách trong số đó rồi.

upgrade database android
Hướng dẫn upgrade databasse android đúng cách

Mặc dù, các tutorial này đều hướng dẫn các bạn cách tạo database, kết nối và lấy dữ liệu từ database… Nhưng phần lớn chúng vẫn thiếu một thao tác mà khi cần thì nó lại rất quan trọng vì nếu sai sót, bạn có thể làm mất toàn bộ dữ liệu của khách hàng. Đó chính là thao tác nâng cấp database.

Có thể liệt kê một số trường hợp phải nâng cấp database như : Thêm table, thêm/xóa column cho một table… Như các bạn thấy, nâng cấp database là việc bắt buộc phải thực hiện trong những trường hợp trên. Trừ khi bạn bỏ ứng dụng cũ và tạo ứng dụng mới hoàn toàn!

Mình đã từng có bài học “xương máu” về việc nâng cấp database khi mù quáng làm theo các tutorial trên mạng mà không test kĩ, chẳng hạn như sử dụng đoạn code sau:

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    	db.execSQL("DROP TABLE IF EXISTS " + TABLE_TEAM);
    	onCreate(db);
}

Kết quả là toàn bộ dữ liệu của khách hàng bị xóa sạch 🙁

Vậy đâu là cách tốt nhất để nâng cấp database của ứng dụng đây? Chúng ta hãy cùng nhau tìm hiểu nhé!

#Bài toán ví dụ minh họa về upgrade database Android

Để dễ hình dung, mình xây dựng một sample app có database với một table. Table này bao gồm các thông tin như sau:  Id (int), Name (string), City (string), Mascot (string).

1. Sử dụng OnUpgrade()

Khi mình bắt đầu làm ứng dụng Android, mình có tìm trên Internet thì thấy một số hướng dẫn về làm việc với Database, nó đại khái như sau:

Cảnh báo: Đây không phải là code chuẩn nên đừng copy vội nhé.
public class SQLiteHelper extends SQLiteOpenHelper {
 
    public SQLiteHelper(Context context) {
    	super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
 
    //database values
    private static final String DATABASE_NAME  	= "demoApp.db";
    private static final int DATABASE_VERSION  	= 1;
    public static final String COLUMN_ID       	= "_id";
 
    //team table
    public static final String TABLE_TEAM   	= "team";
    public static final String COLUMN_MASCOT	= "mascot";
    public static final String COLUMN_CITY  	= "city";
 
    public static final String DATABASE_CREATE_TEAM = "create table "
        	+ TABLE_TEAM + "(" + COLUMN_ID + " integer primary key autoincrement, "
        	+ COLUMN_NAME + " string, "
        	+ COLUMN_MASCOT + " string, "
        	+ COLUMN_CITY + " string);";
 
    @Override
    public void onCreate(SQLiteDatabase db) {
    	db.execSQL(DATABASE_CREATE_TEAM);
    }
 
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    	db.execSQL("DROP TABLE IF EXISTS " + TABLE_TEAM);
    	onCreate(db);
    }
}

Đoạn code này về cơ bản là ok khi dự án mới chỉ ở giai đoạn phát triển.

Bạn có thể thay đổi cấu trúc table dễ dàng (mà không cần quan tâm đến chuyện phải giữ lại dữ liệu cũ) bằng cách thay đổi câu lệnh tạo SQL. Ở ví dụ này là String DATABASE_CREATE_TEAM và tăng chỉ số database version lên.

Vấn đề là kỹ thuật này sẽ drop toàn bộ table và tạo lại từ đầu. Điều này khó có thể chấp nhận đối với các ứng dụng đã được đưa ra thị trường. Nếu bạn chấp nhận để khách hàng chửi thì mình nghĩ kỹ thuật này cũng OK thôi!!!

Mấu chốt của vấn đề là hàm này (bạn cũng biết là nó sẽ làm gì rồi đó!!!)

db.execSQL("DROP TABLE IF EXISTS " + TABLE_TEAM);
upgrade database android
Quá nguy hiểm nếu bạn drop toàn bộ dữ liệu của khách hàng

💦 Có thể bạn cần về cách sử dụng SQLite

2. Đâu là giải pháp upgrade database android tối ưu?

Một số developer đã nhận ra vấn đề và lập tức tra hỏi anh Google như “android onUpgrade add column”.

Nhưng thật đáng tiếc, anh Google chỉ dạy cho một số cách mà trong đó toàn là giải pháp tồi (Tại thời điểm mình viết bài này thì 5 trong số 6 kết quả top đầu là vậy). Không tin thì bạn vào một số link sau mà xem: đây, đây, đây, đây, hoặc đây

Tất cả các gợi ý đều đề nghị sửa đổi logic trong hàm OnUpgrade() để tận dụng giá trị oldVersion. Nhưng tất cả chúng đều có “vấn đề”.

Dưới đây mình liệt kê một số gợi ý “cải tiến”. Hãy xem bạn có phát hiện ra vấn đề trong đoạn code đó không nhé!

#. “Giải pháp xấu” số 1

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
   db.execSQL(ALTER_USER_TABLE_ADD_USER_SOCIETY);
   db.execSQL(ALTER_USER_TABLE_ADD_USER_STREET1);
}

Vấn đề ở đây là gì?

Những câu lệnh ALTER sẽ được thực thi mỗi khi bạn nâng cấp ứng dụng!  Nếu bạn nâng cấp database từ version 1 lên version 2, chúng sẽ được chạy và thêm column vào table.

Sau đó nếu bạn lại nâng cấp từ version 2 lên version 3 (và không thay đổi gì đoạn mã trên) thì bạn sẽ gặp phải lỗi khi SQLite cố gắng thêm column một lần nữa.

Bạn có thể sẽ dễ tặc lưỡi lướt qua khi chỉ thay đổi phần này mỗi khi bạn cần nâng cấp database nhưng điều này sẽ khiến bạn dễ bị lỗi hơn nữa và đưa bạn đến cùng một lỗi ở Example 3 (Chờ xíu nhé, ngay bên dưới này thôi).

#. “Giải pháp xấu” số 2

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
 
    // If you need to add a column
    if (newVersion > oldVersion) {
    	db.execSQL("ALTER TABLE foo ADD COLUMN new_column INTEGER DEFAULT 0");
    }
}

Vấn đề là gì?

Đây là một đoạn code điển hình mà các bạn hay gặp khi tìm kiếm trên mạng. Mà thoạt nhìn qua thì có vẻ đoạn code trên hoàn toàn đúng, không có vấn đề gì cả.

Tuy nhiên, đây là một ví dụ nguy hiểm bởi vì mỗi lần nâng cấp database của ứng dụng, câu lệnh if luôn luôn đúng và lúc này chúng ta sẽ gặp lỗi giống như ở Example 1 (tức là SQLite không thể nâng cấp database vì column đã tồn tại!).

💦 Đừng bỏ lỡ: 5 bí quyết để trở thành lập trình viên “bá đạo”

#. “Giải pháp xấu” số 3

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    String upgradeQuery = "ALTER TABLE mytable ADD COLUMN mycolumn TEXT";
    if (oldVersion == 1 && newVersion == 2)
     	db.execSQL(upgradeQuery);
}

Vấn đề là gì?

Với đoạn code này thì có vẻ như developer đã cẩn thận hơn, và cũng khó nhìn thấy lỗi hơn 2 ví dụ trên.

Tuy nhiên, nếu xem xét kĩ đoạn code trên thì bạn sẽ thấy rằng: Điều gì sẽ xảy ra nếu người dùng ở version 1 lên thẳng version 3?

Ứng dụng của họ sẽ bỏ qua câu lệnh nâng cấp database hoàn toàn ở version 2 và làm cho những người dùng này ở trong tình trạng lúng túng khi thiếu một số cập nhật ở version trung gian (version 2).

Điều này có thể sẽ trở thành vấn đề nghiêm trọng cho ứng dụng của bạn. Đương nhiên, người dùng sẽ thật sự thất vọng với ứng dụng mà bạn đã dày công tạo ra!

💦 Một giải pháp thay thế SQLite: Realm database trong Android – Giải pháp hoàn hảo thay thế SQLite

3. Cách upgrade database android đúng nhất

Qua 3 ví dụ trên, chúng ta thấy rằng không thể drop database khi nâng cấp được. Chúng ta cũng không thể biết trước được người dùng có nâng cấp ứng dụng một cách tuần tự như mình mong muốn được.

Do đó, để đảm bảo việc nâng cấp được diễn ra chính xác, cần phải kiểm tra phiên bản của ứng dụng trước mỗi lần nâng cấp và thực hiện thao tác nâng cấp tương ứng.

Quay lại với bài toán mà mình đã đưa ở phía trên bài viết. Ứng dụng của mình thêm column tên huấn luyện viên (coach’s name) ở version 2.

Sau đó ở version 3, chúng ta lại tiếp tục thêm column tên sân vận động (team’s stadium name).

Thực hiện giải pháp ở trên, chúng ta có một đoạn code tốt hơn rất nhiều như sau:

public SQLiteHelper(Context context) {
    	super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
 
    //database values
    private static final String DATABASE_NAME  	= "demoApp.db";
    private static final int DATABASE_VERSION  	= 3;
    public static final String COLUMN_ID       	= "_id";
 
    //team table
    public static final String TABLE_TEAM   	= "team";
    public static final String COLUMN_MASCOT	= "mascot";
    public static final String COLUMN_CITY  	= "city";
    public static final String COLUMN_COACH 	= "coach";
    public static final String COLUMN_STADIUM   = "stadium";
 
    private static final String DATABASE_CREATE_TEAM = "create table "
        	+ TABLE_TEAM + "(" + COLUMN_ID + " integer primary key autoincrement, "
        	+ COLUMN_NAME + " string, "
        	+ COLUMN_MASCOT + " string, "
        	+ COLUMN_COACH + " string, "
        	+ COLUMN_STADIUM + " string, "
        	+ COLUMN_CITY + " string);";
 
    private static final String DATABASE_ALTER_TEAM_1 = "ALTER TABLE "
    	+ TABLE_TEAM + " ADD COLUMN " + COLUMN_COACH + " string;";
 
    private static final String DATABASE_ALTER_TEAM_2 = "ALTER TABLE "
    	+ TABLE_TEAM + " ADD COLUMN " + COLUMN_STADIUM + " string;";
 
    @Override
    public void onCreate(SQLiteDatabase db) {
    	db.execSQL(DATABASE_CREATE_TEAM);
    }
 
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    	if (oldVersion < 2) {
         	db.execSQL(DATABASE_ALTER_TEAM_1);
    	}
    	if (oldVersion < 3) {
         	db.execSQL(DATABASE_ALTER_TEAM_2);
    	}
    }
}

Với đoạn code này, ứng dụng sẽ không gặp bất kì vấn đề gì bất kể người dùng nâng cấp lên version nào, đây chính là cách upgrade database android đúng nhất .

Một điều cần lưu ý là bạn cũng đừng quên thay đổi cả câu lệnh CREATE SQL (câu lệnh SQL mà bạn gọi trong hàm onCreate) cho người dùng mới cài ứng dụng nhé! Câu lệnh ALTER (gọi trong hàm onUpgrade) thì chỉ dùng cho người dùng hiện tại đã cài ứng dụng thôi đó!

Như vậy, mình đã gợi ý một cách tốt nhất để nâng cấp database cho ứng dụng Android. Nếu bạn cảm thấy đoạn code trên vẫn còn vấn đề tiềm tàng hoặc bạn có một giải pháp khác tốt hơn thì comment bên dưới để mọi người cùng nhau tìm hiểu và thảo luận nhé!

Đừng quên like và share nha!

Nguồn tham khảo: thebhwgroup

Dịch vụ phát triển ứng dụng mobile giá rẻ - chất lượng
Bài trướcJavascript – Khai báo Object Constant bằng cách sử dụng Object.freeze()
Bài tiếp theoHướng dẫn tích hợp tính năng “Đăng nhập không cần mật khẩu” với Account Kit
Tên đầy đủ là Dương Anh Sơn. Tốt nghiệp ĐH Bách Khoa Hà Nội. Mình bắt đầu nghiệp coder khi mà ra trường chẳng xin được việc đúng chuyên ngành. Mình tin rằng chỉ có chia sẻ kiến thức mới là cách học tập nhanh nhất. Các bạn góp ý bài viết của mình bằng cách comment bên dưới nhé !

4
Bình luận. Cùng nhau thảo luận nhé!

avatar
  Theo dõi bình luận  
Mới nhất Cũ nhất Nhiều voted nhất
Thông báo
Luong Do
Guest
Luong Do

rất hữu ích nha 😀

Thoa
Guest
Thoa

hay quá chủ thớt

naq
Guest
naq

hay!!