Lưu dữ liệu bằng SQLite | Nhà phát triển Android | Android Developers

DataStore cung cấp phương pháp tân tiến hơn để tàng trữ dữ liệu cục bộ. Bạn nên sử dụng DataStore thay vì SharedPreferences. Hãy đọc hướng dẫn về DataStore để biết thêm thông tin

Lưu dữ liệu vào cơ sở dữ liệu là lựa chọn lý tưởng dành cho dữ liệu lặp hoặc dữ liệu có cấu trúc,
chẳng hạn như thông tin liên hệ. Trang này giả định rằng bạn đã quen dùng
cơ sở dữ liệu SQL nói chung và hướng dẫn bạn cách bắt đầu
sử dụng cơ sở dữ liệu SQLite trên Android. Những API mà bạn cần có để sử dụng cơ sở dữ liệu
trên Android được cung cấp trong gói android.database.sqlite.

Thận trọng: Mặc dù những API này rất mạnh mẽ, nhưng lại ở cấp khá thấp
và đòi hỏi nhiều thời gian cũng như công sức khi sử dụng:

  • Không có phương thức xác minh thời gian biên dịch đối với các truy vấn SQL thô. Khi biểu đồ
    dữ liệu thay đổi, bạn cần phải cập nhật các truy vấn SQL bị ảnh hưởng theo cách thủ công. Quá trình
    này có thể tốn nhiều thời gian và dễ xảy ra lỗi.
  • Bạn cần phải dùng nhiều mã nguyên mẫu để chuyển đổi giữa các đối tượng dữ liệu
    và truy vấn SQL.

Vì những lý do này, bạn nên dùng
Thư viện lưu trữ Phòng
làm lớp trừu tượng để truy cập thông tin trong cơ sở dữ liệu SQLite
của ứng dụng.

Xác định giản đồ và hợp đồng

Một trong những nguyên tắc chính của cơ sở dữ liệu SQL là giản đồ : nội dung khai báo chính thức về cách sắp xếp cơ sở dữ liệu. Giản đồ này được phản ánh trong câu lệnh SQL mà bạn dùng để tạo cơ sở dữ liệu. Bạn nên tạo một lớp sát cánh, còn gọi là lớp hợp đồng. Lớp này chỉ định rõ bố cục tổng quan của giản đồ theo cách có mạng lưới hệ thống và tự ghi lại .
Lớp hợp đồng là một vùng chứa cho những hằng số xác lập tên của URI, bảng và cột. Lớp hợp đồng được cho phép bạn sử dụng cùng một hằng số trên toàn bộ những lớp khác trong cùng gói. Nhờ vậy, bạn hoàn toàn có thể biến hóa tên cột ở một nơi và tự động hóa vận dụng biến hóa đó trên hàng loạt mã của mình .
Một cách hiệu suất cao để sắp xếp lớp hợp đồng là đặt những định nghĩa dùng chung cho hàng loạt cơ sở dữ liệu của bạn vào Lever gốc của lớp. Sau đó, tạo một lớp bên trong cho từng bảng. Mỗi lớp bên trong sẽ liệt kê những cột của bảng tương ứng .

Lưu ý: Khi triển khai giao diện
BaseColumns, lớp bên trong của bạn có thể kế thừa trường khoá
chính có tên _ID mà một số lớp Android như CursorAdapter muốn có. Tuy không bắt buộc nhưng trường này có thể giúp cơ sở dữ liệu
hoạt động hài hoà với khung Android.

Ví dụ : hợp đồng sau đây xác lập tên bảng và tên cột cho một bảng duy nhất biểu lộ nguồn cấp dữ liệu RSS :

Kotlin

object FeedReaderContract {
    // Table contents are grouped together in an anonymous object.
    object FeedEntry : BaseColumns {
        const val TABLE_NAME = "entry"
        const val COLUMN_NAME_TITLE = "title"
        const val COLUMN_NAME_SUBTITLE = "subtitle"
    }
}

Java

public final class FeedReaderContract {
    // To prevent someone from accidentally instantiating the contract class,
    // make the constructor private.
    private FeedReaderContract() {}

    /* Inner class that defines the table contents */
    public static class FeedEntry implements BaseColumns {
        public static final String TABLE_NAME = "entry";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
    }
}

Tạo cơ sở dữ liệu bằng trình trợ giúp SQL

