Android 一篇很啰嗦的SQLite知识总结

版权声明:本文为博主原创文章,未经博主容许不得转载
源码:github.com/AnliaLee
你们要是看到有错误的地方或者有啥好的建议,欢迎留言评论html

前言

博主这两天心血来潮准备回顾下SQLite的知识,然而网上查找资料的过程是痛苦的,由于不多有一篇博客能把SQLite的入门知识讲全的,得好几篇合着来看才行,所以个人浏览器选项卡基本上是这样的java

正所谓本身动手丰衣足食,我不想之后忘了还要这样再来一次,因此决定本身从新总结一番,集众家之长来篇大杂烩。本篇博客内容不少,为了尽可能涵盖到全部知识不得已写得很啰嗦,你们能够看着目录按需跳着看,若是有什么遗漏或写错的地方欢迎你们指出来~android


什么是SQLite

关于SQLite的简介网上有不少不少,其中看到一篇博客总结得很好,遂直接引用啦~如下摘自Android黄金篇-SQLite数据库git

SQLite介绍

SQLite是一款轻量级的关系型数据库,它的运算速度很是快,占用资源不多,一般只须要几百K的内存就足够了,于是特别适合在移动设备上使用。github

SQLite特色

  • 轻量级
    使用 SQLite 只须要带一个动态库,就能够享受它的所有功能,并且那个动态库的尺寸想当小。
  • 独立性
    SQLite 数据库的核心引擎不须要依赖第三方软件,也不须要所谓的“安装”。
  • 隔离性
    SQLite 数据库中全部的信息(好比表、视图、触发器等)都包含在一个文件夹内,方便管理和维护。
  • 跨平台
    SQLite 目前支持大部分操做系统,不至电脑操做系统更在众多的手机系统也是可以运行,好比:Android和IOS。
  • 多语言接口
    SQLite 数据库支持多语言编程接口。
  • 安全性
    SQLite 数据库经过数据库级上的独占性和共享锁来实现独立事务处理。这意味着多个进程能够在同一时间从同一数据库读取数据,但只能有一个能够写入数据。
  • 弱类型的字段
    同一列中的数据能够是不一样类型

SQLite数据类型

SQLite具备如下五种经常使用的数据类型:sql

存储类 存储类
NULL 值是一个 NULL 值
INTEGER 值是一个带符号的整数,根据值的大小存储在 一、二、三、四、6 或 8 字节中
REAL 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字
TEXT 值是一个文本字符串,使用数据库编码(UTF-八、UTF-16BE 或 UTF-16LE)存储
BLOB 值是一个 blob 数据,彻底根据它的输入存储

调试工具

调试SQLite数据库推荐使用SQLite Expert(Personal),简单易用shell

固然你也能够直接用adb shell查看数据库,这个就看我的喜好了数据库


SQLiteDatabase与SQLiteOpenHelper

在讲如何使用SQLite数据库以前,有必要介绍一下这两个重要的类:SQLiteDatabaseSQLiteOpenHelper,这是SQLite数据库API中最基础的两个类编程

SQLiteDatabase

在Android中,SQLite数据库的使用始于SQLiteDatabase这个类(SQLiteOpenHelper也是基于SQLiteDatabase来进行数据库建立和版本管理,以后会详细介绍),咱们能够看下它的内部方法(未截全)数组

能够发现insertquery等熟悉的字眼,这些方法都是已经封装好的,咱们只须要传入适当的参数便可完成诸如插入、更新、查询等操做。固然SQLiteDatabase也提供了直接执行SQL语句的方法,如

  • execSQL
    能够执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句
  • rawQuery
    用于执行select语句

总结来讲,咱们能够将SQLiteDatabase看做是一个数据库对象,经过调用其中的方法来建立、删除、执行SQL命令,以及执行其余常见的数据库管理任务。咱们会在以后的章节中详细讲述如何使用SQLiteDatabase一步步搭建咱们的本地数据库

SQLiteOpenHelper

SQLiteOpenHelperSQLiteDatabase的辅助类,经过对SQLiteDatabase内部方法的封装简化了数据库建立与版本管理的操做。它是一个抽象类,通常状况下,咱们须要继承并重写这两个父类方法:

  • onCreate
    在初次生成数据库时才会被调用,咱们通常重写onCreate生成数据库表结构并添加一些应用使用到的初始化数据
  • onUpgrade
    当数据库版本有更新时会调用这个方法,咱们通常会在这执行数据库更新的操做,例如字段更新、表的增长与删除等

