Theme Android (2) (Bài trước)
(Bài tiếp) SQlite (2)

Tạo CSDL SQLite và kết nối trong Android

SQLite là một loại CSDL quan hệ, nhỏ gọn - (nguồn mở https://www.sqlite.org/about.html), như giới thiệu ở trang chủ SQLite, nó là hệ CSDL được sử dụng rất rộng rãi trên nhiều nền tảng (Mobile, Destop, Webserver ...), nó có các ưu điểm có thể kế ra như: Là hệ CSDL SQL nhúng, sử dụng ngay mà không cần cấu hình, không cần có một Server SQL riêng (khác với MySQL, MS SQL Server ...) ...

Mỗi CSDL SQlite được lưu trữ thành một file trong strorage (xem thêm SQLite cơ bản), Android hỗ trợ tốt SQLite 3, trước tiên để ứng dụng hoạt động cần cho phép quyền truy cập đến storage để lưu / xoá file CSDL. Trong manifests của ứng dụng cần thêm quyền này vào.

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Nơi lưu SQLite3 DB trong Android

Với Android mỗi ứng dụng được cách ly một vùng để lưu dữ liệu của riêng nó (vùng này không được truy cập bởi ứng dụng khác), các file dữ liệu được hoạch định nên lưu tại /data/data/applicationId/databases/

Trong đó applicationId là ID của ứng dụng thiết lập trong file build.gradle của ứng dụng. Ví dụ ứng dụng có ID là net.xuanthulab.sqlitetutorial thì nơi lưu CSDL sẽ là:
/data/data/net.xuanthulab.sqlitetutoria/databases/

Đây là cấu trúc chuẩn, tuy nhiên bạn có thể lưu DB bất kỳ đâu miễn là ứng dụng có quyền truy cập đến thư mục đó.

Nếu không chắc chắn lưu trữ ở đâu thì từ đối tượng Context (Activity) có thể xác định vị trí lưu trữ của DB. Giả sử DB của bạn có tên là mydb.db, thì gọi phương thức sau sẽ có được đường dẫn đầy đủ:
String pathdb = getDatabasePath("mydb.db").getPath();

Mở, tạo một SQLite DB

Thư viện Android cung cấp lớp cơ sở SQLiteDatabase (android.database.sqlite.SQLiteDatabase) để thực hiện các công việc tạo ra file CSDL, kết nối đến nó, truy vấn, cập nhật dữ liệu ..., để có được một đối tượng lớp SQLiteDatabase có một số cách.

Tạo hoặc mở một DB bằng phương thức tĩnh SQLiteDatabase.openOrCreateDatabase(path, factory)

Trong đó tham số path là đường dẫn ĐẦY ĐỦ đến file cơ sở dữ liệu sẽ kết nối (nếu file này chưa có sẽ tự động tạo ra), nhớ cần chỉ ra đường dẫn chính xác lưu tại vị trí mà ứng dụng có quyền truy cập. Ví dụ, ứng dụng ID là net.xuanthulab.sqlitetutorial, cần tạo db có tên file là mydb.db thì đường dẫn đầy đủ sẽ là "/data/data/net.xuanthulab.sqlitetutorial/databases/mydb.db"

Tham số factory là đối tượng SQLiteDatabase.CursorFactory, nó thi hành khi truy vấn đến DB, ta sẽ tìm hiểu sau. Bạn có thể thiết lập là null, đây là đoạn code để mở (tạo nếu chưa có) một CSDL có tên mydb.db

SQLiteDatabase db = null;
try {
    db = SQLiteDatabase.openOrCreateDatabase("/data/data/net.xuanthulab.sqlitetutorial/databases/mydb.db", null);
}
catch (SQLiteException ex) {
    //Lỗi kết nối
    Log.e(TagSQL, ex.getMessage());
}
// ... Các lệnh truy cấn đến DB ...
// Khi không dùng đến kết nối, cần đóng lại
db.close();
Tạo hoặc mở một DB bằng phương thức tĩnh Context.openOrCreateDatabase(dbname, mode, factory)

Sử dụng cách này khi bạn đang trong một Activity hoặc có đối tượng Context, lúc này bạn chỉ việc chỉ ra tên DB (ví dụ mydb.db, phương thức sẽ định vị db tại cấu trúc đường dẫn trên), tham số mode có thể lấy là Context.MODE_PRIVATE, cách này khá thông dụng, ví dụ đoạn code sau trong một Activity.

SQLiteDatabase db = openOrCreateDatabase("mydb.db", Context.MODE_PRIVATE, null);
// ... Các lệnh truy cấn đến DB ...
// Khi không dùng đến kết nối, cần đóng lại
db.close();

Xoá một SQLite DB

Xoá một DB, đơn giản là xoá file khỏi storage, bạn có thể thực hiện bằng cách.

SQLiteDatabase.deleteDatabase("/data/data/net.xuanthulab.sqlitetutorial/databases/mydb.db");
//oặc trong Activity
deleteDatabase("mydb.db");
//Hoặc từ đối tượng Context
context.deleteDatabase("mydb.db");

Thực hiện các truy vấn SQLite - Android

Sau khi kết nối, tạo được Database (có đối tượng SQLiteDatabase) thì để chạy các truy vấn (câu lệnh SQL - Đọc thêm SQL Cơ bản để biết cách viết các câu lệnh SQL) ta có thể dùng phương thức rawQuery

Thực hiện câu lệnh SQL

execSQL
execSQL(String sql, String[] selectionArgs)
execSQL(String sql)
Thực hiện các câu lệnh SQL mà không có tập dữ liệu trả về như:
Tạo bảng CREATE TABLE, xoá bảng DROP TABLE Persons,
Chèn dữ liệu vào bảng với INSERT INTO,
Xoá dòng dữ liệu với DELETE FROM,
Cập nhật các dòng dữ liệu với UPDATE
rawQuery
rawQuery (String sql, String[] selectionArgs)
Để thực hiện các câu lệnh truy vấn có tập dữ liệu trả về như: Lấy dữ liệu với SELECT

Các câu lệnh SQL viết trên, nếu có cần truyền tham số thì các tham số được ký hiệu bằng ký tự ?, sau đó tham số truyền vào theo thứ tự trong mảng selectionArgs

Ví dụ làm việc với SQLite

Ví dụ này áp dụng các lệnh làm việc với SQLite ở trên, nó ở mức thấp, điều này đòi hỏi viết trực tiếp các truy vấn SQL nên cũng cần thành thao ngôn ngữ SQL.

Ta sẽ thực hiện lại ví dụ tại Dùng ListView hiện thị dữ liệu Danh sách, ứng dụng hiện thị các sản phẩm. Nâng cấp ví dụ này bằng cách dữ liệu lưu ở SQLite, cho phép tạo ra sản phẩm, bấm chọn để edit, xoá sản phẩm ...

Mã nguồn đầy đủ lưu tại: Ví dụ SQLite (phần 1), bạn tải về để đọc mã nguồn. Có thể tải bằng lệnh Git

git clone git@github.com:xuanthulabnet/android-sqlite-example1.git
sqlite

Những chú ý khi đọc code ví dụ:

Kiểm tra một bảng đã tồn tại trong CSDL

Một số tác vụ, như đọc dữ liệu sẽ kiểm tra bảng có tên product tồn tại hay không, làm điều này bằng đoạn code:

//true nếu bảng tồn tại
boolean isTableExist(SQLiteDatabase db, String table) {
    Cursor cursor = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name=?", new String[]{table});
    boolean tableExist = (cursor.getCount() != 0);
    cursor.close();
    return tableExist;
}
Nạp dữ liệu sản phẩm vào mảng của Adapter (ListView):

Các sản phẩm lưu ở bảng product được nạp vào Adapter của ListView, sử dụng phương thức rawQuery để chạy câu lệnh SELECT, qua đó thu được đối tượng Cursor giữ tập hợp dữ liệu trả về, duyệt qua tập này để lấy dữ liệu. Đoạn code cơ bản như sau:

 private void loadDbProduct() {
    listProduct.clear();
    SQLiteDatabase db = openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null);

    if (!isTableExist(db, "product")) {
        Toast.makeText(this, "Bảng product không tồn tại, cần tạo bảng trước", Toast.LENGTH_LONG).show();
        ((TextView) findViewById(R.id.infomation)).setText("Bảng dữ liệu không có, phải tạo bảng");
        findViewById(R.id.addbutton).setVisibility(View.GONE);
        return;
    }

    ((TextView) findViewById(R.id.infomation)).setText("PRODUCT");
    findViewById(R.id.addbutton).setVisibility(View.VISIBLE);


    Cursor cursor = db.rawQuery("SELECT id, name, price from product", null);

    //Đến dòng đầu của tập dữ liệu
    cursor.moveToFirst();
    while (!cursor.isAfterLast()) {

        //Đọc dữ liệu dòng hiện tại
        int productID = cursor.getInt(0);
        String productName = cursor.getString(1);
        int productPrice = cursor.getInt(2);

        listProduct.add(new Product(productID, productName, productPrice));

        // Đến dòng tiếp theo
        cursor.moveToNext();
    }

    cursor.close();

}
Activity soạn thảo / thêm mới sản phẩm