Sau khi xác lập giao diện của cơ sở dữ liệu, bạn nên tiến hành những phương pháp giúp tạo cũng như duy trì cơ sở dữ liệu và bảng. Dưới đây là 1 số ít câu lệnh thường dùng để tạo và xóa bảng :

Kotlin

private const val SQL_CREATE_ENTRIES =
        "CREATE TABLE ${FeedEntry.TABLE_NAME} (" +
                "${BaseColumns._ID} INTEGER PRIMARY KEY," +
                "${FeedEntry.COLUMN_NAME_TITLE} TEXT," +
                "${FeedEntry.COLUMN_NAME_SUBTITLE} TEXT)"

private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS ${FeedEntry.TABLE_NAME}"

Java

private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
    FeedEntry._ID + " INTEGER PRIMARY KEY," +
    FeedEntry.COLUMN_NAME_TITLE + " TEXT," +
    FeedEntry.COLUMN_NAME_SUBTITLE + " TEXT)";

private static final String SQL_DELETE_ENTRIES =
    "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;

Cũng giống như những tệp bạn lưu vào bộ nhớ trong của thiết bị, Android tàng trữ cơ sở dữ liệu của bạn trong thư mục riêng tư của ứng dụng. Dữ liệu của bạn được bảo mật thông tin vì theo mặc định, những ứng dụng khác hoặc người dùng không hề truy cập vào khu vực này .

Lớp SQLiteOpenHelper chứa tập hợp API
hữu ích để quản lý cơ sở dữ liệu.
Khi bạn dùng lớp này để lấy thông tin tham chiếu đến cơ sở dữ liệu của mình, hệ thống
sẽ thực hiện các thao tác tạo và cập nhật cơ sở dữ liệu có khả năng kéo dài chỉ khi
cần thiết, chứ không phải lúc khởi động ứng dụng. Tất cả những gì bạn cần làm là gọi
getWritableDatabase() hoặc
getReadableDatabase().

Lưu ý: Vì các thao tác này có thể kéo dài,
hãy nhớ gọi getWritableDatabase() hoặc getReadableDatabase() trên luồng trong nền.
Bạn có thể xem phần Tạo luồng trên Android để biết thêm thông tin.

Để sử dụng SQLiteOpenHelper, hãy tạo một lớp con
ghi đè phương thức gọi lại onCreate()
onUpgrade(). Ngoài ra, bạn cũng
nên triển khai các phương thức
onDowngrade() hoặc
onOpen(), nhưng
không bắt buộc.

Ví dụ: dưới đây là cách triển khai SQLiteOpenHelper
sử dụng một số lệnh nêu trên:

Kotlin

class FeedReaderDbHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(SQL_CREATE_ENTRIES)
    }
    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        // This database is only a cache for online data, so its upgrade policy is
        // to simply to discard the data and start over
        db.execSQL(SQL_DELETE_ENTRIES)
        onCreate(db)
    }
    override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        onUpgrade(db, oldVersion, newVersion)
    }
    companion object {
        // If you change the database schema, you must increment the database version.
        const val DATABASE_VERSION = 1
        const val DATABASE_NAME = "FeedReader.db"
    }
}

Java

public class FeedReaderDbHelper extends SQLiteOpenHelper {
    // If you change the database schema, you must increment the database version.
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "FeedReader.db";

    public FeedReaderDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_ENTRIES);
    }
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // This database is only a cache for online data, so its upgrade policy is
        // to simply to discard the data and start over
        db.execSQL(SQL_DELETE_ENTRIES);
        onCreate(db);
    }
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(db, oldVersion, newVersion);
    }
}

Để truy cập vào cơ sở dữ liệu của bạn, hãy tạo bản sao lớp con của
SQLiteOpenHelper:

Kotlin

val dbHelper = FeedReaderDbHelper(context)

Java

FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());

Đưa thông tin vào cơ sở dữ liệu

Chèn dữ liệu vào cơ sở dữ liệu bằng cách chuyển đối tượng ContentValues
vào phương thức insert():

Kotlin

// Gets the data repository in write mode
val db = dbHelper.writableDatabase

// Create a new map of values, where column names are the keys
val values = ContentValues().apply {
    put(FeedEntry.COLUMN_NAME_TITLE, title)
    put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle)
}

// Insert the new row, returning the primary key value of the new row
val newRowId = db?.insert(FeedEntry.TABLE_NAME, null, values)

Java

// Gets the data repository in write mode
SQLiteDatabase db = dbHelper.getWritableDatabase();

// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);

// Insert the new row, returning the primary key value of the new row
long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);