此外父类方法中还有onConfigureonDowngradeonOpen,通常项目中不多用到它们,若是你们须要进一步了解能够看下这篇博客,这里就不赘述了

那么SQLiteOpenHelperSQLiteDatabase是如何关联起来的呢?SQLiteOpenHelper中提供了getWritableDatabasegetReadableDatabase方法,其最终都调用了getDatabaseLocked,并在第一次调用(或数据库版本更新)时执行咱们以前在onCreateonUpgrade等方法中重写的数据库操做,这两个方法的区别在于

  • getWritableDatabase
    打开一个可 读/写 的数据库,若是数据库不存在,则会自动建立一个数据库,最终返回SQLiteDatabase对象
  • getReadableDatabase
    打开一个可 的数据库,其余同上

建立数据库

一些实操以前要知道的东西

通常来讲,建立SQLite数据库的方法有三,咱们按照使用频率从高到低的顺序列出这三种方法

  • 继承SQLiteOpenHelper,调用getWritableDatabase / getReadableDatabase打开或建立数据库(推荐初学者使用)
  • 调用SQLiteDatabase.openOrCreateDatabase打开或建立数据库
  • 调用Context.openOrCreateDatabase打开或建立数据库

它们最终都是要调用SQLiteDatabase.openDatabase方法,下图能够简单地归纳它们的调用关系

那么咱们不妨看下openDatabase干了啥

public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) {
	SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
	db.open();//继续执行打开或建立数据库的操做
	return db;
}
复制代码

解释一下各个参数

  • String path
    数据库文件路径
  • CursorFactory factory
    用于构造自定义的Cursor子类对象,在执行查询操做时返回,若传入 null 则使用默认的factory构造Cursor
  • int flags
    用于控制数据库的访问模式,可传入的参数有
    • CREATE_IF_NECESSARY:当数据库不存在时建立该数据库文件
    • ENABLE_WRITE_AHEAD_LOGGING:绕过数据库的锁机制,以多线程操做数据库的方式进行读写
    • NO_LOCALIZED_COLLATORS:打开数据库时,不根据本地化语言对数据库进行排序
    • OPEN_READONLY:以只读方式打开数据库
    • OPEN_READWRITE:以读写方式打开数据库
  • DatabaseErrorHandler errorHandler
    当检测到数据库损坏时进行回调的接口,通常没有特殊须要传入 null 便可

咱们做为初学者刚入门没必要过多地深究每一个参数的含义及使用场景,知道有这么些内容,往后扩展技能起个索引做用就行,所以这里就不费篇幅继续深挖了

关于建立数据库的路径

关于这点颇有必要单独开个小节啰嗦一下,不少资料都忽略了这个或者讲得不是很清楚。数据库的默认路径

/data/data/<package_name>/databases/
复制代码

通常状况下咱们在建立数据库时path参数只需传入“xxx.db”,系统自动会在该默认路径下建立名为“xxx.db”的数据库文件,这样作最大的好处就是安全,要想拿到这个文件咱们得先root手机(听说模拟器中能够直接拿,我没亲自试验过),并且这个数据库文件也会随着App的删除而删除。但某些场景下,例如咱们要取出数据库文件进行调试,在默认路径下建立数据库就显得不那么方便了。所以,咱们能够在内部存储或sd卡中建立该数据库文件,例如咱们要传入的path能够写成这样

Environment.getExternalStorageDirectory().getPath() + "/SQLiteTest/xxx.db"
复制代码

那么数据库文件xxx.db就会放在内存的SQLiteTest目录中,须要注意的是,若是SQLiteTest目录不存在,系统会抛出异常

这是由于咱们前文提到的SQLiteDatabase.openDatabase方法中db.open()后续并无建立目录的相关代码,因此咱们须要手动去建立该目录(记得配置权限)

File dir = new File(Environment.getExternalStorageDirectory().getPath() + "/SQLiteTest/");
if (!dir.exists()) {
    dir.mkdir();
}
复制代码

目录建立以后咱们就能够继续执行建立数据库的操做了

接下来咱们以建立数据库文件test.db,建一张test表为例,按顺序讲讲这三种建立数据库的方法