Khi bấm vào nút Thêm sản phẩm thì sẽ mở một Activity có tên EditProduct để thêm sản phẩm, hoặc khi bấm chọn thì cũng mở Activity này để chỉnh sửa, xoá ...

Code có sử dụng kỹ thuật truyền dữ liệu qua lại giữa các Activity (xem thêm Activity cơ bản để biết chi tiết.). Trong đó, khi bấm chuột vào thêm mới, thì Intent truyền giá trị isupdatefalse lúc này Activity khởi tạo là thêm sản phẩm. Ngược lại khi bấm vào chọn phần tử thì truyền giá trị này là false để khởi tạo Activity là cập nhật dữ liệu sẵn có.

//Bấm vào thêm mới phần tử
findViewById(R.id.addbutton).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {

        Intent intent = new Intent();
        intent.putExtra("isupdate", false);
        intent.setClass(MainActivity.this, EditProduct.class);
        startActivityForResult(intent, RESULT_PRODUCT_ACTIVITY);


    }
});


//Bấm vào phần tử danh sách, mở Activity để soạn thảo phần tử
listViewProduct.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Product product = (Product) productListViewAdapter.getItem(position);
        Intent intent = new Intent();
        intent.putExtra("isupdate", true);
        intent.putExtra("idproduct", product.productID);
        intent.setClass(MainActivity.this, EditProduct.class);
        startActivityForResult(intent, RESULT_PRODUCT_ACTIVITY);
    }
});

