安卓开发之 在应用中使用数据库

概述

在 Android 代码中创建和修改 SQLite 数据库,我们可以参考 Android 文档 Save data using SQLite,我们在 Android 中需要采取两个基本步骤来设置 SQLite 就可以和数据库互动了。如下所示:

Define a schema and contract-创建架构和契约类
Create a database using an SQLOpenHelper-使用 SQLiteOpenHelper 创建数据库

完成上面两步后就可以执行创建、读取、更新和删除我们的数据的操作了。
创建架构和契约类

创建数据库架构其实就是需要规划数据库结构。所以我们需要问两个问题:

表格的名称是什么?
表格里列的名称和数据类型是什么?

还记得下面的语句吗?

CREATE TABLE  ( ,  , ...);

以宠物收容所场景为例:
安卓开发之 在应用中使用数据库_第1张图片

绘制表格,规划数据库结构

表格
安卓开发之 在应用中使用数据库_第2张图片

创建 Contract (契约)类

为什么要使用 Contract 类?

看个创建表格的两个例子:

String makeTableStatement = "CREATE TABLE entry (_id INTEGER PRIMARY KEY, entryid TEXT, title TEXT, subtitle TEXT)";

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

观察上述代码,我们发现第二个创建表格的语句中 表格名称和列名称存储在了常量里。 在生成 SQL 指令时,消除了出现拼写错误的可能性,或者不小心将不应该大写的字母大写了。如果想更改实际的列名称,也只需在一个地方更改下就行了。

总结使用 Contract 类的三个理由:

  • 帮助我们定义架构,规定了去哪里查找数据库常量;
  • 在生成 SQL 指令时,可以帮助我们避免拼写错误;
  • 使我们更容易更新数据库架构
创建 Contract 类

根据之前规划的数据库结构即我们定义的架构,我们在项目的主包下面创建一个包名为 data,然后在改包下创建一个新的 java 类名为 PetContract,将该类用 final 修饰,因为它只是用来提供常量,我们不需要扩展或为此外部类实现任何内容。使用我们定义的架构,为每个表格创建一个内部类,并为每个列标题创建常量,代码如下所示:

public final class PetContract {
    private PetContract() {}

    public static final class PetEntry implements BaseColumns {

        public final static String TABLE_NAME = "pets";

        public final static String _ID = BaseColumn._ID;
        public final static String COLUMN_PET_NAME = "name";
        public final static String COLUMN_PET_BREED = "breed";
        public final static String COLUMN_PET_GENDER = "gender";
        public final static String COLUMN_PET_WEIGHT = "weight";

        public final static int GENDER_UNKONWN = 0;
        public final static int GENDER_MALE = 1;
        public final static int GENDER_FORMALE = 2;
    }
}

使用 SQLiteOpenHelper 创建数据库

在 dada 包中创建一个新的 PetDbHelper 类,该类继承自 SQLiteOpenHelper类。我们需要重写 onCreate() 和 onUpGrade() 方法,并且为数据库名称和版本创建常量,同时别忘了创建该类的构造函数,还要为用来创建表格的 SQLite 指令创建一个字符串常量。

/**
 * Database helper for Pets app. Manages database creation and version management.
 */
public class PetDbHelper extends SQLiteOpenHelper {

    public static final String LOG_TAG = PetDbHelper.class.getSimpleName();

    /** Name of the database file */
    private static final String DATABASE_NAME = "shelter.db";

    /**
     * Database version. If you change the database schema, you must increment the database version.
     */
    private static final int DATABASE_VERSION = 1;