1、继承SQLiteOpenHelper(推荐初学者使用)

按照以前讲的,咱们须要先继承SQLiteOpenHelper,而后重写onCreateonUpgrade方法

如何调用

建立MySQLiteOpenHelper

public class MySQLiteOpenHelper extends SQLiteOpenHelper {
    public static final String FILE_DIR = Environment.getExternalStorageDirectory().getPath() + "/SQLiteTest/";
    public static final int DATABASE_VERSION = 1;
    public static final String TABLE_NAME = "test";

    public MySQLiteOpenHelper(Context context, String name){
        this(context, name, null, DATABASE_VERSION);
    }

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

    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, DatabaseErrorHandler errorHandler) {
        super(context, name, factory, version, errorHandler);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        File dir = new File(FILE_DIR);
        if (!dir.exists()) {
            dir.mkdir();
        }
        
        try{
            db.execSQL("create table if not exists " + TABLE_NAME +
                    "(id text primary key,name text)");
        }
        catch(SQLException se){
            se.printStackTrace();
        }
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        if(newVersion > oldVersion){
            String sql = "DROP TABLE IF EXISTS " + TABLE_NAME;
            db.execSQL(sql);
            onCreate(db);
        }
    }
}
复制代码

Activity中调用getWritableDatabase / getReadableDatabase(我这里把动态申请权限的代码也贴出来,以后就省略了)

public class MainActivity extends AppCompatActivity {
    public static final String DATABASE_NAME = FILE_DIR + "test.db";
    private static final int CODE_PERMISSION_REQUEST = 100;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            //申请写入权限
            ActivityCompat.requestPermissions(this, new String[]{
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, CODE_PERMISSION_REQUEST);
        } else {
            createDB();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch(requestCode) {
            case CODE_PERMISSION_REQUEST:
                if(grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    createDB();
                } else{
                }
                break;
            default:
                break;

        }
    }

    private void createDB(){
        MySQLiteOpenHelper sqLiteOpenHelper = new MySQLiteOpenHelper(this,DATABASE_NAME);
        SQLiteDatabase database = sqLiteOpenHelper.getWritableDatabase();
    }
}
复制代码

SQLite Expert看下结果

2、调用SQLiteDatabase.openOrCreateDatabase打开或建立数据库

先来看下openOrCreateDatabase方法的源码

public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) {
	return openOrCreateDatabase(file.getPath(), factory);
}

public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) {
	return openDatabase(path, factory, CREATE_IF_NECESSARY, null);
}

public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory, DatabaseErrorHandler errorHandler) {
	return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
}
复制代码

能够看到openOrCreateDatabase其实是以CREATE_IF_NECESSARY模式打开建立数据库的

如何调用

Activity中执行相应代码

private static final String FILE_DIR = Environment.getExternalStorageDirectory().getPath() + "/SQLiteTest/";
public static final String DATABASE_NAME = FILE_DIR + "test.db";

private void createDB(){
	File dir = new File(FILE_DIR);
	if (!dir.exists()) {
		dir.mkdir();
	}
	
	SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(DATABASE_NAME, null);
	database.execSQL("create table if not exists " + "test" +
			"(id text primary key,name text)");
}
复制代码

结果同样的我就不重复贴出来了

3、调用Context.openOrCreateDatabase打开或建立数据库

Context.openOrCreateDatabase的具体实如今ContextImpl类中(关于Context的知识你们能够自行查阅资料了解)

@Override
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) {
	checkMode(mode);
	File f = getDatabasePath(name);
	int flags = SQLiteDatabase.CREATE_IF_NECESSARY;
	if ((mode & MODE_ENABLE_WRITE_AHEAD_LOGGING) != 0) {
		flags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING;
	}
	if ((mode & MODE_NO_LOCALIZED_COLLATORS) != 0) {
		flags |= SQLiteDatabase.NO_LOCALIZED_COLLATORS;
	}
	SQLiteDatabase db = SQLiteDatabase.openDatabase(f.getPath(), factory, flags, errorHandler);
	setFilePermissionsFromMode(f.getPath(), mode, 0);
	return db;
}
复制代码

