初学安卓路之初识SQLite

本文详细介绍Android中SQLite数据库的基本概念、创建流程及增删改查等核心操作方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

If you do something and it turns out pretty good, then you should go do something else wonderful, not dwell on it for too long. Just figure out what’s next.你如果出色地完成了某件事,那你应该再做一些其他的精彩事儿。不要在前一件事上徘徊太久,想想接下来该做什么。——乔布斯

  小弟初学安卓,该文算是小弟的学习过程,课后笔记与一些自己的思考,希望在自己的自学路上留下印记,也许有一些自己想的不对的地方,希望各位前辈斧正。

  小弟之前没完全搞清单例模式,结果跑了火车,赶紧改正,向不幸看到小弟说瞎话的同志们道歉!
  

预备知识点


1.什么是SQLite

  “SQL”就已经暴露出它与数据库的联系,没错,SQLite是一个轻量的关系型数据库。它已内置于Android系统中,我们无需进行安装,在编写程序要用到时,直接用代码创建即可,非常方便,而且效率很高,占用资源非常少,速度也很快。SQLite相比一般得数据库使用更简单,它在创建表字段时,可以不指定表字段类型
  

2.准备创建SQLite数据库

  要想方便的创建数据库,需要用到Android为我们提供的一个帮助类——SQLiteOpenHelper(SQLite开启助手?)这是一个抽象类,它其中有两个重要的抽象方法,onCreate()onUpgrade(),我们就是利用它们来对数据库进行创建和升级(按我们的需要操作)。因此我们需要自建一个类来继承SQLiteOpenHelper类,并重写这两个方法。另外还必须要重写SQLiteOpenHelper的构造方法,它有两个构造方法可供选择:
1.

public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
    this(context, name, factory, version, null);
}

2.

public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
        DatabaseErrorHandler errorHandler) {
    if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);

    mContext = context;
    mName = name;
    mFactory = factory;
    mNewVersion = version;
    mErrorHandler = errorHandler;
}

  一般选择第一个构造方法,它的第二个参数name就是指的要创建的数据库的名字(注意,该名字需要有扩展名,例如“database.db”),第三个参数一般写null,除非你想在查询时返回的是自己定义的cursor(游标),否则就传入null来使用默认的cursor,何为cursor,我们待会再来讲解(实际上小弟还未学习如何自定义cursor,未来补上)。第四个参数顾名思义,version(版本号),这是个整型值,一般写1(当然你写2写100都行)。
  
  第二个构造方法多了一个参数,DatabaseErrorHandler 对象,小弟学艺不精,未能找到详细资料,但从名字和代码看,该构造方法与数据库异常有关,而且我们看到,这个代码中要求version(版本号)必须大于或等于1,不然就会抛出异常。
  详细代码操作请往下看。
  

3.Cursor是啥

  这个词直译是“游标”,它对于查询数据非常重要,小弟可能说的有些不对,不过按我个人的理解,它就类似于下图这种感觉(用Excel演示的):
  
就是这种感觉

  大家看这个绿色的框每次移动都只是罩住了一行的表格(记录),cursor就像这样,在数据库的表中各行游走(受控制),将上面图当作数据库表,当绿色框罩住第二行(如同cursor处在数据库表的第0行),此时,这个cursor对象就能获取这一行的日期、学习内容信息,若字段类型为字符串则使用cursor.getString()来获取数据,当然也能获取别的类型数据。而当我们要获取下一行记录时,就必须把cursor移动到下一行(下一个position),那么移动cursor有这些方法来操作:

  • cursor.moveToFirst() //将cursor移动到首位置(position==0,第一条记录),且该方法会返回一个boolean值,若查询结果为空则返回false,可用做判断查询结果。需要注意的是,使用cursor时,如果未进行过移动操作,cursor默认处于-1的位置
  • cursor.moveToNext() //移动到下一位置,同样会返回一个boolean值,如果cursor已经超过了最后一条记录的位置则返回false。
  • cursor.moveToLast() //将cursor移动到最底位置,同样有返回值,若查询结果为空则返回false。
  • cursor.moveToPrevious() //与moveToNext()相反,向上移动
  • cursor.moveToPosition() //移动到一个指定位置。同样会返回boolean值,如果指定位置存在则为true,否则为false。
  • ……

就着代码继续讲


0.*自定义Schema类

  这一步是我在《Android编程权威指南(第二版)》中学到的,不是必须的,但觉得很有道理。首先,我们现在项目包下新建一个包database(database相关.java文件都应该放入其中,如DataBaseHelper),然后建立schema的java类,它是做什么用的呢?首先展示代码:
  

package com.qdf.test.database;