Đối số đầu tiên của insert()
chỉ đơn thuần là tên bảng.

Đối số thứ hai sẽ cho khung biết việc nên làm trong trường hợp
ContentValues bị trống (tức là bạn không
put giá trị nào).
Nếu bạn chỉ định tên của một cột, khung sẽ chèn một hàng và đặt
giá trị của cột đó thành rỗng. Nếu bạn chỉ định null, như trong
mã mẫu này, khung sẽ không chèn hàng khi không có giá trị.

Phương thức insert() sẽ trả về mã nhận dạng cho
hàng mới tạo hoặc sẽ trả về -1 nếu xảy ra lỗi khi chèn dữ liệu. Trường hợp này có thể xảy ra
nếu bạn gặp xung đột với dữ liệu có sẵn từ trước trong cơ sở dữ liệu.

Đọc thông tin từ cơ sở dữ liệu

Để đọc từ một cơ sở dữ liệu, hãy dùng phương thức query(), chuyển phương thức đó vào tiêu chí lựa chọn và cột mong muốn.
Phương thức này kết hợp các thành phần của insert()
update(), ngoại trừ danh sách cột
xác định dữ liệu bạn muốn tìm nạp (“phép chiếu”), thay vì dữ liệu cần chèn. Kết quả
của truy vấn sẽ được trả về cho bạn trong đối tượng Cursor.

Kotlin

val db = dbHelper.readableDatabase

// Define a projection that specifies which columns from the database
// you will actually use after this query.
val projection = arrayOf(BaseColumns._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_SUBTITLE)

// Filter results WHERE "title" = 'My Title'
val selection = "${FeedEntry.COLUMN_NAME_TITLE} = ?"
val selectionArgs = arrayOf("My Title")

// How you want the results sorted in the resulting Cursor
val sortOrder = "${FeedEntry.COLUMN_NAME_SUBTITLE} DESC"

val cursor = db.query(
        FeedEntry.TABLE_NAME,   // The table to query
        projection,             // The array of columns to return (pass null to get all)
        selection,              // The columns for the WHERE clause
        selectionArgs,          // The values for the WHERE clause
        null,                   // don't group the rows
        null,                   // don't filter by row groups
        sortOrder               // The sort order
)

Java

SQLiteDatabase db = dbHelper.getReadableDatabase();

// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
    BaseColumns._ID,
    FeedEntry.COLUMN_NAME_TITLE,
    FeedEntry.COLUMN_NAME_SUBTITLE
    };

// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };

// How you want the results sorted in the resulting Cursor
String sortOrder =
    FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";

Cursor cursor = db.query(
    FeedEntry.TABLE_NAME,   // The table to query
    projection,             // The array of columns to return (pass null to get all)
    selection,              // The columns for the WHERE clause
    selectionArgs,          // The values for the WHERE clause
    null,                   // don't group the rows
    null,                   // don't filter by row groups
    sortOrder               // The sort order
    );

Đối số thứ ba và thứ tư (selectionselectionArgs) được
kết hợp để tạo mệnh đề WHERE. Vì được cung cấp riêng biệt với truy vấn
lựa chọn nên các đối số này sẽ được thoát trước khi kết hợp. Nhờ vậy, các câu lệnh lựa chọn của bạn sẽ miễn nhiễm với lệnh chèn
SQL. Để biết thêm thông tin chi tiết về tất cả đối số, hãy xem
tài liệu tham khảo về query().

Để xem xét một hàng trong con trỏ, hãy dùng một trong các phương thức
di chuyển Cursor. Bạn phải luôn gọi phương thức này trước khi bắt đầu đọc giá trị. Vì con trỏ bắt đầu ở
vị trí -1, nên lệnh gọi moveToNext() sẽ đặt “vị trí đọc” ở
mục đầu tiên trong kết quả và trả về thông tin cho biết liệu con trỏ đã đi qua mục cuối cùng trong
tập kết quả hay chưa. Đối với mỗi hàng, bạn có thể đọc giá trị của một cột bằng cách gọi một trong các
phương thức get Cursor, chẳng hạn như getString() hoặc getLong(). Đối với mỗi phương thức get, bạn phải
chuyển vị trí chỉ mục của cột mong muốn. Bạn có thể lấy vị trí này bằng cách gọi
getColumnIndex() hoặc
getColumnIndexOrThrow(). Khi bạn hoàn tất
quá trình lặp theo kết quả, hãy gọi close() trên con trỏ
để giải phóng tài nguyên.
Ví dụ: nội dung sau đây cho biết cách lấy tất cả mã mục được lưu trữ trong con trỏ
và thêm các mã đó vào danh sách:

Kotlin

val itemIds = mutableListOf()
with(cursor) {
    while (moveToNext()) {
        val itemId = getLong(getColumnIndexOrThrow(BaseColumns._ID))
        itemIds.add(itemId)
    }
}
cursor.close()

Java

List itemIds = new ArrayList<>();
while(cursor.moveToNext()) {
  long itemId = cursor.getLong(
      cursor.getColumnIndexOrThrow(FeedEntry._ID));
  itemIds.add(itemId);
}
cursor.close();

Xoá thông tin khỏi cơ sở dữ liệu

Để xoá hàng khỏi bảng, bạn cần cung cấp tiêu chí lựa chọn
(giúp xác định hàng) cho phương thức delete(). Cơ chế
này hoạt động giống như các đối số lựa chọn cho phương thức
query(). Cơ chế này chia
quy cách lựa chọn thành một mệnh đề lựa chọn và các đối số lựa chọn. Mệnh đề
này xác định cột cần xem xét, đồng thời cho phép bạn kết hợp các
quá trình kiểm thử cột. Đối số là các giá trị cần kiểm thử dựa trên đối số được đưa vào mệnh đề.
Vì được xử lý không giống như câu lệnh SQL thông thường, nên kết quả này
miễn nhiễm với lệnh chèn SQL.

Kotlin

// Define 'where' part of query.
val selection = "${FeedEntry.COLUMN_NAME_TITLE} LIKE ?"
// Specify arguments in placeholder order.
val selectionArgs = arrayOf("MyTitle")
// Issue SQL statement.
val deletedRows = db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs)

Java

// Define 'where' part of query.
String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";
// Specify arguments in placeholder order.
String[] selectionArgs = { "MyTitle" };
// Issue SQL statement.
int deletedRows = db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs);

Giá trị trả về của phương thức delete()
cho biết số hàng đã bị xoá khỏi cơ sở dữ liệu.

Khi bạn cần sửa đổi một nhóm nhỏ giá trị cơ sở dữ liệu, hãy dùng phương thức
update().

Thao tác cập nhật bảng sẽ kết hợp cú pháp ContentValues của
insert() với cú pháp WHERE
của delete().

Kotlin

val db = dbHelper.writableDatabase

// New value for one column
val title = "MyNewTitle"
val values = ContentValues().apply {
    put(FeedEntry.COLUMN_NAME_TITLE, title)
}

// Which row to update, based on the title
val selection = "${FeedEntry.COLUMN_NAME_TITLE} LIKE ?"
val selectionArgs = arrayOf("MyOldTitle")
val count = db.update(
        FeedEntry.TABLE_NAME,
        values,
        selection,
        selectionArgs)

Java

SQLiteDatabase db = dbHelper.getWritableDatabase();

// New value for one column
String title = "MyNewTitle";
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);

// Which row to update, based on the title
String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";
String[] selectionArgs = { "MyOldTitle" };

int count = db.update(
    FeedReaderDbHelper.FeedEntry.TABLE_NAME,
    values,
    selection,
    selectionArgs);

Giá trị trả về của phương thức update()
số hàng bị ảnh hưởng trong cơ sở dữ liệu.

Duy trì kết nối cơ sở dữ liệu

getWritableDatabase()
getReadableDatabase() khá
tốn kém nếu gọi khi cơ sở dữ liệu bị đóng. Vì thế, bạn nên duy trì kết nối cơ sở dữ liệu
trong suốt thời gian cần truy cập (nếu có thể). Thông thường, bạn nên đóng cơ sở dữ liệu
trong onDestroy() của Hoạt động gọi.

Kotlin

override fun onDestroy() {
    dbHelper.close()
    super.onDestroy()
}

Java

@Override
protected void onDestroy() {
    dbHelper.close();
    super.onDestroy();
}

Gỡ lỗi cơ sở dữ liệu

SDK Android có chứa công cụ shell sqlite3 cho phép bạn duyệt xem
nội dung trong bảng, chạy lệnh SQL và thực hiện các hàm hữu ích khác trên cơ sở dữ liệu
SQLite. Để biết thêm thông tin, hãy xem cách gửi lệnh shell.

Lưu dữ liệu bằng SQLite | Nhà phát triển Android | Android Developers

Bài viết liên quan
Hotline 24/7: O984.666.352
Alternate Text Gọi ngay