SQLite
SQLite是一款輕型的嵌入式數據庫,它占用資源非常的低,在嵌入式設備中,可能只需要幾百K的內存就夠了。同時它的處理速度也是非常快速的。
- 因為在iOS中SQLite3依賴于系統提供的
libsqlite.tbd
這個類庫,所以在使用之前首先我們需要導入這個類庫; - 打開數據庫,打開數據庫成功之后進行創表操作;
// 數據庫在沙盒中的存放路徑;
NSString *filename = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"shops.db"];
//1.0 打開數據庫;
/* 參數一:數據庫文件存放路徑;如果數據庫文件不存在, 系統會自動創建文件并且自動初始化數據庫
參數二:數據庫實例對象;
*/
if (sqlite3_open(filename.UTF8String, &_db) == SQLITE_OK) {
const char *sql = "CREATE TABLE IF NOT EXISTS t_shop (id integer PRIMARY KEY, name text NOT NULL, price real);";
//2.0 執行操作;
char *errmsg = NULL;
/* 參數一:數據庫實例對象;
參數二:執行的SQL語句;
*/
if (sqlite3_exec(self.db, sql, NULL, NULL, &errmsg)!= SQLITE_OK) {
NSLog(@"創表失敗--%s", errmsg);
}
} else {
NSLog(@"打開數據庫失敗");
}
在上面的代碼中,我們做了兩個操作,首先打開數據庫我們使用了sqlite3_open
這個方法,該方法根據文件路徑打開數據庫,如果不存在,則會創建一個新的數據庫。如果result等于常量SQLITE_OK,則表示成功打開數據庫。該方法有兩個參數,如下:
int sqlite3_open(
const char *filename, // 數據庫的文件路徑
sqlite3 **ppDb // 數據庫實例
);
其次,我們使用sqlite3_exec
方法來執行建表操作。該方法適合于查詢結果集之外的任何操作。
2.執行任何SQL語句
int sqlite3_exec(
sqlite3*, // 一個打開的數據庫實例
const char *sql, // 需要執行的SQL語句
int (*callback)(void*,int,char**,char**), // SQL語句執行完畢后的回調
void *, // 回調函數的第1個參數
char **errmsg // 錯誤信息
);
- 執行查詢操作;
NSString * sql=[NSString stringWithFormat:@"SELECT name,price FROM %@;",tableName];
// sqlite3_stmt實例,用來獲得數據庫數據
sqlite3_stmt *stmt = NULL;
// 預準備,其實就是分析SQL語句的合法性
if (sqlite3_prepare_v2(self.db, sql.UTF8String, -1, &stmt, NULL) == SQLITE_OK) {
// 返回SQLITE_ROW代表成功取出一條數據
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char *name = (const char *)sqlite3_column_text(stmt, 0);
const char *price = (const char *)sqlite3_column_text(stmt, 1);
}
}
上面用到了兩個方法:
3.檢查SQL語句的合法性(查詢前的準備)
int sqlite3_prepare_v2(
sqlite3 *db, // 數據庫實例
const char *zSql, // 需要檢查的SQL語句
int nByte, // SQL語句的最大字節長度,-1代表系統會幫我們自動計算
sqlite3_stmt **ppStmt, // sqlite3_stmt實例,用來獲得數據庫數據
const char **pzTail
);
4.查詢一行數據
int sqlite3_step(sqlite3_stmt*); // 如果查詢到一行數據,就會返回SQLITE_ROW
查詢分三個階段:
準備階段:sqlite3_prepare_v2()
執行階段:sqlte3_step()
終止階段:sqlite3_finalize()
- 使用約束變量
sqlite3_bind
是用來給sqlite3_stmt *pStmt語句增加值的,對于不同類型的參數要選用不同的函數。
例如,要執行帶兩個約束變量的插入操作,第一個變量是int類型,第二個是C字符串:
char *sql = "insert into oneTable values (?, ?);";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(database, sql, -1, &stmt, nil) == SQLITE_OK) {
sqlite3_bind_int(stmt, 1, 235);
sqlite3_bind_text(stmt, 2, "valueString", -1, NULL);
}
if (sqlite3_step(stmt) != SQLITE_DONE)
NSLog(@"Something is Wrong!");
sqlite3_finalize(stmt);
這里,sqlite3_bind_int(stmt, 1, 235);
有三個參數:
第一個是sqlite3_stmt
類型的變量
第二個是所約束變量的標簽index,(從1開始)
第三個參數是要加的值
有一些函數多出兩個變量,例如
sqlite3_bind_text(stmt, 2, "valueString", -1, NULL);
第四個參數代表第三個參數中需要傳遞的長度。對于C字符串來說,-1表示傳遞全部字符串。
第五個參數是一個回調函數,比如執行后做內存清除工作。
- 利用stmt獲得某一字段的值(字段的下標從0開始)
double sqlite3_column_double(sqlite3_stmt*, int iCol); // 浮點數據
int sqlite3_column_int(sqlite3_stmt*, int iCol); // 整型數據
sqlite3_int64 sqlite3_column_int64(sqlite3_stmt*, int iCol); // 長整型數據
const void *sqlite3_column_blob(sqlite3_stmt*, int iCol); // 二進制文本數據
const unsigned char *sqlite3_column_text(sqlite3_stmt*, int iCol); // 字符串數據
因為獲取某一列的值我們不知道具體的類型,所以我們可以遍歷字段,然后根據類型自動匹配:
//存放最終的返回結果;
NSMutableArray * muArray=[NSMutableArray array];
//2.0 執行預處理 獲取結果,如果下一行有記錄,就會自動返回SQLITE_ROW,同時會自動將指針移動到下一行;
while (sqlite3_step(ppStmt) == SQLITE_ROW) {
//存放當前行的數據;
NSMutableDictionary * mnDic=[NSMutableDictionary dictionary];
[muArray addObject:mnDic];
//獲取當前行 中 列的個數;
int columnCount= sqlite3_column_count(ppStmt);
for(int i=0;i<columnCount;i++ )
{
//獲取列名稱;
NSString * columnName=[NSString stringWithUTF8String:sqlite3_column_name(ppStmt, i)];
//獲取列的類型
int columnType= sqlite3_column_type(ppStmt, i);
//匹配類型;
id value;
//#define SQLITE_INTEGER 1
//#define SQLITE_FLOAT 2
//#define SQLITE_BLOB 4
//#define SQLITE_NULL 5
//#define SQLITE3_TEXT 3
switch (columnType) {
case SQLITE_INTEGER:
{
value= @(sqlite3_column_int(ppStmt, i));
break;
}
case SQLITE_FLOAT:
{
value= @(sqlite3_column_double(ppStmt, i));
break;
}
case SQLITE_BLOB:
{
value= CFBridgingRelease(sqlite3_column_blob(ppStmt, i));
break;
}
case SQLITE_NULL:
{
value= @"";
break;
}
case SQLITE3_TEXT:
{
const char * columnValue=(const char *)sqlite3_column_text(ppStmt, i);
value=[NSString stringWithUTF8String:columnValue];
break;
}
default:
break;
}
[mnDic setValue:value forKeyPath:columnName];
}
}
//3.0 關閉數據庫
sqlite3_finalize(ppStmt);//銷毀準備語句,防止內存泄漏;
[self closeDBWithUID:uid];
return muArray;
FMDB
FMDB是iOS平臺的SQLite數據庫框架,它以OC的方式封裝了SQLite的C語言API,使用起來更加面向對象,省去了很多麻煩的C語言代碼,對比蘋果自帶的Core Data框架,更加輕量級和靈活,提供了多線程安全的數據庫操作方法,有效地防止數據混亂。
FMDB有三個主要的類:
FMDatabase:
一個FMDatabase對象就代表一個單獨的SQLite數據庫
FMResultSet:
使用FMDatabase執行查詢后的結果集
FMDatabaseQueue:
用于在多線程中執行多個查詢或更新,它是線程安全的
在FMDB中,除查詢以外的所有操作,都稱為“更新”
create、drop、insert、update、delete
等
更新操作
使用executeUpdate:方法執行更新:
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
查詢操作
- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
例如:
// 查詢數據
FMResultSet *result = [_db executeQuery:@"SELECT * FROM t_student"];
// 遍歷結果集
while ([result next]) {
NSString *name = [rs stringForColumn:@"name"];
int age = [rs intForColumn:@"age"];
double score = [rs doubleForColumn:@"score"];
}
FMDatabaseQueue
FMDatabase這個類是線程不安全的,如果在多個線程中同時使用一個FMDatabase實例,會造成數據混亂等問題,為了保證線程安全,FMDB提供了非常方便快捷的FMDatabaseQueue類:
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
[queue inDatabase:^(FMDatabase *db) {
[db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
[db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Rose"];
[db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jim"];
FMResultSet *rs = [db executeQuery:@"select * from t_student"];
while ([rs next]) {
// …
}
}];
使用事物:
[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
[db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jack"];
[db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Rose"];
[db executeUpdate:@"INSERT INTO t_student(name) VALUES (?)", @"Jim"];
FMResultSet *result = [db executeQuery:@"select * from t_student"];
while ([result next]) {
// …
}
}];
#import <Foundation/Foundation.h>
@interface StatusTool : NSObject
/**
* 根據請求參數去沙盒中加載緩存的微博數據
*
* @param params 請求參數
*/
+ (NSArray *)statusesWithParams:(NSDictionary *)params;
/**
* 存儲微博數據到沙盒中
*
* @param statuses 需要存儲的微博數據
*/
+ (void)saveStatuses:(NSArray *)statuses;
@end
具體實現:
#import "StatusTool.h"
#import "FMDB.h"
@implementation StatusTool
static FMDatabase *_db;
+ (void)initialize
{
// 1.通過指定SQLite數據庫文件路徑來創建FMDatabase對象
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"Statuses.db"];
_db = [FMDatabase databaseWithPath:path];
//2.0 打開數據庫
[_db open];
// 3.0創表
[_db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_status (id integer PRIMARY KEY, status blob NOT NULL, idstr text NOT NULL);"];
}
+ (NSArray *)statusesWithParams:(NSDictionary *)params
{
// 根據請求參數生成對應的查詢SQL語句
NSString *sql = nil;
if (params[@"since_id"]) {
sql = [NSString stringWithFormat:@"SELECT * FROM t_status WHERE idstr > %@ ORDER BY idstr DESC LIMIT 20;", params[@"since_id"]];
} else if (params[@"max_id"]) {
sql = [NSString stringWithFormat:@"SELECT * FROM t_status WHERE idstr <= %@ ORDER BY idstr DESC LIMIT 20;", params[@"max_id"]];
} else {
sql = @"SELECT * FROM t_status ORDER BY idstr DESC LIMIT 20;";
}
// 執行SQL
FMResultSet *set = [_db executeQuery:sql];
NSMutableArray *statuses = [NSMutableArray array];
while (set.next) {
//可以根據columnName來獲取數據,跟不用根據列號;
NSData *statusData = [set objectForColumnName:@"status"];
NSDictionary *status = [NSKeyedUnarchiver unarchiveObjectWithData:statusData];
[statuses addObject:status];
}
return statuses;
}
+ (void)saveStatuses:(NSArray *)statuses
{
// 要將一個對象存進數據庫的blob字段,最好先轉為NSData,一個對象要遵守NSCoding協議,實現協議中相應的方法,才能轉成NSData
for (NSDictionary *status in statuses) {
// NSDictionary --> NSData
NSData *statusData = [NSKeyedArchiver archivedDataWithRootObject:status];
[_db executeUpdateWithFormat:@"INSERT INTO t_status(status, idstr) VALUES (%@, %@);", statusData, status[@"idstr"]];
}
}
@end
SQLite進行事務的SQL語句:
只要在執行SQL語句前加上以下的SQL語句,就可以使用事務功能了:
開啟事務的SQL語句,"begin transaction;"
進行提交的SQL語句,"commit transaction;"
進行回滾的SQL語句,"rollback transaction;"
只有事務提交了,開啟事務期間的操作才會生效