public class BookDbSchema {
    public static final class BookTable {
        public static final String NAME = "books";

        public static final class Cols {
            public static final String BOOK_NAME = "book_name";
            public static final String PRICE = "price";
        }
    }
}

  这段代码不长,首先说明,我待会将要在数据库中创建一个名叫books的表,那么大家根据代码可以看出来了,我在这个类中创建了一个内部类BooksTable,在其中创建了静态字符串常量NAME即“表名”,又在其中创建了一个内部类Cols(列),其中也都是静态字符串常量,可以看出这些是我定义的数据表字段。这是一种模式,这种做法结构十分清晰,我们可以在其它类中引用,并且很方便的修改和新增元素。当然如果我们还要新建表,还可在Schema下再创建内部类。至于至于为什么要叫Schema,有兴趣的朋友可以网络搜索下关键字“SQL Schema”,Schema对于SQL有“架构”的意思,小弟才疏学浅,这里只是抛砖引玉。
  

1.自定义BookDataBaseHelper(extends SQLiteOpenHelper )类
package com.qdf.test.database;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.qdf.test.database.BookDbSchema.BookTable;

public class BookDataBaseHelper extends SQLiteOpenHelper {
    public static final String DATABASE_NAME = "book.db";
    public static final int VERSION = 1;
    private SQLiteDatabase mDB;
    public BookDataBaseHelper(Context context) {
        //修改了一下构造方法需要的参数,直接向父类方法中传入了值
        super(context, DATABASE_NAME, null, VERSION);
    }

    /**
     * 单例模式获取数据库对象
     */
    public SQLiteDatabase getDbInstance() {
        if (mDB== null) {
            mDB= this.getWritableDatabase();
        }
        return mDB;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        //建表,书名类型为varchar(20),价格为浮点型
        sqLiteDatabase.execSQL("create table " + BookTable.NAME + "(" + BookTable.Cols.BOOK_NAME + " varchar(20) not null," + BookTable.Cols.PRICE + " float not null)");
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
        //在这里做升级操作

    }
}


  首先大家可能看到构造方法和我之前说的不一样,参数只剩下了Context context,因为我直接向父类方法传入了context以外的值,这样别的类创建BookDataBaseHelper 对象的时候就只需要传入context就行了,当然你也可以保持原有的构造方法,只要在创建对象时传入相应参数就行。
  
  第二个要介绍下之前说过的从SQLiteOpenHelper继承的两个方法onCreate()与onUpgrade(),让我们来重写它们。先说onCreate(),我们一般用它来初始化数据库,它是什么时候执行的呢,当我们要操作数据库时,必须先创建我们自定义的帮助类(BookDataBaseHelper)的实例(注意根据所选构造方法传参)。然而这时,onCreate()方法还未执行,因为我们还需要创建一个SQLiteDatabase的实例,并通过我们的帮助类实例来获取
SQLiteDatabase的实例,也就是:

SQLiteDatabase mSqLiteDatabase=mBookDataBaseHelper.getReadableDatabase()

  实际上,有两个方法可以获取SQLiteDatabase 实例,一个是上面的 getReadableDatabase(),一个是getWritableDatabase(),这两个方法被调用时,当数据已经创建时则读取数据库,若数据库不存在则创建,然后返回SQLiteDatabase 对象(可对数据库进行读写),但是当磁盘满了无法再写入数据时,getReadableDatabase()返回的对象会以只读的形式打开数据库,getWritableDatabase()则会异常(GG)。当我们通过这个方法获取到SQLiteDatabase 对象时,帮助类的onCreate()方法就执行了,这时候数据库文件以及其内的表都被创建了(数据库名就是我们在构造方法中传入的字符串)。其路径为/data/data/package_name/databases
  这里写图片描述
  根据我上面的onCreate()方法中的内容,可以看到我创建了一个books表,该方法的参数sqLiteDatabase应该就是在我们获取SQLiteDatabase 对象时得到的。至于onUpgrade()方法,当我们想升级版本时,你只需改一下静态常量VERSION的数字,比如2,这时候我们再运行程序的时候,onUpgrade()方法也就执行了,你可以完成你想要的操作,且其参数中有两个整型值,int oldVersion, int newVersion,记录了旧版本号和新版本号。(另外细心的你通过放置Log可以发现你第二次运行代码时,onCreate()就没执行了,即使你写了新代码,感觉突然悟出为什么要有onUpgrade()了)
  第三个要提一下一个从同学(宣传宣传他的简书:Raymond_qi_kang的简书)那知道的一个获取SQLiteDatabase 实例的方法,就是上面代码中的getDbInstance(),返回值属性为SQLiteDatabase 。这样做的话,之前我们说的获取SQLiteDatabase 实例可以写成:
  

