前言
在开发中,经常需要写sql语句。但有个很严重的问题, sql 语句是 字符串型的,书写容易出错。 所以要封装sql语句。 我在用代码一步一步实现自己的 ios 架构 中进行了封装,查看这个源文件SqlStatement.swift
封装的两个目标:
- 不要手写字符串
- 使用起来简单
有两种方案:
一种使用简单,但功能有限
一种使用复杂,但功能强大
这里先看第一种方案
方案一
分析 sql 语句
SELECT LastName,FirstName FROM Persons
SELECT * FROM Persons WHERE City='Beijing'
SELECT * FROM Persons WHERE firstname='Thomas' OR lastname='Carter'
SELECT Company, OrderNumber FROM Orders ORDER BY Company
SELECT * FROM Persons WHERE City LIKE 'N%'
INSERT INTO Persons VALUES ('Gates', 'Bill', 'Xuanwumen 10', 'Beijing')
UPDATE Person SET FirstName = 'Fred' WHERE LastName = 'Wilson'
DELETE FROM Person WHERE LastName = 'Wilson'
Sql语句有四种主要的类型,它们有一些固定的属性:表名、列名、where条件,order,group等,固定格式。
用类来封装Sql语句,面向对象的思想。
- 我创建了一下几个类。 分别对应 增删改查Sql 基类 四个子类 SqlInsert SqlUpdate SqlDelete,SqlSelect为了避免 sql 注入,使用占位符。FMDB 给了两种方式 ? :colum 这选择用后者,这两种方式效果一样。一开始使用的是 ? 后面发现,? 的对应关系不好实现。就改为 冒号的方式。
- 链式调用比较自然。只有将方法返回自己就可以实现链式调用。
使用
理解了这些,其实代码很好实现 代码我放在最后。先看一下如何使用:
比如我想实现 SELECT * FROM db.Persons WHERE City='Beijing'
let sql = Sql.select("db").table("Persons").whereStatement("City='Beijing'").build()
print(sql)
# SELECT * FROM Persons WHERE City='Beijing'
因为我这里用的是多数据库,所以指定了db
。
当然对于where有多种写法,这个看代码就明白了。
原理
链式方法提供必要属性
build() 做属性字符串拼接的工作,组装成标准sql字符串
优缺点
- 使用简单,基本不用理解 sql 语句,链式调用的顺序并不重要,build()是按照标准sql语句生成的
- 能应对90%的场景
- 功能简单,无法实现复杂的 sql 语句
- update 时 where 语句的参数 Key 不能相同,不然有bug。
方案二
将Sql语句的每一个声明都对应一个属性。设置一个全局字符串,每调用一个方法就拼接一次字符串。这样就和sql语句一样强大。但这样使用达到了目标1。
方案三
你有什么好方案,望赐教!
方案一源码:
import Foundation
class Sql {
fileprivate let dbName_: String
private(set) var table_: String? //select 可能是有多个table 所以 optional
private(set) var colums_: [String]?
/// 目前这三个属性where 只能同时满足一个
private(set) var where_: String?
private(set) var andWhere_: [String]?
private(set) var orWhere_: [String]?
lazy var realTable: String = {
return "\(dbName_).\(table_!)"
}()
init(_ dbName: String) {
dbName_ = dbName
}
// @discardableResult
static func insert(dbName: String) -> SqlInsert {
return SqlInsert(dbName)
}
static func update(dbName: String) -> SqlUpdate {
return SqlUpdate(dbName)
}
static func delete(dbName: String) -> SqlDelete {
return SqlDelete(dbName)
}
static func select(dbName: String) -> SqlSelect {
return SqlSelect(dbName)
}
func table(_ table: String) -> Self {
table_ = table
return self;
}
func colums(_ colums: [String]) -> Self {
colums_ = colums
return self
}
/// 目前这三个 where 只能同时满足一个
func whereStatement(_ condition: String) -> Self {
where_ = condition
return self
}
func andWhere(_ andWhere: [String]) -> Self {
andWhere_ = andWhere
return self
}
func orWhere(_ orWhere: [String]) -> Self {
orWhere_ = orWhere
return self
}
class func buildWhere() -> String {
return ""
}
func buildWhere() -> String {
var sql :String = String()
guard let wh = where_ else {
if let andWhere = andWhere_ {
sql.append(" where 1 = 1 ")
_ = andWhere.map {
let line = " AND \($0) = :\($0) "
sql.append(line)
}
}
if let orWhere = orWhere_ {
if andWhere_ != nil {
} else {
sql.append(" where 1 = 2 ")
}
_ = orWhere.map {
let line = " OR \($0) = :\($0) "
sql.append(line)
}
}
return sql
}
return sql.appending(" \(wh)")
}
func build() -> String {
return ""
}
}
final class SqlSelect: Sql {
private(set) var tables_: [String]?
private(set) var orderBy_: [(String,String)]?
private(set) var groupBy_: [String]?
private(set) var limitOffset: Int?
private(set) var limitCount: Int?
override lazy var realTable: String = {
if let ts = tables_ {
var rt: String = ""
tables_.map {
rt.append(contentsOf: "\(dbName_).\($0) ")
}
return rt
} else {
return "\(dbName_).\(table_!)"
}
}()
func orderBy(_ orderBy: [(String,String)]) -> Self {
orderBy_ = orderBy
return self
}
func groupBy(_ groupBy: [String]) -> Self {
groupBy_ = groupBy
return self
}
func limit(_ offset: Int, _ count: Int) -> Self {
limitOffset = offset
limitCount = count
return self
}
override func build() -> String {
var tbs = "*"
if let cls = colums_ {
tbs = cls.joined(separator: ",")
}
var sql = "SELECT \(tbs) FROM \(realTable) "
sql.append(buildWhere())
//注:GROUP BY 子句使用时必须放在 WHERE 子句中的条件之后,必须放在 ORDER BY 子句之前。
if let gb = groupBy_ {
sql.append(contentsOf: " Group BY \(gb.joined(separator: ","))")
}
if let ob = orderBy_ {
var tmpStr = ""
_ = ob.map { kv in
tmpStr.append(contentsOf: "\(kv.0) \(kv.1)")
}
sql.append(contentsOf: " ORDER BY \(tmpStr)")
}
if let offset = limitCount ,let cnt = limitCount {
sql.append(contentsOf: " LIMIT \(offset) OFFSET \(cnt)")
}
return sql
}
}
final class SqlInsert: Sql {
override func build() -> String {
var sql = "INSERT INTO \(realTable)"
if let cls = colums_ {
var cnames = [String]()
var values = [String]()
_ = cls.map {
cnames.append($0)
values.append(":\($0)")
}
sql.append("(")
sql.append(cnames.joined(separator: ","))
sql.append(") values (")
sql.append(values.joined(separator: ","))
sql.append(")")
}
sql.append(buildWhere())
return sql;
}
}
final class SqlUpdate: Sql {
override func build() -> String {
var sql = "UPDATE \(realTable) SET "
if let cls = colums_ {
_ = cls.map {
sql.append("\($0) = :\($0) ")
}
}
sql.append(buildWhere())
return sql;
}
}
final class SqlDelete: Sql {
override func build() -> String {
var sql = "DELETE FROM \(realTable) "
sql.append(buildWhere())
return sql;
}
}
补充
WCDB 腾讯出品,更优。