Android——数据持久化技术(三) 数据库存储

数据持久化就是指将内存中的瞬时数据保存到存储设备中,保证即使设备在重启之后,数据仍然不会丢失。 持久化技术提供了一种机制可以让数据在瞬时状态和持久状态之间进行转换。

Android系统主要提供了三种方式用于实现数据持久化功能:文件存储SharedPreference存储、以及数据库存储

一、文件存储

Android——数据持久化技术(一) 文件存储

二、SharedPreference存储

Android——数据持久化技术(二) SharedPreference存储

三、SQLite数据库存储

如果只是存储一些简单的数据,上面中提到的文件存储和SharedPreference存储也足以应付了,但是如果遇到需要存储大量复杂的关系型数据,那么这两种存储方式很明显就已经不适用了。这时候,就需要用到数据库存储了。

为了实现更加强大的数据持久化技术,Android系统内置了SQLite数据库。SQLite是一款轻量级的关系型数据库,它运算速度快,占用资源少,特别适合在移动设备中使用。SQLite不仅支持标准的SQL语法,还遵循数据库的ACID事务,因此如果我们以前接触过其他的关系型数据库,就可以很快上手了。

3.1 创建数据库

Android提供了一个抽象类SQLiteOpenHelper,SQLiteOpenHelper类中有两个抽象方法,分别是onCreate()onUpgrade()方法,它们的作用是对数据库进行创建升级

我们可以通过创建一个继承SQLiteOpenHelper类的帮助类,并且重写onCreate()和onUpgrade()方法来实现数据库的创建和升级。

SQLiteOpenHelper中还有两个实例方法:getReadableDatabase()getWritableDatabase()。这两个方法可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则将创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不过当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则出现异常。

SQLiteOpenHelper中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法接收四个参数,第个是Context;第个参数是数据库名,创建数据库时使用的就是这里指定的名称;第个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null;第个参数代表数据库版本号,用于对数据库进行升级的操作。在构建出SQLiteOpenHelper的示例后,再调用getReadacleDatabase()或getWritableDatabase()方法,这时重写的onCreate方法就会执行,进行数据库的创建操作了。而数据库文件则默认存放在/data/data/<package nam>/databases/目录下。

下面新建一个项目来作为实例:

我们希望创建一个名为BookStore.db的数据库,在数据库中新建一张Book表,表中有id(主键)、作者、价格、页数、书名等列。Book表的建表语句如下:

create table Book (
    id integer primary key autoincrement,
    author text,
    price real,
    pages integer,
    name text)

这里简单说明一下SQLite的数据类型:integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型。上述代码中还是用到了primary key 将id列设为主键,并用autoincrement关键字表示id列是自增长的。

先创建一个简单的UI界面,界面中只有一个用于创建数据库的Button。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/create_database"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Create database"
        />
</LinearLayout>

然后创建一个继承SQLiteOpenHelper类的自定义类MyDatabaseHelper:

把我们想要创建的数据库的建表语句赋值给一个String类型,然后将该String传入SQLiteDatabase.execSQL()方法来执行SQL语句。

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

public class MyDatabaseHelper extends SQLiteOpenHelper {

    public static final String CREATE_BOOK = "create table Book ("
            + "id integer primary key autoincrement,"
            + "author text,"
            + "price real,"
            + "pages integer,"
            + "name text)";

    private Context mContext;
    public MyDatabaseHelper(Context context, String name,
                            SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

最后在MainActivity中通过创建继承SQLiteOpenHelper类的自定义类的实例,然后通过调用该实例的getWritableDatabase()方法则成功创建了一个数据库。

public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null,1);
        Button createDatabase = (Button) findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                dbHelper.getWritableDatabase();
            }
        });
    }
}

如下图所示,在点击创建数据库的按钮后,弹出Toast提示创建成功。

如果想要验证是否真的创建了这个数据库,也可以通过adb工具来查看(如果使用Android Studio里的Device File Explorer,最多只能看到databases目录下出现了一个BookStore.db文件,这样仍然无法验证是否创建了book这个表)。