Nạp lại ListView khi đóng cửa sổ soạn thảo

Mỗi khi Activity soạn thảo đóng lại (MainActivity nhận biết được Activity khác nó gọi đóng lại ở phương thức onActivityResult) thì nạp lại dữ liệu cho ListView

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        switch (requestCode) {
            case RESULT_PRODUCT_ACTIVITY:
                //Khi đóng Activity EditProduct thì nạp lại dữ liệu
                loadDbProduct();
                productListViewAdapter.notifyDataSetChanged();
                break;
            default:
                break;
        }

    }

EditProduct đọc dữ liệu sản phẩm cần soạn thảo

Khi EditProduct mở ra, nếu nó cần đọc dữ liệu soạn thảo thì sẽ nạp từ Database, đoạn code như sau:

//ID sản phẩm do MainActivity truyền
idproduct = intent.getIntExtra("idproduct", 0);

SQLiteDatabase db = openOrCreateDatabase(MainActivity.DB_NAME, Context.MODE_PRIVATE, null);
Cursor cursor = db.rawQuery("SELECT id, name, price from product where id = ?",
        new String[]{idproduct + ""});
cursor.moveToFirst();
int productID = cursor.getInt(0);
String productName = cursor.getString(1);
int productPrice = cursor.getInt(2);đến

//Product có được sau khi đọc từ DB
product = new MainActivity.Product(productID, productName, productPrice);
cursor.close();

Xoá một sản phẩm đoạn code như sau:

 findViewById(R.id.deleteBtn).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        SQLiteDatabase db = openOrCreateDatabase(MainActivity.DB_NAME, Context.MODE_PRIVATE, null);
        db.execSQL("DELETE FROM product where id = ?", new String[]{String.valueOf(idproduct)});
        db.close();
        finish();
    }
});

Save sản phẩm vào DB

findViewById(R.id.save).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        SQLiteDatabase db = openOrCreateDatabase(MainActivity.DB_NAME, Context.MODE_PRIVATE, null);
        product.name = editName.getText().toString();
        product.price = Integer.parseInt(editPrice.getText().toString());

        if (isupdate) {
            //Cập nhật
            db.execSQL("UPDATE product SET name=?, price = ? where id = ?",
                    new String[]{product.name, product.price + "", product.productID + ""});
        } else {
            //Tạo
            //Cập nhật
            db.execSQL("INSERT INTO product (name, price ) VALUES (?,?)",
                    new String[]{product.name, product.price + ""});
        }
        db.close();
        finish();
    }
});

Trên đây đã áp dụng các truy vấn cơ bản đến SQLite, ngoài ra còn có các phương thức chuyên biệt tiện lợi hơn sẽ trình bày ở phần tiếp nhất là kỹ thuật sử dụng SQLiteOpenHelper


Đăng ký nhận bài viết mới
Theme Android (2) (Bài trước)
(Bài tiếp) SQlite (2)