ActiveSQLite更简单的Swift数据库方案(SQLite.swift封装)

ActiveSQLite 更简单的Swift数据库方案。项目地址:https://github.com/KevinZhouRafael/ActiveSQLite

——————————————————————————————————

在做swift数据库的时候,选择并不多,有三种:CoreData,SQLite,Realm。很多喜欢用SQL的开发者在Objective-c中使用FMDB,而在swift中,star最多的SQLite框架就是SQLite.swift,其star数量也才4k+,但是没有更好的选择了。SQLite.swift功能很强大,但是有几点非常不方便:

1,要写Expression表达式。
2,不能自动包装成对象。

参考如下代码:

let users = Table("users")
let id = Expression("id")
let email = Expression("email")

for stmt in try! db.prepare(users.select(id, email)) {
    print("id: \(stmt[id]), email: \(stmt[email])")
}

1、在创建表,增删查改表都会用到Expression表达式,一般我们通常将表达式作为类的静态属性,这样统一管理。
2、读取stmt的数据只能显式的通过Expression表达式或者索引,这样在修改表的时候也是非常麻烦的,也很容易弄错。

为了让SQLite.swift更加好用,我写了一个框架ActiveSQLite,是对SQLite.swift的封装。主要特性:

1、自动创建表。
2、增删查改除了支持Expression表达式,还支持String和key-value的字典。
3、自动把查询结果包装为model。
4、更方便的事务和异步,模型与数据库表映射,日志,简便的数据库升级。

写的过程参考了MagicalRecordActiveRecord。易用性达到Realm的级别。

——————————————————————————————————————
具体介绍如下:

特性

  • 支持 SQLite.swift 的所有特性。
  • 自动创建表. 自动创建 id , created_at 和 updated_at 列。
  • 自动把SQL查询的数据赋值给数据库模型DBModel的属性。
  • 自定义表名和模型名之间的映射,列名和模型的属性名之间的映射。
  • 支持事务和异步。
  • 提供可扩展,链式,延迟执行的查询接口。
  • 通过属性名字符串,字典,或SQLite.swift的表达式Expression<T>查询和修改数据。
  • 日志级别

例子

执行 ActiveSQLiteTests target.

用法

import ActiveSQLite

//定义model和table
class Product:DBModel{

    var name:String!
    var price:NSNumber!
    var desc:String?
    var publish_date:NSDate?

}

//保存
let product = Product()
product.name = "iPhone 7"
product.price = NSNumber(value:599)
try! product.save()

//查询
let p = Product.findFirst("name",value:"iPhone") as! Product

//or 
let name = Expression<String>("name")
let p = Product.findAll(name == "iPhone")!.first as! Product                    
//id = 1, name = iPhone 7, price = 599, desc = nil,  publish_date = nil, created_at = 1498616987587.237, updated_at = 1498616987587.237, 

//更新
p.name = "iPad"
try! p.update()

//删除
p.delete()

开始

在你的工程的target使用ActiveSQLite, 需要首先导入 ActiveSQLite 模块.

import ActiveSQLite

连接数据库

DBConfigration.dbPath = "..."

如果你没有设置dbPath,那么默认的数据库文件是在documents目录下的ActiveSQLite.db。

支持的数据类型

ActiveSQLite<br />Swift Type SQLite.swift<br />Swift Type SQLite<br /> SQLite Type
NSNumber Int64 INTEGER
NSNumber Double REAL
String String TEXT
nil nil NULL
SQLite.Blob BLOB
NSDate Int64 INTEGER

NSNumber类型对应SQLite.swift的两种类型(Int64和Double)。NSNumber默认的映射类型是Int64。重写DBModel的doubleTypes()方法能标记属性为Double类型。

class Product:DBModel{

    var name:String!
    var price:NSNumber!
    var desc:String?
    var publish_date:NSDate?

  override func doubleTypes() -> [String]{
      return ["price"]
  }
  
}

