Hướng dẫn upgrade database android một cách chuẩn nhất

0
42

upgrade database android

Trong bất kể dự án nào thì database luôn là một phần rất quan trọng và các ứng dụng  Android cũng không phải ngoại lệ. Trên internet có rất nhiều các tutorial 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 trong số đó rồi. Mặc dù, các tutorial này 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 tối 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à 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 khác 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

Để 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).

Hướng dẫn 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 lên market (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

Đâ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 đề 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. 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!).

“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 và đương nhiên, người dùng sẽ thật sự thất vọng với chính ứng dụng mà bạn đã dày công tạo ra!

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 colum 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 rất 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 nữa nha!

Bình luận. Đặt câu hỏi cũng là một cách học

avatar
  Theo dõi bình luận  
Thông báo