MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Android SQLiteDatabase类应用实践

2023-02-117.8k 阅读

一、SQLiteDatabase 基础概述

SQLite 是一款轻型的数据库,它是遵守 ACID 的关系型数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持 Windows/Linux/Unix 等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java 等,还有 Android 系统中同样内建了 SQLite 数据库。

在 Android 开发中,SQLiteDatabase 类是用于与 SQLite 数据库进行交互的核心类。它提供了一系列方法来执行 SQL 语句,管理数据库事务,以及进行数据的增删改查操作。

二、SQLiteDatabase 的获取与创建

在 Android 中,获取 SQLiteDatabase 实例通常通过 SQLiteOpenHelper 类的子类来实现。SQLiteOpenHelper 是一个抽象类,我们需要创建一个它的子类并实现其抽象方法。

2.1 创建 SQLiteOpenHelper 子类

以下是一个简单的 SQLiteOpenHelper 子类示例:

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class MyDatabaseHelper extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "my_database.db";
    private static final int DATABASE_VERSION = 1;

    public MyDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        String createTableQuery = "CREATE TABLE users (" +
                "id INTEGER PRIMARY KEY AUTOINCREMENT," +
                "name TEXT," +
                "age INTEGER)";
        db.execSQL(createTableQuery);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 当数据库版本发生变化时,这里可以进行表的升级操作
        db.execSQL("DROP TABLE IF EXISTS users");
        onCreate(db);
    }
}

在上述代码中:

  • 构造函数中调用了 super(context, DATABASE_NAME, null, DATABASE_VERSION),其中 context 是上下文对象,DATABASE_NAME 是数据库名称,null 表示使用默认的游标工厂,DATABASE_VERSION 是数据库版本号。
  • onCreate 方法在数据库首次创建时被调用,这里我们执行了一条 SQL 语句来创建一个名为 users 的表,该表有 id(自增长主键)、name(文本类型)和 age(整数类型)三个字段。
  • onUpgrade 方法在数据库版本发生变化时被调用,这里简单地删除旧表并重新创建。

2.2 获取 SQLiteDatabase 实例

在 Activity 或其他组件中获取 SQLiteDatabase 实例:

import android.os.Bundle;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyDatabaseHelper dbHelper = new MyDatabaseHelper(this);
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        if (db != null) {
            Toast.makeText(this, "数据库获取成功", Toast.LENGTH_SHORT).show();
            db.close();
        } else {
            Toast.makeText(this, "数据库获取失败", Toast.LENGTH_SHORT).show();
        }
    }
}

在上述代码中,通过 MyDatabaseHelper 的实例调用 getWritableDatabase 方法获取一个可写的 SQLiteDatabase 实例。如果获取成功,会弹出提示“数据库获取成功”,否则提示“数据库获取失败”。获取到的数据库使用完毕后,应该调用 close 方法关闭,以释放资源。

三、数据插入操作

SQLiteDatabase 提供了多种方法来插入数据,包括 insertinsertOrThrowexecSQL 等。

3.1 使用 insert 方法

insert 方法的语法如下:

long insert(String table, String nullColumnHack, ContentValues values)
  • table:要插入数据的表名。
  • nullColumnHack:如果 ContentValues 为空,为了确保插入操作能够正确执行,需要指定一个列名。通常设置为 null
  • ContentValues:一个键值对集合,用于存储要插入的数据。键是列名,值是对应的数据。

以下是插入数据的示例:

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyDatabaseHelper dbHelper = new MyDatabaseHelper(this);
        SQLiteDatabase db = dbHelper.getWritableDatabase();

        ContentValues values = new ContentValues();
        values.put("name", "John");
        values.put("age", 30);

        long newRowId = db.insert("users", null, values);
        if (newRowId != -1) {
            Toast.makeText(this, "数据插入成功,行 ID: " + newRowId, Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(this, "数据插入失败", Toast.LENGTH_SHORT).show();
        }

        db.close();
    }
}

在上述代码中,我们创建了一个 ContentValues 对象,并向其中添加了 nameage 的值。然后调用 db.insert 方法将数据插入到 users 表中。如果插入成功,insert 方法会返回新插入行的 ID,否则返回 -1。

3.2 使用 insertOrThrow 方法

insertOrThrow 方法与 insert 方法类似,不同之处在于如果插入操作失败,insertOrThrow 会抛出异常,而 insert 只会返回 -1。

long insertOrThrow(String table, String nullColumnHack, ContentValues values)

使用示例如下:

try {
    long newRowId = db.insertOrThrow("users", null, values);
    Toast.makeText(this, "数据插入成功,行 ID: " + newRowId, Toast.LENGTH_SHORT).show();
} catch (SQLException e) {
    Toast.makeText(this, "数据插入失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}

通过 try - catch 块捕获可能抛出的 SQLException,并在捕获到异常时显示错误信息。

3.3 使用 execSQL 方法

execSQL 方法可以执行任何 SQL 语句,包括插入语句。使用 execSQL 插入数据的示例如下:

String insertQuery = "INSERT INTO users (name, age) VALUES ('Jane', 25)";
db.execSQL(insertQuery);
Toast.makeText(this, "数据插入成功", Toast.LENGTH_SHORT).show();

这种方式直接执行 SQL 语句进行插入操作,但需要手动编写 SQL 语句,相比 insertinsertOrThrow 方法,代码的可读性和安全性略差,因为容易出现 SQL 注入问题。

四、数据查询操作

SQLiteDatabase 提供了 query 方法来执行查询操作,它的语法较为复杂,以满足不同的查询需求。

4.1 基本查询

query 方法的基本语法如下:

Cursor query(String table, String[] columns, String selection, String[] selectionArgs,
             String groupBy, String having, String orderBy)
  • table:要查询的表名。
  • columns:要返回的列名数组,如果设置为 null,则返回所有列。
  • selection:查询条件,类似于 SQL 语句中的 WHERE 子句,但不包含 WHERE 关键字。
  • selectionArgs:用于替换 selection 中的占位符的值数组。
  • groupBy:分组条件,类似于 SQL 语句中的 GROUP BY 子句。
  • having:分组后的过滤条件,类似于 SQL 语句中的 HAVING 子句。
  • orderBy:排序条件,类似于 SQL 语句中的 ORDER BY 子句。

以下是一个基本查询的示例,查询 users 表中所有数据:

MyDatabaseHelper dbHelper = new MyDatabaseHelper(this);
SQLiteDatabase db = dbHelper.getReadableDatabase();

Cursor cursor = db.query("users", null, null, null, null, null, null);
if (cursor.moveToFirst()) {
    do {
        int id = cursor.getInt(cursor.getColumnIndex("id"));
        String name = cursor.getString(cursor.getColumnIndex("name"));
        int age = cursor.getInt(cursor.getColumnIndex("age"));
        Toast.makeText(this, "ID: " + id + ", Name: " + name + ", Age: " + age, Toast.LENGTH_SHORT).show();
    } while (cursor.moveToNext());
}
cursor.close();
db.close();

在上述代码中,通过 db.query 方法查询 users 表中的所有数据。cursor.moveToFirst() 方法将游标移动到结果集的第一行,如果结果集不为空,则通过 do - while 循环遍历结果集,使用 cursor.getColumnIndex 方法获取列的索引,然后通过列索引获取对应列的值。最后使用 cursor.close() 关闭游标,释放资源。

4.2 带条件查询

以下是查询 users 表中年龄大于 25 岁的用户的示例:

String selection = "age >?";
String[] selectionArgs = {"25"};
Cursor cursor = db.query("users", null, selection, selectionArgs, null, null, null);
if (cursor.moveToFirst()) {
    do {
        int id = cursor.getInt(cursor.getColumnIndex("id"));
        String name = cursor.getString(cursor.getColumnIndex("name"));
        int age = cursor.getInt(cursor.getColumnIndex("age"));
        Toast.makeText(this, "ID: " + id + ", Name: " + name + ", Age: " + age, Toast.LENGTH_SHORT).show();
    } while (cursor.moveToNext());
}
cursor.close();
db.close();

在上述代码中,selection 设置为 age >?selectionArgs 设置为 {"25"},表示查询年龄大于 25 岁的用户。

4.3 分组和排序查询

以下是查询 users 表中按年龄分组,并统计每个年龄组的人数,按人数降序排列的示例:

String groupBy = "age";
String having = "COUNT(*) > 1";
String orderBy = "COUNT(*) DESC";
Cursor cursor = db.query("users", new String[]{"age", "COUNT(*) AS count"}, groupBy, null, groupBy, having, orderBy);
if (cursor.moveToFirst()) {
    do {
        int age = cursor.getInt(cursor.getColumnIndex("age"));
        int count = cursor.getInt(cursor.getColumnIndex("count"));
        Toast.makeText(this, "Age: " + age + ", Count: " + count, Toast.LENGTH_SHORT).show();
    } while (cursor.moveToNext());
}
cursor.close();
db.close();

在上述代码中,groupBy 设置为 age,表示按年龄分组;having 设置为 COUNT(*) > 1,表示只显示人数大于 1 的分组;orderBy 设置为 COUNT(*) DESC,表示按人数降序排列。

五、数据更新操作

SQLiteDatabase 提供了 update 方法来更新表中的数据。

5.1 update 方法语法

int update(String table, ContentValues values, String whereClause, String[] whereArgs)
  • table:要更新数据的表名。
  • values:一个 ContentValues 对象,包含要更新的列名和新值。
  • whereClause:更新条件,类似于 SQL 语句中的 WHERE 子句,但不包含 WHERE 关键字。
  • whereArgs:用于替换 whereClause 中的占位符的值数组。

5.2 更新数据示例

以下是将 users 表中 nameJohn 的用户的年龄更新为 35 的示例:

MyDatabaseHelper dbHelper = new MyDatabaseHelper(this);
SQLiteDatabase db = dbHelper.getWritableDatabase();

ContentValues values = new ContentValues();
values.put("age", 35);

String whereClause = "name =?";
String[] whereArgs = {"John"};

int rowsUpdated = db.update("users", values, whereClause, whereArgs);
if (rowsUpdated > 0) {
    Toast.makeText(this, "数据更新成功,更新行数: " + rowsUpdated, Toast.LENGTH_SHORT).show();
} else {
    Toast.makeText(this, "数据更新失败", Toast.LENGTH_SHORT).show();
}

db.close();

在上述代码中,首先创建一个 ContentValues 对象,设置要更新的 age 字段的值。然后设置更新条件 whereClausename =?,并通过 whereArgs 提供占位符的值。调用 db.update 方法执行更新操作,返回值 rowsUpdated 表示更新的行数。如果更新行数大于 0,则表示更新成功。

六、数据删除操作

SQLiteDatabase 提供了 delete 方法来删除表中的数据。

6.1 delete 方法语法

int delete(String table, String whereClause, String[] whereArgs)
  • table:要删除数据的表名。
  • whereClause:删除条件,类似于 SQL 语句中的 WHERE 子句,但不包含 WHERE 关键字。
  • whereArgs:用于替换 whereClause 中的占位符的值数组。

6.2 删除数据示例

以下是删除 users 表中 age 大于 30 的用户的示例:

MyDatabaseHelper dbHelper = new MyDatabaseHelper(this);
SQLiteDatabase db = dbHelper.getWritableDatabase();

String whereClause = "age >?";
String[] whereArgs = {"30"};

int rowsDeleted = db.delete("users", whereClause, whereArgs);
if (rowsDeleted > 0) {
    Toast.makeText(this, "数据删除成功,删除行数: " + rowsDeleted, Toast.LENGTH_SHORT).show();
} else {
    Toast.makeText(this, "数据删除失败", Toast.LENGTH_SHORT).show();
}

db.close();

在上述代码中,设置删除条件 whereClauseage >?,并通过 whereArgs 提供占位符的值。调用 db.delete 方法执行删除操作,返回值 rowsDeleted 表示删除的行数。如果删除行数大于 0,则表示删除成功。

七、数据库事务处理

数据库事务是一组操作的集合,这些操作要么全部成功执行,要么全部不执行,以确保数据的一致性和完整性。在 SQLiteDatabase 中,可以通过 beginTransactionsetTransactionSuccessfulendTransaction 方法来管理事务。

7.1 事务操作示例

以下是一个简单的事务操作示例,将两个用户的数据插入到 users 表中:

MyDatabaseHelper dbHelper = new MyDatabaseHelper(this);
SQLiteDatabase db = dbHelper.getWritableDatabase();

try {
    db.beginTransaction();

    ContentValues values1 = new ContentValues();
    values1.put("name", "Alice");
    values1.put("age", 28);
    db.insert("users", null, values1);

    ContentValues values2 = new ContentValues();
    values2.put("name", "Bob");
    values2.put("age", 32);
    db.insert("users", null, values2);

    db.setTransactionSuccessful();
} catch (Exception e) {
    e.printStackTrace();
} finally {
    db.endTransaction();
    db.close();
}

在上述代码中,通过 db.beginTransaction() 开始一个事务,在事务中执行两个插入操作。如果所有操作都成功执行,调用 db.setTransactionSuccessful() 标记事务成功。最后在 finally 块中调用 db.endTransaction() 结束事务。如果事务过程中发生异常,由于没有标记事务成功,endTransaction 方法会回滚事务,确保数据的一致性。

八、SQLiteDatabase 的优化与注意事项

  1. 避免频繁打开和关闭数据库:每次打开和关闭数据库都有一定的开销,尽量在需要使用数据库的时间段内保持数据库连接打开,使用完毕后再关闭。
  2. 使用事务:如前面所述,使用事务可以确保数据的一致性和完整性,同时也能提高批量操作的效率。
  3. 合理使用索引:在经常用于查询条件的列上创建索引可以显著提高查询性能,但索引也会占用额外的存储空间,并且会增加插入、更新和删除操作的时间,所以要根据实际情况权衡。
  4. 避免 SQL 注入:使用 selectionArgs 等方式来传递参数,而不是直接将用户输入拼接到 SQL 语句中,以防止 SQL 注入攻击。
  5. 及时释放资源:在使用完 SQLiteDatabaseCursor 等资源后,要及时调用 close 方法释放资源,避免内存泄漏。

通过以上对 SQLiteDatabase 在 Android 应用中的详细实践介绍,开发者可以更加熟练地运用 SQLite 数据库来管理应用数据,提高应用的性能和稳定性。在实际开发中,应根据具体需求和场景,合理选择和使用 SQLiteDatabase 的各种方法和特性,以实现高效的数据管理。