ActiviteSQLite映射NSDate类型到SQLite.swift的Int64类型。 你可以通过查找SQLite.swift的文档Custom Types of Documentaion映射NSDate到String。

创建表

ActiveSQLite自动创建表并且添加"id", "created_at"和 "updated_at"字段。"id"字段是主键。 创建的代码类似于下面这样:

try db.run(products.create { t in      
    t.column(id, primaryKey: true)
    t.column(Expression<NSDate>("created_at"), defaultValue: NSDate(timeIntervalSince1970: 0))  
    t.column(Expression<NSDate>("updated_at"), defaultValue: NSDate(timeIntervalSince1970: 0))  
    t.column(...)  

})                             

// CREATE TABLE "Products" (
//      "id" INTEGER PRIMARY KEY NOT NULL,
//      created_at INTEGER DEFAULT (0),
//      created_at INTEGER DEFAULT (0),
//     ...
//  )
  

"created_at"和"updated_at"字段的单位是毫秒ms。

映射

你可以自定义表的名字, 列的名字,还可以设置瞬时属性不存在数据库中。

1. 映射表名

默认的表名和类名相同。

//设置表名为 "ProductTable"
override class var nameOfTable: String{
    return "ProductTable"
}

2. 映射列名

默认的列名和属性名相同。

//设置"product"属性映射的列名为"product_name"
//设置"price"属性映射的列名为"product_price"
override class func mapper() -> [String:String]{
    return ["name":"product_name","price":"product_price"];
}

3. 瞬时属性。

瞬时属性不会被存在数据库中。

override class func transientTypess() -> [String]{
    return ["isSelected"]
}

ActiveSQLite 仅仅保存三种属性类型 (String,NSNumber,NSDate)到数据库。 如果属性不是这三种类型,那么不会被存入数据库,它们被当做瞬时属性看待。

表约束

如果你要自定义列, 你仅需要实现CreateColumnsProtocol协议的createColumns方法,那么ActiveSQLite就不会自动创建列。写自己的建列语句,要注意列名和属性名必须一致,否则不能自动从查询sql封装数据库模型对象。


class Users:DBModel,CreateColumnsProtocol{
    var name:String!
    var email:String!
    var age:Int?
   
    func createColumns(t: TableBuilder) {
        t.column(Expression<NSNumber>("id"), primaryKey: true)
        t.column(Expression<String>("name"),defaultValue:"Anonymous")
        t.column(Expression<String>("email"), , check: email.like("%@%"))
    }
}

更多信息查考SQLite.swift的文档table constraints document

插入记录

有三个方法用来插入记录。

插入一条。

func insert()throws ;

插入多条。

class func insertBatch(models:[DBModel])throws ;

保存方法。

如果数据库模型对象的 id == nil,那么执行插入。如果id != nil那么执行更新语句。

func save() throws;

例如:

let u = Users()
u.name = "Kevin"
try! u.save()
                
var products = [Product]()
for i in 1 ..< 8 {
    let p = Product()
    p.name = "iPhone-\(i)"
    p.price = NSNumber(value:i)
    products.append(p)
}
                
try! Product.insertBatch(models: products)

更多信息可以看ActiveSQLite的源码和例子, 也可以查阅SQLite.swift的文档Inserting Rows document

更新记录

有三种更新策略。

1. 通过改属性值

首先修改属性的值,然后执行save() 或者 update() 或者 updateBatch()。

p.name = "zhoukai"
p.save()

2. 通过属性名字符串和属性值

//更新一条
u.update("name",value:"3ds")
u.update(["name":"3ds","price":NSNumber(value:199)])


//更新多条
Product.update(["name": "3ds","price":NSNumber(value:199)], where: ["id": NSNumber(1)])

2. 通过SQLite.swift的Setter