adb是Android SDK中自带的一个调试工具,使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。adb存放在sdk的platform-tools目录下。sdk的目录可以通过打开Android Studio->Files->Settings->Android SDK查看,如下图。

在windows系统中,在使用adb工具之前需要先配置环境变量:右击计算机->属性->高级系统设置->环境变量->系统变量->Path,将platform-tools目录配置进去

配置好环境变量之后就可以打开cmd,输入adb shell进入设备的控制台了。(因为后面的操作需要手机root,我的手机没有root,就不展示了,只说做法)

然后使用cd /data/data/com.example.SQLiteTest(包名)/databases进入到数据库的文件夹里,这是可以看到目录下有两个数据库文件,一个是我们创建的BookStore.db,另一个是BookStore.db-journal,这是为了让数据库能够支持事务而产生的临时日志文件。

接下来我们需要使用sqlite命令来打开数据库,输入sqlite3 BookStore.db(数据库文件名)

这时就已经进入BookStore.db数据库了,然后还可以输入.table命令来查看当前数据库中有哪些表。

3.2 升级数据库

那么如果我们在新建完Book表之后,想要再新建一个Category表的话,要怎么做呢?

这里要注意的是,如果你是想着像新建第一张表那样,在MyDatabaseHelper类中添加新表Category的SQL语句,然后通过db.execSQL()来新建Category表的话(如下代码)

public class MyDatabaseHelper extends SQLiteOpenHelper {

    public static final String CREATE_BOOK = "create table Book ("
            + "id integer primary key autoincrement,"
            + "author text,"
            + "price real,"
            + "pages integer,"
            + "name text)";

    public static final String CREATE_CATEGORY = "create table Category ("
            + "id integer primary key autoincrement,"
            + "category_name text,"
            + "category_code integer)";

    private Context mContext;
    public MyDatabaseHelper(Context context, String name,
                            SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

是不行的。为什么呢?其实在上面新建Book表的例子里,你在点了第一次创建数据库的按钮,然后弹出toast提示成功创建之后,再点第二第三次都是不会弹出toast了,这是因为在第一次点击按钮之后,系统会检测到BookStore.db数据库已经创建了,所以并不会再次运行onCreate方法了,所以其实这样修改代码的话是不能添加新表的。

当我们需要做添加新表或其他升级数据库的操作的时候,onUpgrade()方法就起作用了。

那么如何才能让onUpgrade()方法执行呢?还记得SQLiteOpenHelper构造方法里的第四个参数吗?第四个参数代表的是数据库版本号。因为上面运行的数据库版本号输入的是1,那么现在只需要输入一个比1还大的数字,onUpgrade()方法就会运行起来了。

代码示例:

public class MainActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null,2);
        Button createDatabase = (Button) findViewById(R.id.create_database);
        createDatabase.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                dbHelper.getWritableDatabase();
            }
        });
    }
}
public class MyDatabaseHelper extends SQLiteOpenHelper {

    public static final String CREATE_BOOK = "create table Book ("
            + "id integer primary key autoincrement,"
            + "author text,"
            + "price real,"
            + "pages integer,"
            + "name text)";

    public static final String CREATE_CATEGORY = "create table Category ("
            + "id integer primary key autoincrement,"
            + "category_name text,"
            + "category_code integer)";

    private Context mContext;
    public MyDatabaseHelper(Context context, String name,
                            SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
        Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table if exists Book");        //删除Book表
        db.execSQL("drop table if exists Category");    //删除Category表
        onCreate(db);
    }
}

在onUpgrade方法中需要先将已经创建好的表删除,再运行onCreate()方法,否则当我们创建新表的时候如果这张表已经存在了系统会直接报错。这也是SQLite数据库的一个缺陷:你想要在数据库中新建一张表的话就必须要先把所有已存在的表删除掉,然后再重新新建所有表。

重新运行程序后,再次点击CREATE DATABASE按钮会发现toast再次弹出来了,表示创建新表成功。