能够看出Context.openOrCreateDatabaseSQLiteDatabase.openOrCreateDatabase本质上没有太大区别,只是多了一个mode参数用于设置操做模式,可传入的参数有

  • MODE_PRIVATE
    默认的模式,建立的数据库文件只能经过调用该模式的应用程序访问(或全部应用程序共享相同的用户ID),咱们通常设置这个参数便可
  • MODE_ENABLE_WRITE_AHEAD_LOGGING
    功能和以前讲的设置ENABLE_WRITE_AHEAD_LOGGING同样
  • MODE_NO_LOCALIZED_COLLATORS
    功能和以前讲的设置NO_LOCALIZED_COLLATORS同样
  • MODE_WORLD_READABLE
    设置后当前文件能够被其余应用程序读取官方不建议在API 17以上的版本设置该参数Android 7.0以上会直接抛出异常:XXX no longer supported)
  • MODE_WORLD_WRITEABLE
    设置后当前文件能够被其余应用程序写入官方不建议在API 17以上的版本设置该参数Android 7.0以上会直接抛出异常:XXX no longer supported)

如何调用

private static final String FILE_DIR = Environment.getExternalStorageDirectory().getPath() + "/SQLiteTest/";
public static final String DATABASE_NAME = FILE_DIR + "test.db";

private void createDB(){
	File dir = new File(FILE_DIR);
	if (!dir.exists()) {
		dir.mkdir();
	}

	SQLiteDatabase database = this.openOrCreateDatabase(DATABASE_NAME,MODE_PRIVATE,null);
	database.execSQL("create table if not exists " + "test" +
			"(id text primary key,name text)");
}
复制代码

数据库的相关操做

这里只介绍简单的增删改查操做,其他的你们能够自行查阅资料了解

SQLiteDatabase提供了insert方法

public long insert(String table, String nullColumnHack, ContentValues values) {
	try {
		return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
	} catch (SQLException e) {
		Log.e(TAG, "Error inserting " + values, e);
		return -1;
	}
}
复制代码

各参数含义以下

  • String table
    要插入数据的表的名称
  • String nullColumnHack
    values参数为空或者里面没有内容的时候,执行insert是会失败(底层数据库不容许插入一个空行),为了防止这种状况,咱们要在这里指定一个列名,若是发现将要插入的行为空行时,就会将指定的这个列名的值设为null,而后再向数据库中插入。若values不为null而且元素的个数大于0,则通常将nullColumnHack设为null
  • ContentValues values
    ContentValues相似一个map.经过键值对的形式存储值

如何调用

像前文所说的,咱们能够经过SQLiteDatabase.insertSQLiteDatabase.execSQL两种方式添加数据

ContentValues values = new ContentValues();
values.put("id","1");
values.put("name","name1");
database.insert("test",null,values);

database.execSQL("insert into test(id, name) values(2, 'name2')");
database.close();
复制代码

结果如图

一样的SQLiteDatabase提供delete方法删除数据

public int delete(String table, String whereClause, String[] whereArgs) {
	acquireReference();
	try {
		SQLiteStatement statement =  new SQLiteStatement(this, "DELETE FROM " + table +
				(!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
		try {
			return statement.executeUpdateDelete();
		} finally {
			statement.close();
		}
	} finally {
		releaseReference();
	}
}
复制代码
  • String table
    表名称
  • String whereClause
    条件语句,至关于where关键字,可使用占位符分隔多个条件
  • String[] whereArgs
    对应条件语句的值的数组,注意如有多个值则需与selection中的多个条件(?符号)一一对应

如何调用

咱们先添加几条数据用来测试

database.execSQL("insert into test(id, name) values(3, 'name3')");
database.execSQL("insert into test(id, name) values(4, 'name4')");
database.execSQL("insert into test(id, name) values(5, 'name5')");
复制代码

执行删除语句

String whereClause = "id=?";
String[] whereArgs = {"3"};
database.delete("test",whereClause,whereArgs);

database.execSQL("delete from test where name = 'name4'");
database.close();
复制代码

SQLiteDatabase.update用于更新数据

public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
	return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE);
}
复制代码

updateWithOnConflict你们能够自行查阅资料了解,这里只简单解释一下各个参数的含义

  • String table
    表名称
  • ContentValues values
    ContentValues相似一个map.经过键值对的形式存储值
  • String whereClause
    条件语句,至关于where关键字,可使用占位符分隔多个条件
  • String[] whereArgs
    对应条件语句的值的数组,注意如有多个值则需与selection中的多个条件(?符号)一一对应

