初学安卓路之初识SQLite

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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值