本章主要讲解Kotlin中如何使用原生的Android数据库进行数据的存储,主要通过示例引导读者进行主要开发,后期维护需要更深入理解:
android的数据库开发中主要是针对数据进行CURD操作。针对该操作,早期在培训机构里主要讲解的顺序依次是:
1.创建数据的Domain层,也就是需要保存数据的模型。
2.创建数据库文件 数据库表创建与更新的帮助类
3.创建数据库表字段类
4.创建执行CURD的Dao层(该层主要对外实现CURD操作)
当我们完成上面的步骤后,就可以创建具体的业务客户端类来调用上面服务端代码。
现在,我们开始来开发Kotlin版的数据库,由于kotlin本身的开发没提供对数据库的再次封装,我们使用了kotlin的扩展开发包anko.其官方是这样解释该开发包的:
Anko 是一个可以让安卓应用开发更加高效和简单的依赖库,它使用了更简介,可读性更高的代码方式来开发安卓应用程序,Anko包含了如下部分:
Anko Commons: 操作Intents,dialogs和日志等等的轻量级依赖包
Anko Layouts: 一个更加快速 安全的安卓动态布局开发包(目测目前只需要Adroid Extansions开发包就可以替代该包)
Anko SQLite: 数据库操作包
Anko Coroutines: utilities based on the kotlinx.coroutines library.
这里我们主要讲解SQLite的配置,本人使用Android Studio3.0预览版亲测有效:
1.在Project的gradle文件中添加:
buildscript {
ext.kotlin_version = '1.1.2-4'
}
2.在app的gradle文件中添加:
apply plugin: 'com.android.application'
dependencies {
...
compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
}
1.在Project的gradle文件中添加:
buildscript {
...
ext.ANKO_VERSION = '0.10.0'
}
2.在app的gradle文件中添加:
apply plugin: 'com.android.application'
dependencies {
...
compile("org.jetbrains.anko:anko:0.10.0") {
exclude group: 'com.google.android', module: 'android'
}
compile "org.jetbrains.anko:anko-sqlite:$ANKO_VERSION"
}
这里假如我们模拟京东的一个商品数据模型。假如我想保存一个商品队列到数据库中,那么商品的模型应该如下;
data class Product(var id: Long,
var name: String,
var iconUrl: String = "",
var price: Double)
这里声明每个商品都有id 商品名称 商品的图片地址和价格。data关键字添加后,会将该模型作为数据实体类,并自动生成toString等方法,以便打印出商品的各个属性。
在Android原生开发过程中,如果想创建数据库文件,就必须使用到帮助类SqliteOpenHelper.anko为我们的SqliteOpenHelper再次封装了一层,叫ManagedSQLiteOpenHelper。
class JDMallOpenHelper(ctx: Context = JdApplication.sInstance) :
ManagedSQLiteOpenHelper(ctx, DB_NAME, null, DB_VERSION) {
companion object {
var DB_NAME = "jdmall.db"
var DB_VERSION = 1
var PRODUCT_TABLE="shopcar_product"
}
/**
* 创建数据库表
* */
override fun onCreate(db: SQLiteDatabase) {
db.createTable(PRODUCT_TABLE,true,
ProductTable.id to INTEGER + PRIMARY_KEY + AUTOINCREMENT,
ProductTable.name to TEXT,
ProductTable.iconUrl to TEXT,
ProductTable.price to REAL
)
}
/**
* 更新数据库表
* */
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.dropTable(PRODUCT_TABLE,true)
onCreate(db)
}
}
上面的代码中我为了获取Context实例(JdApplication.sInstance),我创建了应用的Context以防内存泄漏,具体的代码如下,当然Application是需要配置在AndroidMenifest.xml中的:
class JdApplication():Application(){
companion object{
var sInstance:JdApplication by Delegates.notNull()
}
override fun onCreate() {
super.onCreate()
sInstance=this
}
}
上面的代码足以让我们创建数据库文件和表内容,包括更新数据库表,在创建数据库表的时候,我们也使用anko提供给我们的函数createTable(…):
/**
* tableName 指定表明
* ifNotExists true表示如果表不存在则直接创建
* columns 表示创建表字段的队列
**/
fun SQLiteDatabase.createTable(tableName: String, ifNotExists: Boolean = false, vararg columns: Pair) {
...
}
columns主要是一个个的键值对,key表示字段名,value则是该字段的类型,该类型是由接口SqlType提供,它包含了类型如下:
val NULL: SqlType = SqlTypeImpl("NULL")
val INTEGER: SqlType = SqlTypeImpl("INTEGER")
val REAL: SqlType = SqlTypeImpl("REAL")
val TEXT: SqlType = SqlTypeImpl("TEXT")
val BLOB: SqlType = SqlTypeImpl("BLOB")
在类型后面我们可以通过+来扩展,如让某个字段作为主键,保证唯一性等等,可添加的类型如下:
val PRIMARY_KEY: SqlTypeModifier = SqlTypeModifierImpl("PRIMARY KEY")
val NOT_NULL: SqlTypeModifier = SqlTypeModifierImpl("NOT NULL")
val AUTOINCREMENT: SqlTypeModifier = SqlTypeModifierImpl("AUTOINCREMENT")
val UNIQUE: SqlTypeModifier = SqlTypeModifierImpl("UNIQUE")
更新数据库的代码中,我们调用dropTable(…):
/**
* tableName 指定表明
* ifExists true表示如果表存在则直接销毁
**/
fun SQLiteDatabase.dropTable(tableName: String, ifExists: Boolean = false) {
...
}
这个主要是上面创建过程中所需要的一些字段,没什么好解释:
/**
* Created by lean on 2017/6/1.
* 构建数据库表字段
*/
object ProductTable {
var id="_id"
var name = "NAME"
var iconUrl = "ICONURL"
var price = "PRICE"
}
Dao的代码主要提供了2个操作,一是保存商品队列到数据库,二是取出商品队列,下面比较陌生的方法本人已经做了标注 如难点n,后面会进行单独的讲解:
class ProductDao(var mContext: Context) {
var mJdMallHelper: JDMallOpenHelper
init {
mJdMallHelper = JDMallOpenHelper(mContext)
}
private fun converDomain2Map(data: Product): MutableMap {
var result = mutableMapOf()
//难点2
with(data){
result[ProductTable.id] = "$id"
result[ProductTable.name] = name
result[ProductTable.iconUrl] = iconUrl
result[ProductTable.price] = "$price"
}
return result
}
/**
* 保存数据到数据库中
* */
fun saveProducts(datas: List) {
//难点1
mJdMallHelper.use {
//难点3
datas.forEach({
val varargs = converDomain2Map(it).map { Pair(it.key, it.value) }.toTypedArray()
insert(JDMallOpenHelper.PRODUCT_TABLE, *varargs)
})
}
}
/**
* @return List? 返回的数据可能为null
* */
fun queryProducts(): List? {
var productList: List? = null
mJdMallHelper.use {
val selectQueryBuilder = select(
JDMallOpenHelper.PRODUCT_TABLE,
ProductTable.id,
ProductTable.name,
ProductTable.iconUrl,
ProductTable.price)
//.whereArgs("") 设置查询条件
//难点4
productList = selectQueryBuilder.parseList(object : MapRowParser {
override fun parseRow(columns: Map): Product {
var _id = columns[ProductTable.id] as Long
var name = columns[ProductTable.name] as String
var iconUrl = columns[ProductTable.iconUrl] as String
var price = columns[ProductTable.price] as Double
return Product(_id, name, iconUrl, price)
}
})
}
return productList
}
}
我们知道要操作数据库如果严格点来说应该通过helper对象获取一个可读/可写的数据库,并在数据库操作完毕后关闭数据库,但是use这个函数帮我们做了这一切,如下源码:
fun use(f: SQLiteDatabase.() -> T): T {
try {
return openDatabase().f()
} finally {
closeDatabase()
}
}
因此,我们可在我们传进去的匿名内部类中直接操作数据库并返回我们想要的数据。
首先知道该方法主要是将一个对象里面的所有属性拿出来,并放到一个可变的Map中,也就我们要不断的调用bean.xxx来获取属性并传给map,使用了map后,在{ }内就可以直接拿到data的属性,减少了大部分的工作:
with(data){
result[ProductTable.id] = "$id"
result[ProductTable.name] = name
result[ProductTable.iconUrl] = iconUrl
result[ProductTable.price] = "$price"
}
datas.forEach({
val varargs = converDomain2Map(it).map { Pair(it.key, it.value) }.toTypedArray()
insert(JDMallOpenHelper.PRODUCT_TABLE, *varargs)
})
forEach表示我们想通过增强型for循环遍历一个队列,里面的每个元素用it这个单词来表示。
使用insert函数的第二个参数必须是vararg values: Pair 类型,也就是一系列的key,value键值对。所以我们必须先将对象转换成Map(converDomain2Map(it)),再将里面的键值对转换成一个队列,记住最后我们穿进去的数据前面有个*号。
如果想做查询,只要调用select()即可,它根据拼接返回selectQueryBuilder对象。
如果返回的是一个对象,则直接调用parseSingle()即可,如果返回的是一个队列,直接调用parseList()。
不管是哪一种 都需要提供转换的规则,这个规则由接口RowParser/MapRowParser来实现。
productList = selectQueryBuilder.parseList(object : MapRowParser {
override fun parseRow(columns: Map): Product {
...
return Product(_id, name, iconUrl, price)
}
})