Android SQLite

【Android SQLite】

SQLite 简介

SQLite 是一款内置到移动设备上的轻量型的数据库,是遵守 ACID(原子性、一致性、隔离性、持久 性)的关联式数据库管理系统,多用于嵌入式系统中。
SQLite 数据库是无类型的,可以向一个 integer 的列中添加一个字符串,但它又支持常见的类型比如: NULL,VARCHAR, TEXT, INTEGER, BLOB, CLOB 等。
Android 系统内置了 SQLite,并提供了一系列 API 方便对其进行操作。

使用 SQLiteOpenHelper 创建数据库

SQLiteOpenHelper 是 Android 提供的一个抽象工具类,负责管理数据库的创建、升级工作。如果我们 想创建数据库,就需要自定义一个类继承 SQLiteOpenHelper,然后覆写其中的抽象方法。

创建 SQLiteOpenHelper 类

【文件 1-1】 MySQLiteOpenHelper.java

public class MySQLiteOpenHelper extends SQLiteOpenHelper {
    private static final String TAG = "MySQLiteOpenHelper";
    //定义数据库文件名
    public static final String TABLE_NAME = "myuser.db";

    /**
     * @param context
     * @param name    数据库文件的名称
     * @param factory null
     * @param version 数据库文件的版本
     */
    private MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory,
                               int version) {
        super(context, name, factory, version);
    }

    // 对外提供构造函数
    public MySQLiteOpenHelper(Context context, int version) {
        //调用该类中的私有构造函数
        this(context, TABLE_NAME, null, version);
    }

    // 当第一次创建数据的时候回调方法
    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.d(TAG, "onCreate");
//创建数据库表的语句
        String sql = "create table t_user(uid integer primary key not null, c_name varchar (20), c_age integer, c_phone varchar(20))";
        db.execSQL(sql);
    }

    // 当数据库升级是回调该方法
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.d(TAG, "onUpgrade: oldVersion" + oldVersion + " newVersion=" + newVersion);
        String sql = "alter table t_user add c_money float";
        db.execSQL(sql);
    }

    // 当数据库被打开时回调该方法
    @Override
    public void onOpen(SQLiteDatabase db) {
        Log.d(TAG, "onOpen");
    }
}

注意:
上面的代码我们只是定义了一个 MySQLiteOpenHelper 类继承了 SQLiteOpenHelper 类。在 onCreate()方法中通过执行 sql 语句实现表的创建。

使用 SQLiteOpenHelper 类

在 Activity 中可以执行如【文件 1-2】所示代码。如果第一次执行,则会创建一个数据库文件,创建的数据库文件位/data/data/包名/databases/目录中。
【文件 1-2】 代码片段

    /* 通过构造函数创建一个 MySQLiteOpenHelper 对象,
    * 此时数据库文件还未创建   */
    MySQLiteOpenHelper openHelper = new MySQLiteOpenHelper(this, VERSION);
    // 调用以下任一方法可使数据库文件得以创建
    openHelper.getWritableDatabase();
 // openHelper.getReadableDatabase();
    //关闭资源
    openHelper.close();

注意:
如果 openHelper.getWritableDatabase();或者 openHelper.getReadableDatabase();是第一次被调用,那么数据库文件会被创建,否则只打开不创建。

创建的数据库文件如图 1-1 所示,包含两个文件,一个就是我们自定义的名称 myuser.db,另外一个 myuser.db-journal,该文件会被自动创建,是 sqlite 的一个临时的日志文件,主要用于 sqlite 数据库的事务 回滚操作。



图 1-1 database 文件

数据库的增删改查

以上创建了 MySQLiteOpenHelper 类,通过该类可以获取 SQLiteDatabase 对象。而 SQLiteDatabase对象则可是实现对数据库增删改查操作。

对数据库的增删改查我分为两种方式:

1、使用纯 SQL 语句实现

添加数据

// 纯 SQL 方式添加数据
//通过 SQLiteOpenHelper 对象获取 SQLiteDatabase
SQLiteDatabase database = mOpenHelper.getWritableDatabase();
String sql = "insert into t_user (c_name,c_age,c_phone) values (?,?,?)";
// 执行数据库,参数以 Object[]的形式传递
database.execSQL(sql, new Object[] { 
                "zhangsan" + new Random().nextInt(100),
                new Random().nextInt(30), 
                "" + (5550 + new Random().nextInt(100)) });
