SQLite数据库存储
SQLite是一种轻量级的关系型数据库,它的运算速度非常快,占用资源少,通常只需要几百KB的内存就足够了,因此特别适合在移动设备上使用。同时,它支持标准的SQL语法,遵循数据库的ACID事务,上手较为简单。
定义架构和协定
SQL 数据库的主要原则之一是架构,即数据库组织方式的正式声明。架构反映在您用于创建数据库的 SQL 语句中。您可能会发现创建伴随类(称为协定类)很有用,该类以系统化、自记录的方式明确指定了架构的布局。
协定类是定义 URI、表和列名称的常量的容器。通过协定类,您可以在同一软件包的所有其他类中使用相同的常量。这样一来,您就可以在一个位置更改列名称并将其传播到整个代码中。
组织协定类的一种良好方法是将对整个数据库而言具有全局性的定义放入类的根级别。然后,为每个表创建一个内部类。每个内部类都枚举相应表的列。
注意:通过实现 BaseColumns 接口,您的内部类可以继承名为 _ID 的主键字段,CursorAdapter 等某些 Android 类需要内部类拥有该字段。
object FeedReaderContract {
// Table contents are grouped together in an anonymous object.
object FeedEntry : BaseColumns {
const val TABLE_NAME = "entry"
const val COLUMN_NAME_TITLE = "title"
const val COLUMN_NAME_SUBTITLE = "subtitle"
}
}
创建数据库
可以使用标准的sql语言来创建数据库
private const val SQL_CREATE_ENTRIES =
"CREATE TABLE ${FeedEntry.TABLE_NAME} (" +
"${BaseColumns._ID} INTEGER PRIMARY KEY," +
"${FeedEntry.COLUMN_NAME_TITLE} TEXT," +
"${FeedEntry.COLUMN_NAME_SUBTITLE} TEXT)"
private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS ${FeedEntry.TABLE_NAME}"
安卓提供了一个SQLiteOpenHelper帮助类。它是一个抽象类,需要我们创建一个自己的类去继承它。
它有两个抽象方法:onCreate() 和 onUpgrade()
class TestDatabaseHelper(val context: Context, name: String, version: Int) :
SQLiteOpenHelper(context, name, null, version) {
private val createTest = "create table Test ( " +
"id integer primary key autoincrement, " +
"text text" +
")"
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(createTest)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
db?.execSQL("drop table if exists Test")
onCreate(db)
}
}
SQLite的数据类型很简单:integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型
/*
创建SQLite//java
*/
TestDatabaseHelper testDatabaseHelper = new TestDatabaseHelper(this, "Test.db", SQLITE_VERSION);
testDatabaseHelper.getWritableDatabase();
/*
创建SQLite//kotlin
*/
val dbHelper = TestDatabaseHelper(this, "Test.db", SQLITE_VERSION)
dbHelper.writableDatabase
我们用onCreate() 方法构建了一个TestDatabaseHelper 对象,并且通过构造函数的参数将数据库指定命名为Test.db ,版本号指定为1。
SQLiteOpenHelper 类包含一组用于管理数据库的实用 API。当您使用此类获取对数据库的引用时,系统仅在需要时才执行可能需要长时间运行的数据库创建和更新操作,而不是在应用启动期间执行。仅需调用 getWritableDatabase() 或 getReadableDatabase() 即可。
注意:由于这些操作可能会长时间运行,因此请务必在后台线程中调用 getWritableDatabase() 或 getReadableDatabase()。
升级数据库
有的时候在 TestDatabaseHelper 中写了两条新建表的语句并在 onCreate() 中进行执行,但执行了 onCreate() 方法并没创建第二张表,是因为我们已经存在了第一张表了,此时就不会再执行 onCreate() 方法,新加入的表就无法被创建。
这个时候只有两个解决办法,一个是将程序卸载,再重新安装。因为卸载程序后就会删除这个程序所带的所有的 SQLite 数据库。但是这种卸载重装的方法显然不可取。我们只需要在 TestDatabaseHelper 中重写 onUpgrade() 方法就可以解决这个问题。
class TestDatabaseHelper(val context: Context, name: String, version: Int) :
SQLiteOpenHelper(context, name, null, version) {
private val createTest = "create table Test ( " +
"id integer primary key autoincrement, " +
"text text" +
")"
private val createCategory = "create table Category ( " +
"id integer primary key autoincrement, " +
"text text" +
")"
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(createTest)
db?.execSQL(createCategory)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
db?.execSQL("drop table if exists Test")
db?.execSQL("drop table if exists Category")
onCreate(db)
}
}
让我们模拟一个升级 SQLite 的案例,版本1 的程序要求数据库创建一张 Table1 的表。
class TestDatabaseHelper(val context: Context, name: String, version: Int) :
SQLiteOpenHelper(context, name, null, version) {
private val createTable1 = "create table Table1 ( " +
"id integer primary key autoincrement, " +
"text text" +
")"
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(createTable1)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
}
}
几个星期后,需要向数据库添加一张 Table2 的表。
class TestDatabaseHelper(val context: Context, name: String, version: Int) :
SQLiteOpenHelper(context, name, null, version) {
private val createTable1 = "create table Table1 ( " +
"id integer primary key autoincrement, " +
"text text" +
")"
private val createTable2 = "create table Table2 ( " +
"id integer primary key autoincrement, " +
"text text" +
")"
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(createTable1)
db?.execSQL(createTable2)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
if (oldVersion <= 1) {
db?.execSQL(createTable2)
}
}
}
又过了一段时间,需要给 Table1 增加一个 name 字段。
class TestDatabaseHelper(val context: Context, name: String, version: Int) :
SQLiteOpenHelper(context, name, null, version) {
private val createTable1 = "create table Table1 ( " +
"id integer primary key autoincrement, " +
"text text, " +
"name text" +
")"
private val createTable2 = "create table Table2 ( " +
"id integer primary key autoincrement, " +
"text text" +
")"
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(createTable1)
db?.execSQL(createTable2)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
if (oldVersion <= 1) {
db?.execSQL(createTable2)
}
if (oldVersion <= 2) {
db?.execSQL("alter table Table1 add column name text")
}
}
}
注意:每当升级一个数据库版本的时候,onUpgrade() 方法里都要写一个相应的 if 判断语句。这是为了保证 App 在跨版本升级的时候,每一次的数据库修改都能被全部执行。
添加数据
通过使用 ContentValues 添加数据
// Gets the data repository in write mode
val db = dbHelper.writableDatabase
// Create a new map of values, where column names are the keys
val values = ContentValues().apply {
put(FeedEntry.COLUMN_NAME_TITLE, title)
put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle)
}
// Insert the new row, returning the primary key value of the new row
val newRowId = db?.insert(FeedEntry.TABLE_NAME, null, values)
SQLiteDatabase 提供了一个 insert() 方法。它接收3个参数:第一个参数是表名。第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 null。第三个参数是一个 ContentValues 对象,它提供了一系列的 put() 方法重载,用于向 ContentValues 中添加数据。
更新数据
val db = dbHelper.writableDatabase
// New value for one column
val title = "MyNewTitle"
val values = ContentValues().apply {
put(FeedEntry.COLUMN_NAME_TITLE, title)
}
// Which row to update, based on the title
val selection = "${FeedEntry.COLUMN_NAME_TITLE} LIKE ?"
val selectionArgs = arrayOf("MyOldTitle")
val count = db.update(
FeedEntry.TABLE_NAME,
values,
selection,
selectionArgs)
SQLiteDatabase 提供了一个 update() 方法。它接收4个参数:第一个参数是表名。第二个参数是构建的 ContentValues 对象。第三个参数对应的是 SQL 语句的 where 部分,表示更新所有满足 selection 条件的行。?是一个占位符,可以通过第四个参数提供的一个数组为第三个参数的每个占位符指定相应的内容。
删除数据
// Define 'where' part of query.
val selection = "${FeedEntry.COLUMN_NAME_TITLE} LIKE ?"
// Specify arguments in placeholder order.
val selectionArgs = arrayOf("MyTitle")
// Issue SQL statement.
val deletedRows = db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs)
SQLiteDatabase 提供了一个 delete() 方法。它接收3个参数:第一个参数是表名。第二个参数和第三个参数同上更新数据参数。
查询数据
val db = dbHelper.readableDatabase
// Define a projection that specifies which columns from the database
// you will actually use after this query.
val projection = arrayOf(BaseColumns._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_SUBTITLE)
// Filter results WHERE "title" = 'My Title'
val selection = "${FeedEntry.COLUMN_NAME_TITLE} = ?"
val selectionArgs = arrayOf("My Title")
// How you want the results sorted in the resulting Cursor
val sortOrder = "${FeedEntry.COLUMN_NAME_SUBTITLE} DESC"
val cursor = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The array of columns to return (pass null to get all)
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
)
SQLiteDatabase 提供了一个 query() 方法用于对数据进行查询。它总共要传入7个参数:
query() 方法参数 | 对应 SQL 部分 | 描述 |
---|---|---|
table | from table_name | 指定查询的表名 |
columns | select column1, column2 | 指定查询的列名 |
selection | where column = value | 指定where的约束条件 |
selectionArgs | - | 为where中的占位符提供具体的值 |
groupBy | group by column | 指定需要group by的列 |
having | having column = value | 对group by后的结果进一步约束 |
orderBy | order by column1, column2 | 指定查询结果的排序方式 |
使用SQL操作数据库
虽然 Android 已经给我们提供了很多好用的 API,但是总会有人还是习惯与使用 SQL 语句来进行操作。
// 添加数据
db?.execSQL("insert into Test (id, content) values(?, ?)", arrayOf(1, "test"))
// 更新数据
db?.execSQL("update Test set content = ? where id = ?", arrayOf("test1", 1))
// 删除数据
db?.execSQL("delete from Test where id = ?", arrayOf(1))
// 查询数据
val cursor = db?.rawQuery("select * from Test", null)
注意:除了查询数据的时候调用的是 SQLiteDatabase 的 rawQuery() 方法,其他操作都是调用的 execSQL() 方法
使用事务
db?.beginTransaction()
try {
db?.delete("Test", null, null)
if (true) {
// 手动抛出一个异常
throw NullPointerException()
}
val values = ContentValues().apply {
put("id", 1)
put("content", "hello world")
}
db?.insert("Test", null, values)
db?.setTransactionSuccessful()
} catch (e: Exception) {
e.printStackTrace()
} finally {
db?.endTransaction()
}
这就是 Android 中事务的标准用法,首先调用 SQLiteDatabase 的 beginTransaction() 方法开启一个事务,然后在一个 异常捕获的代码块中执行具体的数据库操作,当所有的操作都完成之后,调用 setTransactionSuccessful() 表示事务已经执行成功了,最后在 finally 代码块中调用 endTransaction() 结束事务。
持久化数据库连接
由于在数据库关闭时调用 getWritableDatabase() 和 getReadableDatabase() 的开销很大,因此只要您可能需要访问它,就应该让数据库连接保持打开状态。通常,最好在 Activity 调用 onDestroy() 时关闭数据库。
override fun onDestroy() {
dbHelper.close()
super.onDestroy()
}
第一行代码Android(第三版 ) 郭霖 著 人民邮电出版社
android开发者指南:https://developer.android.google.cn