    /**
     * Constructs a new instance of {@link PetDbHelper}.
     *
     * @param context of the app
     */
    public PetDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    /**
     * This is called when the database is created for the first time.
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        // Create a String that contains the SQL statement to create the pets table
        String SQL_CREATE_PETS_TABLE =  "CREATE TABLE " + PetEntry.TABLE_NAME + " ("
                + PetEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                + PetEntry.COLUMN_PET_NAME + " TEXT NOT NULL, "
                + PetEntry.COLUMN_PET_BREED + " TEXT, "
                + PetEntry.COLUMN_PET_GENDER + " INTEGER NOT NULL, "
                + PetEntry.COLUMN_PET_WEIGHT + " INTEGER NOT NULL DEFAULT 0);";

        // Execute the SQL statement
        db.execSQL(SQL_CREATE_PETS_TABLE);
    }

    /**
     * This is called when the database needs to be upgraded.
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // The database is still at version 1, so there's nothing to do be done here.
    }
}

写了这么多我们也不知道数据库是否能正常运行,我们可以通过下面的方法进行检查。

查看数据库是否正常运转:

创建 displayDatabaseInfo() 方法,将其放置到我们代码中,只有 SQL 能正常运转时,该方法才能运转。
将数据库下载到本地,通过在终端使用 SQL 语句查看。

@Override
protected void onCreate(Bundle savedInstanceState) {

displayDatabaseInfo()
}

/**
* Temporary helper method to display information in the onscreen TextView about the state of
* the pets database.
*/
private void displayDatabaseInfo() {
// To access our database, we instantiate our subclass of SQLiteOpenHelper
// and pass the context, which is the current activity.
PetDbHelper mDbHelper = new PetDbHelper(this);

    // Create and/or open a database to read from it
    SQLiteDatabase db = mDbHelper.getReadableDatabase();

    // Perform this raw SQL query "SELECT * FROM pets"
    // to get a Cursor that contains all rows from the pets table.
    Cursor cursor = db.rawQuery("SELECT * FROM " + PetEntry.TABLE_NAME, null);
    try {
        // Display the number of rows in the Cursor (which reflects the number of rows in the
        // pets table in the database).
        TextView displayView = (TextView) findViewById(R.id.text_view_pet);
        displayView.setText("Number of rows in pets database table: " + cursor.getCount());
    } finally {
        // Always close the cursor when you're done reading from it. This releases all its
        // resources and makes it invalid.
        cursor.close();
    }
}

连接数据库流程

安卓开发之 在应用中使用数据库_第3张图片

PetDbHelper mDbHelper = new PetDbHelper(this);
SQLiteDatabase db = mDbHelper.getReadableDatabase();

请求数据库,mDbHelper 将检查是否已经存在一个数据库?

如果不存在,则 PetDbHelper 的实例将使用 onCreate() 方法创建一个数据库,接着创建 SQLiteDatabase 的实例对象返回给 Activity。

如果数据库已经存在,PetDbHelper 的实例将不会调用 onCreate() 方法,相反,将创建一个 SQLiteDatabase 的实例对象并关联现有的数据库,然后将该对象返回给请求数据库的 Activity。

最终时帮助我们创建 SQLiteDatabase 对象与 shelter 数据库关联的 SQLiteDatabase 对象,之后我们就可以通过该对象向 shelter 数据库传达 SQLite 指令了。
从设备中提取数据库

了解了数据库的连接流程,我们就可以检测数据库是否正常运转。当然我们还可以通过另一种方式,通过查看设备的文件系统,使我们能够实际地看到 petDbHelper 类在创建数据库,然后当数据库实际创建好后看到 .db 文件出现。

安卓开发之 在应用中使用数据库_第4张图片

在这之前我们可以打开手机应用信息,将 pets 应用中清楚缓存和存储数据。同时注释掉代码中的 displayDatabaseInfo() 方法。在 Android Studio 中点击 Android Device Monitor 按钮,选择我们当前使用的模拟器,转到 File Explorer,该功能可以让我们浏览设备的文件系统,点开 data 文件夹,再点开里面的 data 文件夹,在里面找到我们应用的包,再打开包,查找文件,我们会发现没有 .db 文件,这时我们的 getReadableDatabase() 方法尚未被调用。

安卓开发之 在应用中使用数据库_第5张图片

当我们把刚才注释掉的 displayDatabaseInfo() 方法重新恢复,运行代码后,我们再查看文件系统中该包下有了 shelter.db 数据库文件了。再运行一次就不会再出现新的文件了。如果想看 shelter.db 文件中的内容,可以通过 DDMS 下载文件,保存到电脑本地上,通过 SQL 语言在终端查看。

安卓开发之 在应用中使用数据库_第6张图片
数据库对象及插入数据

在 Android 文档 Put information into a database 中,为我们举出了例子。

// Gets the data repository in write mode
SQLiteDatabase db = mDbHelper.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);

ContentValues 中存储了大量的键值对,键是数据库中的列名称,值就是插入的值。举例:

安卓开发之 在应用中使用数据库_第7张图片

ContentValues values = new ContentValues();
values.put(PetEntry.COLUMN_PET_NAME, "Garfield");
values.put(PetEntry.COLUMN_PET_BREED, "Tabby");
values.put(PetEntry.COLUMN_PET_GENDER, PetEntry.GENDER_MALE);
values.put(PetEntry.COLUMN_PET_WEIGHT, 7);
db.insert(PetEntry.TABLE_NAME, null, values);

注意 insert() 方法会返回新插入的 ID,如果出现错误它会返回 -1。
数据库查询方法

我们可以通过 db.rawquery() 方法来读取数据库,但不建议使用。就像存在 SQLiteDatabase.insert() 方法一样,我们可以使用 SQLiteDatabase.query() 方法来读取数据库,它会帮助我们构建查询,从而避免语法错误。我们看下 Android 文档来了解下:

SQLiteDatabase db = mDbHelper.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
    );

观察上述代码,我们发现首先定义了一个字符串数组 projection,它其实是指我们想要获取的列名称,就像这样的语句:

SELECT name, breed FROM pets;

定义 projection 使我们能够指定想要获取的具体列,默认则获取所有列,类似于:

SELECT * FROM pets;

projection 大小会影响到性能。

调用 query() 方法,该方法具有大量输入参数,代表了 SELECT 语句的不同部分。第一个参数 projection 我们已经在上面介绍了,接下来 selection 和 selectionArgs 处理的是可选 WHERE 条件。例如根据 ID 选择单个宠物:

SELECT * FROM pets WHERE _id = 1;

使用 selection 和 selectionArgs 属性就是这样的:

// Define 'where' part of query
String selection = PetEntry._ID + "?";
// Specify arguments in placeholder order
String[] selectionArgs = {"1"}; 

selection 参数是 WHERE 关键字之后的类型为 String 的,这里使用 ? 作为占位符,然后填充为 selectionArgs 参数中的值,它是一个字符串数组,负责替换这里 selection 中的问号,这里将其设为 1。

为什么使用 ? 号和 selectionArgs ,而不是直接写成 1 ?

这里没有区别,我们可以将 selectionArgs 设为 null,将 ? 号改为 1 。但是在某些情况下,选择内容可能来自表格,使用占位符是一种安全措施,可以防止出现 SQL 注入攻击,也就是用户不按套路出牌,编辑一些代码类内容输入,使我们的查询语句出现歧义错误。

调用 query() 方法后会返回一个 Cursor 对象,它是一种可以捕获数据库中所有子集的对象。
什么是 Cursor

简而言之,Cursor 是指 代表数据库中多行内容的对象。假设如下是我们整个数据库的架构:
这里写图片描述

如果我们针对这个数据库中这个表格调用 query() 方法,我们可以指定希望返回的值,然后这些信息就会以 Cursor 对象的形式返回给我们。

如果我们的选择参数是

SELECT * FROM pets

我们获得的 Cursor 对象,其中包含数据库中的各行内容。

如果我们希望进一步指定仅选择 pets 表格中宠物名为 Toto 的行,我们获得的 CUrsor 对象就是其中所有行里的宠物名是 Toto 的行,结果如下:
安卓开发之 在应用中使用数据库_第8张图片

我们再来看另一个示例,假设从 pets 表格中选择 name 和 breed,也就是将白鸽缩小为仅返回这两列:

安卓开发之 在应用中使用数据库_第9张图片
也就是将表格缩小为仅返回这两列,返回的 Cursor 对象包含所有行,但是仅包含 name 和 breed 列的详情。

Cursor 包含了是我们能够访问和浏览 Cursor 对象的方法,如果 Cursor 包含多行的话就可以浏览各行。

Cursor.getCount()

Cursor 对象代表了数据库中的行和列,此外,他还提供了这些行中的当前位置,为了能获得特定的数据,我们需要将 Cursor 移动到我们所需要的精确行

安卓开发之 在应用中使用数据库_第10张图片

当我们第一次获取 Cursor 时,位置从 -1 开始,这是无效的位置,首个可用位置为0,然后逐步递增。

Cursor.moveToFirst

该方法既将 Cursor 中的位置移动到结果中的第一行,这使我们能够访问第一条记录里的数据。

安卓开发之 在应用中使用数据库_第11张图片

Cursor.moveToLast

该方法会使我们跳到这里的最后一行。
安卓开发之 在应用中使用数据库_第12张图片

Cursor.moveToPosition(int position)

该方法会将 Cursor 位置移动到指定位置。

安卓开发之 在应用中使用数据库_第13张图片

上面方法返回的都是 Boolean 类型的值,这样可以帮助我们判断实际是否移动到了该位置。比如我们当前 Cursor 已经移动到了最后一行,再调用向下移动的方法就会返回 false。

我们可以使用不同的 get 方法从数据库中获取特定的值。
安卓开发之 在应用中使用数据库_第14张图片

使用 getColumnIndex(String columnName)方法,来根据名称来获取列的索引。举例:

安卓开发之 在应用中使用数据库_第15张图片

对于 Cursor 要注意的一个事项是,使用完毕后,一定要记得调用 cursor.close(),这样会完全清空 Cursor 使其无效。仅在完全操作完毕后调用该方法。不关闭 Cursor 的话,会因内存泄漏而降低性能。

你可能感兴趣的:(安卓开发之 在应用中使用数据库)