//关闭数据库释放资源
database.close();

删除数据

// 删除年龄小于 21 的用户
SQLiteDatabase database = mOpenHelper.getReadableDatabase();
String sql = "delete from t_user where c_age<?";
database.execSQL(sql, new Object[] { 21 });
database.close();

修改数据

SQLiteDatabase database = mOpenHelper.getWritableDatabase();
String sql = "update t_user set c_name=? where c_age=?"; 
database.execSQL(sql, new Object[] { "lisi", 25 });
database.close();

查询数据

【文件 1-6】 查询数据代码

public void query1(View view) {
    //将查询到的数据封装到 User 集合中
    ArrayList<User> users = new ArrayList<User>();
    //获取 SQLiteDatabase
    SQLiteDatabase database = mOpenHelper.getReadableDatabase();
    String sql = "select uid,c_name,c_age,c_phone from t_user where c_age<?";
    // 执行查询语句,返回游标对象,可以将游标看做指向结果集的指针
    Cursor cursor = database.rawQuery(sql, new String[]{"130"});
    // 游标的默认位置位于第一行数据的前面
    while (cursor.moveToNext()) {
        User user = new User();
        //从第 1 列获取 int 型数据,字段的顺序是由查询语句决定的
        int uid = cursor.getInt(0);
        //从第 2 列获取字符串数据
        String name = cursor.getString(1);
        //从第 3 列获取 int 型数据
        int age = cursor.getInt(2);
        String phone = cursor.getString(3);
        user.setAge(age);
        user.setName(name);
        user.setPhone(phone);
        user.setUid(uid);
        users.add(user);
    }
    cursor.close();
    database.close();
}

注意:
使用纯 SQL 语句操作适合 SQL 比较熟练的程序员。如果 SQL 掌握的不好,没关系,Android 提供了一套 API 可以帮助我们完成以上操作。

2、使用特有 API 实现

添加数据

//获取 SQLiteDatabase
SQLiteDatabase database = mOpenHelper.getWritableDatabase();
/* ContentValues底层是 Map 数据结构
* key 对应数据库表中字段
* value 是想插入的值 */
ContentValues values = new ContentValues();
values.put("c_name", "王五" + new Random().nextInt(10));
values.put("c_age", new Random().nextInt(50));
values.put("c_phone", "5556");
/*
* 第一个参数:表名,注意不是数据库名!
* 第二个参数:如果 ContentValues 为空,那么默认情况下是不允许插入空值的,
* 但是如果给该参数设置了一个指定的列名,那么就允许 ContentValues 为空,
*同时给该列插入 null 值。
* 第三个参数:要插入的数值,封装成了 ContentValues 对象\
* 返回 long 类型的值,代表该条记录在数据库中的 id */
long insert = database.insert(TABLE_NAME, null, values);
//释放资源
database.close();

删除数据

SQLiteDatabase database = mOpenHelper.getReadableDatabase();
/*
* 第一个参数:表名
* 第二个参数:删除条件,比如 c_age=?或者 c_age<?
* 第三个参数:参数,用于替换?
* 返回值:删除成功的个数 */
int delete = database.delete(TABLE_NAME, "c_age<?", new String[] { "25" });
database.close();

修改数据

SQLiteDatabase database = mOpenHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("c_name", "wangwu");
// 将年龄等于 28 的姓名改为 wangwu
int update = database.update(TABLE_NAME, values, "c_age=?", new String[] { "28" });
database.close();

查询数据

ArrayList<User> users = new ArrayList<User>();
SQLiteDatabase database = mOpenHelper.getReadableDatabase();
/*
* 参数 1:表名
* 参数 2:要查询的字段
* 参数 3:查询条件
* 参数 4:条件中?对应的值
* 参数 5:分组查询参数
* 参数 6:分组查询条件
* 参数 7:排序字段和规则 */
Cursor cursor = database.query(TABLE_NAME, 
                new String[] {"uid", "c_name", "c_age", "c_phone" }, 
                "c_age<?", new String[] { "130" }, null, null, "c_age desc");
//对游标 Cursor 的遍历
while (cursor.moveToNext()) {
    User user = new User();
    int uid = cursor.getInt(0);
    String name = cursor.getString(1);
    int age = cursor.getInt(2);
    String phone = cursor.getString(3);
    user.setAge(age);
    user.setName(name);
    user.setPhone(phone);
    user.setUid(uid);
    users.add(user);
}
cursor.close();
database.close();