mSqLiteDatabase = mBookDataBaseHelper.getDbInstance();

  这样就不用每次获取实例时再getReadableDatabase()或者getWritableDatabase()了。
  

2.来!增删改查!

  说起来小弟刚知道对数据库增删改查是交CRUD操作,好酷炫。事先要说的是,对SQLite数据库增删改查,SQLiteDatabase 拥有自己的方法来辅助我们方便的操作,但是也许会有对SQL比较熟练的朋友更习惯用SQL语句来操作,可喜的是Android是支持我们使用SQL语句的,比如之前的帮助类中onCreate()方法中创建表时我们就是使用的SQL语句,当你想要使用SQL语句操作时只需像上面一样使用execSQL(执行SQL):
  

        sqLiteDatabase.execSQL(SQL语句); //SQL语句为字符串

  我们接下来要将的CRUD操作主要讲讲利用Android提供的辅助方法,利用SQLiteDatabase 对象来操作。(虽然我也更喜欢直接使用SQL语句)
  
  我创建了一个Activity,布局中有四个按钮,分别是ADD、DELETE、QUERY、UPDATE,为它们添加了点击事件监听器,非常简单就不贴出来了,分段讲解,最后再奉上自己的一个小DEMO。
  这里写图片描述

⑴增

  说是增加数据其实就是往现有的数据表中插入一条或多条记录。先来看看这个方法的参数。
  

insert(String table, String nullColumnHack, ContentValues values)

  第一个参数为我们要插入数据的表名,第二个是给未添加数据的可为空的字段自动赋值为NULL,我们一般填null。第三个参数是这个方法的点睛之比,之后的update也会用到,这是个ContentValues类型的参数。
  ContentValues的作用是通过重载它的put()方法来存储键值对,我们只要与表的字段相对应的添加键值对就好(小弟语言组织能力太好,见谅),代码如下:

点击ADD按钮时进行的操作:

//插入一条数据
ContentValues contentValues = new ContentValues();
contentValues.put(BookTable.Cols.BOOK_NAME, "《朝花夕拾》");
contentValues.put(BookTable.Cols.PRICE, 20.8);
mSqLiteDatabase.insert(BookTable.NAME, null, contentValues);
contentValues.clear();//若还要接着插入第二条数据真,则要清空contentValues
//接着插入数据
contentValues.put(BookTable.Cols.BOOK_NAME, "《水浒传》");
contentValues.put(BookTable.Cols.PRICE, 59.9);
mSqLiteDatabase.insert(BookTable.NAME, null, contentValues);
//SQL语句操作
mSqLiteDatabase.execSQL("insert into " +
        BookTable.NAME + "(" +
        BookTable.Cols.BOOK_NAME + "," +
        BookTable.Cols.PRICE + ")" +
        " values('《三重门》',22.5)");

  这段代码首先实例花了ContentValues,然后我们开始用put传入键值对,put中的KEY直接引用了我们之前创建的BookDbSchema类中内部类BookTable的静态常量,是不是让人看了一目了然?同时还展示了用SQL语句操作。记住连续插入数据时要记得在第一次插入后clear哦。最终结果(点开先前说的路径中的.db文件选择相应数据表可查看):
  这里写图片描述
插入成功!

⑵.删

  删除则是使用SQLiteDatabase 的delete方法,先让我们看它的参数:
  

delete(String table, String whereClause, String[] whereArgs)

  它的第一个参数也是表名,第二、第三个参数是条件,如果为null的话,则会默认删除所有行的数据。小弟我的理解呢whereClause是条件的一个大致框架,他表示我们将删除某条数据,而这条数据中的某字段“>”、“<”,“>=”,”<=”或“=”某个值,而这个值则在whereArgs这个数组中,为什么是数组呢,这是为了应对有多条件的情况:
  
DELETE按钮操作:

String whereClause_delete = BookTable.Cols.BOOK_NAME + "=? and " + BookTable.Cols.PRICE + "=?";
String whereArgs_delete[] = {"《三重门》", "22.5"};
mSqLiteDatabase.delete(BookTable.NAME, whereClause_delete, whereArgs_delete);
//使用SQL操作
mSqLiteDatabase.execSQL("delete from " + BookTable.NAME + " where " +
        BookTable.Cols.BOOK_NAME + "='《水浒传》' and " +
        BookTable.Cols.PRICE + "=59.9");