3.3 添加数据

在SQLite数据库中,我们对数据的CRUD操作,基本都是通过对SQLiteDatabase对象的操作来实现的。而SQLiteDatabase对象则是通过上述代码中使用到的SQLiteOpenHelpergetWritableDatabase()方法或者getReadableDatabase()方法来获取的。

CRUD操作:C代表添加(Create),R代表查询(Retrieve),U代表更新(Update),D代表删除(Delete)。

SQLiteDatabase中提供了一个用于添加数据的方法:insert()。insert()方法接收三个参数,第个参数是表名,传入我们需要添加数据的表名,第个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值null,一般用不到这个功能,直接传入null即可。第个参数则是ContentValues对象,他提供了一系列put()方法用于向ContentValues中添加数据。关于ContentValues的简单说明可查看我的这篇博客:浅析ContentValues

SQLite数据库中的添加数据操作主要是先给ContentValues对象通过put()方法添加数据,然后通过SQLiteDatabase类的insert()方法将ContentValues传入,这样就添加数据成功了。

首先在布局中添加一个Button用于触发添加数据的操作 

<Button
        android:id="@+id/add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Add data"/>

然后在MainActivity中的onCreate()方法中添加以下代码:

        Button addData = (Button) findViewById(R.id.add_data);
        addData.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues content = new ContentValues();
                //组装第一条数据
                content.put("id", 1);
                content.put("author", "jack");
                content.put("price", 19.9);
                content.put("pages", 150);
                content.put("name", "The universe in the shell");
                db.insert("Book", null, content);   //往Book表中插入一条数据

                content.clear();    //清空content中的内容
                //组装第二套数据
                content.put("category_name", "History");
                content.put("category_code", 1001);
                db.insert("Category", null, content);   //往Category表中插入一条数据
                Toast.makeText(MainActivity.this, "insert data success!", Toast.LENGTH_SHORT).show();
            }
        });

 

3.4 更新数据

对于数据更新,SQLiteDatabase也提供了一个update()方法。update()方法接收四个参数,第个参数是表名,第个参数是ContentValues,需要把更新的数据传入ContentValues中。第三、第四个参数则是用于对数据的约束条件,update()方法只更新符合约束条件的数据。如果不指定第三第四个参数,则默认为对所有数据进行更新。

SQLite数据库中的更新数据操作主要是先将需要更新的数据传入ContentValues,然后将ContentValues对象传入update()方法,update()方法通过第三第四个参数中的约束条件对数据进行更新。

首先在布局中添加一个Button用于触发更新数据操作:

<Button
        android:id="@+id/update_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Update data"/>

然后在MainActivity的onCreate()中添加如下代码:

        Button updateData = (Button) findViewById(R.id.update_data);
        updateData.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues content = new ContentValues();
                content.put("price", 20);
                db.update("Book", content, "author = ?", new String[]{"jack"});
                Toast.makeText(MainActivity.this, "data update success", Toast.LENGTH_SHORT).show();
            }
        });

通过以上代码,可以知道第三个参数有点类似于SQL语句中的where部分,而第四个参数String数组,用于接收第三个参数中占位符所对应的数据

 

3.5 删除数据

SQLiteDatabase提供了一个delete()方法用于对数据的删除。delete()方法接收三个参数,第一个为表名,第二、第三个参数和上面的update()方法中的第三第四个参数一样,用于传入对删除数据的条件约束。第三个参数相当于SQL语句中的where部分,第四个参数为String数组,用于接收第二个参数中占位符所对应的的数据。

在SQLite数据库中删除数据很简单,只需要调用SQLiteDatabase的delete()方法,传入表名和约束条件即可对符合条件的数据进行删除。

首先在布局中添加一个Button用于触发删除数据的操作:

<Button
        android:id="@+id/delete_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Delete data"/>

 然后在MainActivity的onCreate()中添加如下代码:

        Button deleteData = (Button) findViewById(R.id.delete_data);
        deleteData.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v){
                SQLiteDatabase db = dbHelper.getReadableDatabase();
                db.delete("Book", "price > ?", new String[]{"18"});
                Toast.makeText(MainActivity.this,"data delete success!",Toast.LENGTH_SHORT).show();
            }
        });

 