两种 SQLiteDatabase 的不同

SQLiteOpenHelper 有两个方法均可返回 SQLiteDatabase 对象:
一、getWritableDatabase()
该方法返回的对象和另外一个方法返回的对象没有任何差异,返回的对象对数据库都可以进行读、写操作,当磁盘已满或者权限不足的情况下该方法会抛出异常。

二、getReadableDatabase()
跟另外一个方法相比,在磁盘已满的情况下,该方法不会抛出异常,而是返回一个只读的数据库操作对象。
根据这两种方法返回对象的差异,如果需要对数据库进行查询操作则推荐使用后者,如果添加、修改、删除数据则推荐使用前者

数据库的升级和事务操作

数据库的升级

在创建 MySQLiteOpenHelper 对象的时候需要传递一个 int 类型的 version 参数,代表数据库的版本号,数值从 1 开始。如果在 new MySQLiteOpenHelper 对象的时候传递的 version 大于先去创建的 version,那 么就会导致系统回调 onUpgrade 方法,从而实现了数据库的升级。

事务的操作

在 SQLiteDatabase 提供了对事务的支持,处于事务中的操作都是“临时性”的,只有事务提交了 才会将数据保存到数据库
事务的使用不仅可以保证数据一致性,也可以提高批处理时的执行效率

SQLiteDatabase 提供的 beginTransaction()打开事务endTransaction()结束事务
注意:结束 事务并不代表事务提交,如果想让数据写入的数据库需要在结束事务前执行 setTransactionSuccessful()方法。这是事务提交的唯一方式。

如下:展示了当开启事务后,如果因为异常导致事务没有提提交,那么整个转账过程都不会成功。

//数据库的事务操作
SQLiteDatabase database = mOpenHelper.getReadableDatabase();
String sql = "update t_user set c_money=c_money-500 where c_name=?";
// 模拟转账
// 开启了事务,那么之后的操作都是在缓存中执行
database.beginTransaction();
database.execSQL(sql, new String[] { "wangwu" });
 
// 在事务中模拟一个异常的发生
int a = 1 / 0;
String sql2 = "update t_user set c_money=c_money+500 where c_name=?";
database.execSQL(sql2, new String[] { "lisi" });

// 只有执行该代码,才将缓存中的数据写到数据库中
database.setTransactionSuccessful();
database.endTransaction();
database.close();

如下:展示了如果批处理数据时,使用事务可以显著提高效率。运行结果见图 1-2。
【文件 1-13】 代码片段

// 测试批处理效率
public void testBatch(View view) {
    SQLiteDatabase database = mOpenHelper.getWritableDatabase();
// 普通方式添加 10000 条记录
    String sql = "insert into t_user (c_name,c_age,c_phone) values (?,?,?)";
    long startTime = SystemClock.currentThreadTimeMillis();
    for (int i = 0; i < 1000; i++) {
        database.execSQL(sql, new Object[] { "zhangsan" + new Random().nextInt(100),
                new Random().nextInt(30), "" + (5550 + new Random().nextInt(100)) });
    }
    System.out.println("普通模式耗时:"+(SystemClock.currentThreadTimeMillis()-startTime)+"毫秒");
// 开启事务的方式添加 1000 条记录
    startTime = SystemClock.currentThreadTimeMillis();
    database.beginTransaction();
    for(int i=0;i<1000;i++){
        database.execSQL(sql, new Object[] { "zhangsan" + new Random().nextInt(100),
                new Random().nextInt(30), "" + (5550 + new Random().nextInt(100)) });
    }
    database.setTransactionSuccessful();
    database.endTransaction();
    System.out.println("事务模式耗时:"+(SystemClock.currentThreadTimeMillis()-startTime)+"毫秒");
    // 关闭数据库释放资源
    database.close();
}

图 1-2 事务执行效率对比

通过如图 1-2 的测试结果我们发现使用事务大大提高了批量处理的效率。

sqlite3 工具的使用

sqlite3 是 Android 内置的操作数据库工具,使用该工具可以直接对 SQLite 数据库进行操作。 操作步骤(如图 1-3):
1、在命令行界面使用 adb shell 命令进入 linux 内核
2、使用 cd 命令进入数据库所在目录(数据库的路径为”/data/data/应用包名/databases/数据库”)
3、使用”sqlite3 数据库名”进入数据库操作模式



图 1-3 sqlite3 使用截图

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容