SQLite是由C语言编写的一款轻型数据库,因占用资源小,处理速度快,功能齐全,特别适用于移动设备,最重要的是开源,任何人都可以使用它,许多开源项目(PHP,Python)和当今两大手机操作系统Android和iOS都使用了SQLite,造就了SQLite成为目前世界上最常见的数据库引擎。
2.SQLite特点除了占用资源小,处理速度快等优点,SQLite还有自己的特点,那就是支持弱数据类型,其他主流SQL数据库通常支持强类型的数据,也就是每一列的类型都必须预先指定,如果输入的数据和指定数据类型对不上则报错,而SQLite采用的是弱类型,也就是说,创建一个表时指定某列的数据类型,但是你可以把任何数据类型插入该列,SQLite将检查它的类型,如果该类型与关联的列不匹配,则SQLite会尝试将该值转换成该列的类型,如果不能转换,则该值将作为其本身具有的类型存储。
SQLite支持数据类型如下:包括NULL、INTEGER、REAL、TEXT和BLOB
NULL: 该值是空值; INTEGER: 该值是一个有符号整数,根据值的大小以1、2、3、4、6或8个字节存储; REAL: 该值是一个浮点值,以8字节浮点数存储; TEXT: 该值是一个文本字符串,使用数据库编码(UTF-8,UTF-16BE或UTF-16LE)存储; BLOB: 值是BLOB数据块,以输入的数据格式进行存储。 3.Android使用SQLite上文已说,Android集成了SQLite数据库,与此同时,Android还提供了使用SQLite数据库的API,可通过这些API可很方便的进行数据库操作。
3.1 SQLiteOpenHelper介绍SQLiteOpenHelper是Android提供的SQLite帮助类,用于管理数据库(包括创建、增,删,改)和管理数据库的版本,方便开发者操作SQLite,SQLiteOpenHelper基本方法如下(更相信请参考官方文档https://developer.android.google.cn/reference/android/database/sqlite/SQLiteOpenHelper?hl=zh-cn)
方法名 | 描述 |
---|---|
onCreate() | 首次创建数据库时执行该方法,如果数据库不存在则创建,onCreate()方法在初次调用时才会被调用。重写onCreate()方法时,生成数据表结构 |
onUpgrade() | 在需要升级数据库时调用方法,该方法检测数据库传入的版本号与当前的版本号是否相同,如果传入的版本号高于之前的版本,触发该方法 |
getReadableDatabase() | 创建或者打开可读数据库 |
getWritableDatabase() | 创建打开可读/写的数据库 |
close() | 关闭所有打开的数据库对象 |
onCreat和onUpgrade是开发者实现,由SQLiteOpenHelper来调用。
其中onCreat是SQLiteOpenHelper在尝试找数据库时,如果没有找到则执行该方法,那么什么时候SQLiteOpenHelper去找数据库呢?是在创建SQLiteOpenHelper实例时,还是使用SQLiteOpenHelper的getWritableDatabase()或者getReadableDatabase()获取SQLiteDatabase时?
我们可以用代码来验证一下,我们在继承了SQLiteOpenHelper的MySQLiteOpenHelper代码里面添加一个flag作为标记,flag初始值是0,然后在构造函数里赋值成100,最后在onCreate中添加打印代码。然后我们在Activity里首先只实例化MySQLiteOpenHelper,不获取数据库,看是否触发了onCreate里的打印信息。之后再添加java SQLiteDatabase sqLiteDatabase= mySQLiteOpenHelper.getWritableDatabase();
获取数据,再看打印信息。
Activity代码如下:
package com.test.sqlitedemo;
import androidx.appcompat.app.AppCompatActivity;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
MySQLiteOpenHelper mySQLiteOpenHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mySQLiteOpenHelper=new MySQLiteOpenHelper(this,"test",1);
}
}
MySQLiteOpenHelper代码如下:
package com.test.sqlitedemo;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
private int flag=0;
/**
*
* @param context
* @param name 数据库名称
* @param version 数据库版本号 当想升级数据库时,填入大于当前版本号的数
*/
public MySQLiteOpenHelper(Context context, String name, int version) {
super(context, name, null, version);
//这里赋值成100
flag=100;
}
/**
*
* @param db SQLiteDatabase数据库对象
*/
@Override
public void onCreate(SQLiteDatabase db) {
Log.d("MySQLiteOpenHelper", "flag="+flag);
//新建数据表
db.execSQL("create table translog(_id integer primary key autoincrement, transid , amount,date)");
flag=50;
}
/**
*
* @param db
* @param oldVersion 旧版本号
* @param newVersion 新版本号
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i("MySQLiteOpenHelper", oldVersion + "------->" + newVersion);
}
}
验证结果是只实例化MySQLiteOpenHelper没有触发onCreate里的打印信息,在获取数据库的时候,SQLiteOpenHelper才会调用onCreate方法,关闭应用后再打开,也不会触发打印信息,因为数据库已经创建了。(这里验证半天,然后发现官网用一句话说清楚了什么时候创建数据库:
Create a helper object to create, open, and/or manage a database. This method always returns very quickly. The database is not actually created or opened until one of getWritableDatabase() or getReadableDatabase() is called
再来看onUpgrade方法,该方法在数据库需要升级的时候调用,当传入SQLiteOpenHelper构造版本参数大于当前的版本时,触发该方法。升级数据库是开发者经常要面对的,而且多数时候在升级数据库时不能把之前的数据全部删掉,开发者可在onUpgrade函数里添加搬运数据的代码,在触发升级后执行里面的搬运逻辑。
有人可能会问了,SQLiteOpenHelper难道仅仅是帮忙检查数据库是否存在和升级数据库用到?如果只是这两个功能,虽然能帮开发者省点工作量,但并没有省多少工作啊!别忘了一句getWritableDatabase或者getWritableDatabase就可以获得数据库实例,换成其他平台操作数据库,还得慢慢拼凑数据库连接语句,这也省了不少工作量。其他数据库降级等也可以通过这个类操作,更具体请参考官方文档。
3.2 创建数据库了解了SQLiteOpenHelper之后,便可以开始实践数据库之旅了。创建数据库代码见上边的onCreate里的创建SQL语句。该语句创建一个名为translog的表,用来记录交易日志,translog表有4列,分别是主键id,交易单号,金额和日期,注意到除了_id
设置了为integer类型(因为要设置自增长主键),其他列都没指定,执行不会报错,这是SQLite弱数据类型的好处。
db.execSQL("create table translog(_id integer primary key autoincrement, transid , amount,date)");
3.3 添加数据
往表里添加数据有两种方法,一种使用原生SQL语句,往表里添加一条交易日志SQL语句如下
db.execSQL("INSERT INTO translog (transid, amount,date)"+
"VALUES ('955162020', 100.00,20200305)");
还有第二种也稍微简单移动的办法,那就是使用SQLiteDatabase对象的提供的insert(), update(),delete(),query()方法。这些方法其实是做了一层封装,最后还是内部转换成SQL语句完成插入数据操作。
添加数据insert(String table,String nullColumnHack,ContentValues values)有3个参数,其中:
参数1 表名; 参数2 空列的默认值; 参数3 ContentValues类型数据;其中第二个参数nullColumnHack是空列的默认值,但又可以填null,我们知道,SQL不允许在不指定一个列名的情况下插入,例如***INSERT INTO translog ()VALUES ()")***是错的,如果第三个参数为空值,则会出现这种语法错误,第二个参数正是为了防止错误诞生,这样第三个参数为空值时,则使用第二个参数作为默认字段。那为什么又可以设置为null?这是在第三个参数values不为Null并且元素的个数大于0可以填的 。这个参数的思路是,如果你懒得检查第三个参数是否为空,那么填一个参数让我管,如果你填了一个null不让我管,那么你自己就要注意第三个参数不能为空。
第三个参数是ContentValues类型的数值,用法和map相似,提供了存取数据的put和get方法。注意键应该是表中存在的列名称。
下面是插入代码示例,注意到我们没给主键添加值,我们已经设置主键是自增长列,当没再ContentValues里给一个列put一个值时,相当于为该列添加一个null的值,主键自动增长,但是如果赋值了,则id设置成赋值后的值,建议让id自动增长,自己赋值不注意的话会导致id重复而报错。
//实例化常量值
ContentValues cValue = new ContentValues();
//添加交易单号
cValue.put("transid","9551620200307");
//添加消费金额
cValue.put("amount",100.00);
//添加交易日期
cValue.put("date",2020-03-07);
//调用insert()方法插入数据
db.insert("translog",null,cValue);
3.4 更新数据
更新数据当然也可用原生SQL语句然后使用execSQL() 方法可以达到目的,或者使用update()函数,函数的说明如下
int update(String table, ContentValues values, String whereClause, String[] whereArgs)
参数1 表名;
参数2 ContentValues类型数据;
参数3 WHERE表达式(String),指定需进行数据更新的行,其中?号是占位符,如果传递null将更新所有行;
参数4 填充第三个参数里的占位符,String[]数组类型;
例如想更新交易记录***9551620200307***的消费金额,可用如下代码实现
ContentValues replaceAmount = new ContentValues();
//要修改的金额
replaceAmount.put("amount","50.00");
//指定第3个参数里占位符?的内容,最后将翻译成transid=9551620200307
String[] parms=new String[] {"9551620200307"};
//执行更新
db.update("translog", replaceAmount, "transid=?", parms);
3.5 删除数据
经过熟悉增改函数后,发现函数都是有固定讨论的,删除函数delete和update函数参数很像,只不过少了要增加的参数,因为删除是破坏,破坏起来简单。
public int delete (String table, String whereClause, String[] whereArgs)
参数1 表名;
参数2 WHERE表达式(String),指定需进行删除的行,其中?号是占位符,如果传递null将删除所有行;
参数3 填充第二个参数里的占位符,String[]数组类型;
例如想删除交易记录***9551620200307***,可用如下代码实现
//指定第3个参数,也就是占位符?的内容,最后将翻译成transid=9551620200307
String[] parms=new String[] {"9551620200307"};
//执行更新
db.delete("translog", "transid=?", parms);
3.6 查询数据
查询方法放到最后,因为查询比较复杂,如果是查询全部数据还好,否则要指定各种条件。和前面的功能一样,查询也有两种方式,但要注意的是,原生查询SQL不支持db.execSQL,而是使用单独为查询设置的db.rawQuery方法,rawQuery方法两种类型,其中简单一点的原型如下:
Cursor rawQuery (String sql, String[] selectionArgs)
其中第一个参数是SQL语句,第二个参数用于填充第一个参数里的占位符。rawQuery返回Cursor,最后再通过Cursor找到想要的查询结果。示例代码如下:
Cursor cursor = db.rawQuery("select * from transid",null);
while(cursor.moveToNext()){
int id=cursor.getInt(cursor.getColumnIndex("id"));
String transid=cursor.getString(cursor.getColumnIndex("transid"));
String amount=cursor.getString(cursor.getColumnIndex("amount"));
String date=cursor.getString(cursor.getColumnIndex("date"));
}
rawQuery比较适用于简单查询,如果要设置更多条件,使用这个方法将会非常复杂。这个时候建议使用query方法,虽然参数比前面介绍的数据库增删改要多得多,但要比原生SQL查询语句可读性强。
来看其中一个query原型:
Cursor query (boolean distinct,
String table,
String[] columns,
String selection,
String[] selectionArgs,
String groupBy,
String having,
String orderBy,
String limit);
参数说明
distinct:指定是否过滤结果里的重复值,true过滤,false不过滤; table:表名 columns:指定想要查询的列的名称集; selection:WHERE之后的条件语句,可以使用占位符 selectionArgs:如果selection里有占位符,使用该参数填充 groupBy:指定分组的列名对,指定的话结果将按照列名分组 having:指定分组条件,配合groupBy使用 orderBy:指定排序的列名 limit:用于限定返回结果行数除了table必须指定,其他参数都可以设置为null,这样等同于一句简单的select * from table
。
SQLite因性能优秀占用体积小且开源被Android采用,如果Android程序需要保存大量数据,选择使用SQLite是一个明智的方案,Android针对SQLite封装了简单易用的API,其中SQLiteOpenHelper类用于创建和更新数据库,通过SQLiteOpenHelper,开发者不用纠结于创建数据库连接等繁琐的操作。在Android上,既可以选择使用原生SQL语句,也可以使用封装好的API操作,API包含了数据库的增删改查基本操作,通过使用API,不仅方便开发者,也增强了程序的可读性。不过,Android封装的API还是有点繁琐,目前已有大量好用的第三方库,可以更加方便操作数据库,这将会在后续介绍。