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.
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é!
Nội dung chính của bài viết
#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:
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);
💦 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
rất hữu ích nha 😀
hay quá chủ thớt
Cám ơn bạn nha 🤓
hay!!