//更新一条记录
p.update([Product.price <- NSNumber(value:199))

//更新多条
Product.update([Product.price <- NSNumber(value:199), where: Product.name == "3ds")

了解更多请看ActiveSQLite的源码和例子, 查看SQLite.swift的文档Updating Rows document , Setters document

查询记录

使用findFirst方法查询一条记录,使用findAll方法查询多条记录。

方法名前缀是"find"的是类方法,这种方法一次性查询出结果。

1.通过属性名字符串和属性值查询

let p = Product.findFirst("name",value:"iWatch") as! Product

let ps = Product.findAll("name",value:"iWatch",orders:["price",false]) as! [Product]

2.通过SQLite.swift的Expression查询

let id = Expression<NSNumber>("id")
let name = Expression<String>("name")

let arr = Product.findAll(name == "iWatch") as! Array<Product>

let ps = Product.findAll(id > NSNumber(value:100), orders: [Product.id.asc]) as! [Product]

链式查询

链式查询方法是属性方法。

let products = Product().where(Expression<NSNumber>("code") > 3)
                                .order(Product.code)
                                .limit(5)
                                .run() as! [Product]

不要忘记执行run()。

更多复杂的查询参考ActiveSQLite的源码和例子。和SQLite.swift的文档Building Complex Queries

表达式Expression

SQLite.swift再更新update和查询select操作中,使用表达式Expression转换成SQL的'where'判断,。更多复杂的表达式用法,参考文档filtering-rows

删除记录

//1. 删除一条
try? product.delete()

//2. 删除所有
try? Product.deleteAll()

//3. 通过表达式Expression链式删除。
try? Product().where(Expression<NSNumber>("code") > 3)
                                .runDelete()

事务

建议把所有的insert,update,delete操作和alter表的代码全部放在ActiveSQLite.save代码块中。一个块中的sql操作在同一个事务当中。

 ActiveSQLite.save({ 

                var products = [Product]()
                for i in 0 ..< 3 {
                    let p = Product()
                    p.name = "iPhone-\(i)"
                    p.price = NSNumber(value:i)
                    products.append(p)
                }
                try Product.insertBatch(models: products)
                

                let u = Users()
                u.name = "Kevin"
                try u.save()
                

            }, completion: { (error) in
                
                if error != nil {
                    debugPrint("transtion fails \(error)")
                }else{
                    debugPrint("transtion success")
                }

            })

异步

ActiveSQLite.saveAsync是一个异步的操作,当然代码块中的sql也在同一个事务当中。

 ActiveSQLite.saveAsync({ 
            .......

            }, completion: { (error) in
                ......
            })

改变表结构

重命名表和添加列

第1步. 用新的表名做映射,添加新的属性。

class Product{
    var name:String!
    
    var newColumn:String!
    override class var nameOfTable: String{
        return "newTableName"
    }
    
}

Step 2. 当数据库版本改变时候,执行修改表名和添加列sql,并放在同一个事务中。

let db = DBConnection.sharedConnection.db
            if db.userVersion == 0 {
                ActiveSQLite.saveAsync({
                    try Product.renameTable(oldName:"oldTableName",newName:"newTableName")
                    try Product.addColumn(["newColumn"])
                    
                }, completion: { (error) in
                    if error == nil {
                    
                        db.userVersion = 1
                    }
                })
                
            }             

更多SQLite.swift的修改表信息参看 Altering the Schema

索引

    let name = Expression<String>("name")
    Product.createIndex(name)
    Product.dropIndex(name)

更多信息查看 Indexes of SQLite.swift Document

删除表

Product.dropTable()

日志

有四种日志级别,分别是: debug,info,warn,error。
默认的日志级别是info。像这样来设置日志级别:

//1. 设置日志级别
DBConfigration.logLevel = .debug

//2. 设置数据库路径
DBConfigration.dbPath = "..."

保证首先设置日志级别,后设置数据库路径。

硬件需求

  • iOS 8.0+
  • Xcode 8.3.2
  • Swift 3

安装

Cocoapods

再Podfile文件中添加:

pod "ActiveSQLite"

作者

Rafael Zhou

License

ActiveSQLite is available under the MIT license. See the LICENSE file for more info.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335

推荐阅读更多精彩内容