//
mSqLiteDatabase.execSQL("delete from " + BookTable.NAME + " where " + BookTable.Cols.BOOK_NAME + "=?", new String[]{"《朝花夕拾》"});

  可以看到,我设置了两个条件就是书名为《水浒传》并且价格为59.9才删除,而whereClause 中的?为占位符,其值为whereArgs数组中的元素,因为我设置了两个条件,所以数组中有两个元素分别为两个?的值。
  提到这个”?“,还要说说execSQL 的重载方法
  

execSQL(String sql, Object[] bindArgs)

  实际使用是这样:

mSqLiteDatabase.execSQL("delete from " + BookTable.NAME + " where " + BookTable.Cols.BOOK_NAME + "=?", new String[]{"《朝花夕拾》"});

⑶ 改

  改在SQL中就是更新-Update,同样需要用到ContentValues,来为你要更新的行重新传值,
  
UPDATE按钮操作:

ContentValues contentValues_update = new ContentValues();
contentValues_update.put(BookTable.Cols.PRICE, 29.9);
String whereClause_update = BookTable.Cols.BOOK_NAME + "=?";
String whereArgs_update[] = {"《朝花夕拾》"};
mSqLiteDatabase.update(BookTable.NAME, contentValues_update, whereClause_update, whereArgs_update);
//SQL操作
mSqLiteDatabase.execSQL("update " + BookTable.NAME +
        " set " + BookTable.Cols.PRICE + " =99.9 where " +
        BookTable.Cols.BOOK_NAME + "='《水浒传》'");

  大家可以看出,我更改了《朝花夕拾》和《水浒传》的价格分别改成了29.9和99.9。
  

⑷查

  首先我们看看SQLiteDatabase 提供的query方法的四种可传参数选择:
  

  1. boolean distinct, String table, String[] columns,String selection, String[] selectionArgs, String groupBy,String having, String orderBy, String limit

  2. boolean distinct, String table, String[] columns,String selection, String[] selectionArgs, String groupBy,String having, String orderBy, String limit, CancellationSignal cancellationSignal

  3. String table, String[] columns, String selection,String[] selectionArgs, String groupBy, String having,String orderBy
  4. String table, String[] columns, String selection,String[] selectionArgs, String groupBy, String having,String orderBy, String limit

      相信熟悉SQL对其中一些参数名应该不会陌生,简单提一提:
      

    • boolean distinct:查询结果是否消除重复值 true/false
    • String[] columns:要查询的列,数组内元素为字段名
    • String selection:where的约束条件
    • String[] selectionArgs:where中占位符的值
    • String groupBy:对查询结果进行分组
    • String having:分组过滤条件,必须跟groupBy一起用
    • String orderBy:根据传入参数(字段)进行排序,例如:BookTable.Cols.PRICE + ” DESC”/”ASC”——根据书的价格(降序/升序)排序
    • String limit:限制查询结果返回数目,且可以指定第几条到第几条记录
    • CancellationSignal cancellationSignal:进程中取消操作的信号, 如果操作被取消, 当查询命令执行时会抛出 OperationCanceledException 异常(小弟暂时不会用)

        要注意的是这个query方法最后返回给我们的是一个Cursor对象,比如我们要查询整个Books表并且按书的价格降序,那么代码就是:
        

Cursor cursor = mSqLiteDatabase.query(BookTable.NAME, null, null, null, null, null, BookTable.Cols.PRICE + " DESC", null);
while (cursor.moveToNext()) {
    String book_name = cursor.getString(cursor.getColumnIndexOrThrow(BookTable.Cols.BOOK_NAME));
    Float price = cursor.getFloat(cursor.getColumnIndexOrThrow(BookTable.Cols.PRICE));
    Log.i("query_result", "书名: " + book_name + "  价格: " + price);
}
cursor.close();//游标使用完后要记得close

  看看Log输出的结果,
  这里写图片描述
  
  while(cursor.moveToNext())会让游标从-1的position开始不停的往表底移动直到超过表底的position,没移动一次就会扫描一条数据,因为我两个字段的类型分别为字符串和浮点数,所以cursor分别使用了getString()与getFloat()方法来获取字段数据(getFloat()也可以替换为getString()将其转换为字符串),而我们写在内部的cursor.getColumnIndexOrThrow(”字段名”),是其从第一个字段到最后一个字段来寻找我们在括号内指定的字段,返回的是个整型值,如果不存在将抛出IllegalArgumentException 异常,结合在一起才让我们找到了某条数据的某个字段的值。


  最后在这里预留一个小弟我还没弄的特明白的知识点:事务,等小弟弄清楚后在来补上。


  
  学习总结:还有很多相关知识大家有兴趣可以看看源码,相信熟悉SQL的人会很轻松的使用。小弟只是抛砖引玉,这里只是简单记录了各方法的使用,实际使用中还需要们灵活的将各方法封装。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值