如何调用

ContentValues values = new ContentValues();
values.put("name","update2");
String whereClause = "id=?";
String[] whereArgs={"2"};
database.update("test",values,whereClause,whereArgs);

database.execSQL("update test set name = 'update5' where id = 5");
database.close();
复制代码

SQLiteDatabase提供queryrawQuery方法执行查询的操做,查询结束后返回一个Cursor对象。Cursor是一个游标接口,提供了遍历查询结果的方法,因为本篇博客重点不是这个,就不展开讲了。咱们接着看query方法

public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) {

	return query(false, table, columns, selection, selectionArgs, groupBy,
			having, orderBy, null /* limit */);
}

public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {

	return query(false, table, columns, selection, selectionArgs, groupBy,
			having, orderBy, limit);
}

public Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) {
	return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,
			groupBy, having, orderBy, limit, null);
}

public Cursor query(boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit, CancellationSignal cancellationSignal) {
	return queryWithFactory(null, distinct, table, columns, selection, selectionArgs,
			groupBy, having, orderBy, limit, cancellationSignal);
}
复制代码

query方法的参数不少,你们简单了解下就行

  • boolean distinct
    是否去除重复记录
  • String table
    表名称
  • String[] columns
    要查询的列名称数组,例如这句查询语句的加黑部分:SELECT id, name FROM test
  • String selection
    条件语句,至关于where关键字,可使用占位符分隔多个条件
  • String[] selectionArgs
    对应条件语句的值的数组,注意如有多个值则需与selection中的多个条件(?符号)一一对应
  • String groupBy
    分组语句,对查询的结果进行分组
  • String having
    分组条件,对分组的结果进行限制
  • String orderBy
    排序语句
  • String limit
    分页查询限制
  • CancellationSignal cancellationSignal
    取消操做的信号,通常用于设置查询取消时的后续操做,若是没有则设置为null。若是操做取消了,query语句运行时会抛出异常(OperationCanceledException)

如何调用

前文提到执行查询SQL语句的方法是rawQuery,一样在这里咱们对照着query方法看看调用过程(若是数据量大的话查询操做是须要放在单独的线程中去执行的)

调用query方法,各个参数我就不一一试了,简单举几个例子

if(database!=null){
	Cursor cursor = database.query ("test",null,null,null,null,null,null);
	while (cursor.moveToNext()){
		String id = cursor.getString(0);
		String name=cursor.getString(1);
		Log.e("SQLiteTest query","id:"+id+" name:"+name);
	}
	database.close();
}
复制代码

或者

if(database!=null){
	Cursor cursor = database.rawQuery("SELECT * FROM test", null);
	while (cursor.moveToNext()){
		String id = cursor.getString(0);
		String name=cursor.getString(1);
		Log.e("SQLiteTest query","id:"+id+" name:"+name);
	}
	database.close();
}
复制代码

加上多个条件限制

if(database!=null){
	String selection = "id=? or name=?";
	String[] selectionArgs = {"1","update2"};
	Cursor cursor = database.query ("test",null,selection,selectionArgs,null,null,null);
	while (cursor.moveToNext()){
		String id = cursor.getString(0);
		String name=cursor.getString(1);
		Log.e("SQLiteTest query","id:"+id+" name:"+name);
	}
	database.close();
}
复制代码

或者

if(database!=null){
	Cursor cursor = database.rawQuery("SELECT * FROM test WHERE id=? or name=?", new String[]{"1","update2"});
	while (cursor.moveToNext()){
		String id = cursor.getString(0);
		String name=cursor.getString(1);
		Log.e("SQLiteTest query","id:"+id+" name:"+name);
	}
	database.close();
}
复制代码


SQLite的替代者们

随着技术的更迭,原生的SQLite早已不是惟一的选择,咱们利用其打好基础后,也不妨尝试下各类热门的数据库框架,例如OrmLitegreenDAOObjectBoxRealm等等。各个框架都有本身的优势和不足之处,你们能够按需选择适合本身的框架使用。因为博主并无每一个框架都进行过度析和尝试,在这强行科普那就是耍流氓了,因此直接拉几篇写得很好的对比博客来作外援吧~

相关博客连接
数据库到底哪家强?
【Android 数据库框架总结,总有一个适合你!】
Android数据库框架GreenDao&Realm实战分析

相关文章
相关标签/搜索