3.6 查询数据

SQLite数据库中的查询功能相对复杂。SQLiteDatabase提供了一个query()方法用于查询数据。query()方法的参数非常复杂,最短的一个方法重载也需要传入7个参数。但是先不要对他产生抵触,因为并不是所有的查询都需要把每一个参数传入,大部分只需要输入某几个参数即可完成查询。下面看看这7个参数都分别是什么:

query()方法参数参数类型对应SQL部分说明
tableStringfrom  table_name指定查询数据的表名
columnsString[]select columns1, column2指定查询数据的列名
selectionStringwhere column = values指定where的约束条件
selectionArgsstring[]values指定selection中占位符的值
groupByStringgroup by column指定需要group by的列名
havingStringhaving column = value对group by 后的结果进一步约束
orderByStringorder by column1, column2指定排序方式

调用query()方法后会返回一个Cursor对象,我们需要的数据都要从这个对象中取出。

在SQLite数据库中查询数据的主要操作为,使用query()方法获取一个Cursor对象,然后对Cursor对象遍历取出数据。

在布局中添加用于触发查询功能的Button :

    <Button
        android:id="@+id/query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Query data"/>

在MainActivity中添加Button的点击触发事件:

Button queryData = (Button) findViewById(R.id.query_data);
queryData.setOnClickListener(new View.OnClickListener(){
    @Override
    public void onClick(View v){
        SQLiteDatabase db = dbHelper.getWritableDatabase();
        //查询Book表中所有的数据
        //Cursor cursor1 = db.query("Book", null, null, null, null, null, null);
        //查询Book表中pages>150的name、price、author这三列数据并按price从低到高排序
        Cursor cursor = db.query("Book", new String[]{"name", "price", "author"}, 
                                "pages > ?", new String[]{"150"},null, null, "price");

        if (cursor.moveToFirst()) {    //将指针移动到第一行
            do {
                //遍历Cursor对象,取出数据并打印
                String name = cursor.getString(cursor.getColumnIndex("name"));
                String author = cursor.getString(cursor.getColumnIndex("author"));
//              int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                double price = cursor.getDouble(cursor.getColumnIndex("price"));

                Log.i("MainActivity", "book name is " + name);
                Log.i("MainActivity", "book author is " + author);
//              Log.i("MainActivity", "book pages is " + pages);
                Log.i("MainActivity", "book price is " + price);
            }while(cursor.moveToNext());
            cursor.close();
        }
        Toast.makeText(MainActivity.this, "Query data success!", Toast.LENGTH_SHORT).show();
    }
});

在调用query()方法后,获取到其返回的cursor对象,首先调用Cursor.moveToFirst()方法将读取数据的指针移动到第一行,然后进入遍历cursor对象的循环中。在循环中,可以通过Cursor对象的getColumnIndex()方法传入列名来获取到该列在表中对应的位置索引,然后通过cursor的get方法来读取数据。然后我们使用Log方法将数据打印出来验证。最后调用Cursor.close()方法关闭Cursor。

3.7 使用SQL操作数据库

考虑到有些开发者更加青睐于直接使用SQL来操作数据库,Android提供了一系列方法,可以直接通过SQL来操作数据库。

使用SQL进行CRUD操作:

  • 添加数据:
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", 
            new String[] {"The universe in the shell", "jack", "150", "19.9"});
  • 更新数据:
db.execSQL("update Book set price = ? where name = ?", new String[] {"9.9", "The universe in the shell" });
  • 删除数据:
db.execSQL("delete from Book where pages > ?", new String[] {"100"};
  • 查询数据:(和别的不一样,使用rawQuery())
db.rawQuery("select * from Book", null);

这些方法和上面的insert()、update()、delete()、query()方法可以完全替代,我们可以根据自己的习惯选择喜欢的操作方式。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值