iOS - SQLite 數據庫存儲


1、SQLite 數據庫

  • SQLite 是一種輕型的嵌入式數據庫,安卓和 iOS 開發使用的都是 SQLite 數據庫。它占用資源非常低,在嵌入式設備中,可能需要幾百 K 的內存數據就夠了。他的處理速度比 Mysql、PostgreSQL 這兩款著名的數據庫都要快。數據庫的存儲和 Excel 很像,以表(table)為單位。表由多個字段(列、屬性、column)組成,表里面的每一行數據稱為記錄。數據庫操作包含打開數據庫、創建表,表的增、刪、改、查。

  • SQL(Structured Query Language)結構化查詢語言,SQL 是一種對數據庫中的數據進行定義和操作的語言。使用 SQL 語言編寫出來的句子/代碼叫 SQL 語句,在程序運行過程中,想要操作(增刪改查,CRUD)數據庫中的數據,必須使用 SQL 語句。SQL 語句不區分大小寫,每句語句都必須以分號結尾。

  • SQL 中常用的關鍵字有 select、insert、update、delete、from、create、where、desc、orderby、table,數據庫中不可以使用關鍵字來命名表、字段。SQL 語句中用 ?來作為占位符,不管字段是何種類型。

  • SQLite 語句的種類:

    • 數據定義語句(DDL:Data Definition Language):包括 create 和 drop 等操作,在數據庫中創建新表或刪除表(create table 或 drop table)。
    • 數據操作語句(DML:Data Manipulation Language):包括 insert、update、delete 等操作,上面的三種操作分別用於添加、修改、刪除表中的數據。
    • 數據查詢語句(DQL:Data Query Language):可以用於查詢獲得表中的數據,關鍵字 select 是 SQL(也是所有 SQL)用的最多的操作,其他 DQL 常用的關鍵字有 where、order by、group by 和 having。
  • 注意:寫入數據庫,字符串可以采用 char 方式,而從數據庫中取出 char 類型,當 char 類型有表示中文字符時,會出現亂碼。這是因為數據庫默認使用 ASCII 編碼方式。所以要想正確從數據庫中取出中文,需要用 NSString 來接收從數據庫取出的字符串。

  • SQLite 操作方法

    	sqlite3 *db             數據庫句柄,跟文件句柄很類似
    	sqlite3_stmt *stmt      這個相當於 ODBC 的 Command 對象,用於保存編譯好的 SQL 語句
    	sqlite3_open()          打開數據庫,沒有數據庫時創建。
    	sqlite3_exec()          執行非查詢的 sql 語句
    	sqlite3_prepare_v2      執行查詢的 sql 語句
    	Sqlite3_step()          在調用 sqlite3_prepare 后,使用這個函數在記錄集中移動。
    	sqlite3_free()          清空變量
    	Sqlite3_close()         關閉數據庫文件
    	
    	還有一系列的函數,用於從記錄集字段中獲取數據,如:
    	
    	sqlite3_column_text()   取 text 類型的數據。
    	sqlite3_column_blob()   取 blob 類型的數據
    	sqlite3_column_int()    取 int 類型的數據
    
  • SQLite 命令行

    	.help    :幫助
    	.quit    :退出
    	.database:列出數據庫信息
    	.dump    :查看所有的 sql 語句
    	.schema  :查看表結構
    	.tables  :顯示所有的表
    
  • SQL 語句常用數據類型

    	(1)整型:
    
    		bigint  :整形數據,大小為 8 個字節
    		integer :整形數據,大小為 4 個字節
    		smallint:整形數據,大小為 2 個字節
    		tinyint :從 0 到 255 的整形數據,存儲大小為 1 字節
    	
    	(2)浮點型:
    	
    		float :4 字節浮點數
    		double:8 字節浮點數
    		real  :8 字節浮點數
    	
    	(3)字符型:
    	
    		char(n)   :n 長度的字串,n 不能超過 254
    		varchar(n):長度不固定且其長度為 n 的字串,n 不能超過 4000
    		text      :text 存儲可變長度的非 unicode 數據,存放比 varchar 更大的字符串
    		
    		注意事項:
    		
    			盡量用 varchar
    			超過 255 字節的只能用 varchar 或 text
    			能用 varchar 的地方不用 text
    		
    		SQLite 字符串區別:
    	
    			char 存儲定長數據很方便,char 字段上的索引效率極高,比如定義 char(10),那么不論你存儲的數據是否達到了 10 個字節,都要占去 10 個字節的空間,不足的自動用空格填充。
    			varchar 存儲變長數據,但存儲效率沒有 char 高,如果一個字段可能的值是不固定長度的,我們只知道它不可能超過 10 個 > 字符,把它定義為 varchar(10) 是最合算的,varchar 類型的實際長度是它的值的實際長度 +1,為什么 +1 呢 ?這個字節用於保存實際使用了多大的長度。因此,從空間上考慮,用 varchar 合適,從效率上考慮,用 char 合適,關鍵是根據情況找到權衡點。
    			text 存儲可變長度的非 Unicode 數據,最大長度為 2^31-1(2147483647)個字符。
    	
    	(4)日期類型:
    	
    		date     :包含了年份,月份,日期
    		time     :包含了小時,分鍾,秒
    		datetime :包含了年,月,日,時,分,秒
    		timestamp:包含了年,月,日,時,分,秒,千分之一秒
    	
    		注意:datetime 包含日期時間格式,必須寫成 ‘2010-08-05’不能寫為‘2010-8-5’,否則在讀取時會產生錯誤。
    	
    	(5)其他類型:
    	
    		null         :空值
    		blob         :二進制對象,主要用來存放圖片和聲音文件等
    		default      :缺省值
    		primary key  :主鍵值
    		autoincrement:主鍵自動增長
    		
    	(6)什么是主鍵:
    
    		primary key,主鍵就是一個表中,有一個字段,里面的內容不可以重復,一般一個表都需要設置一個主鍵,autoincrement 讓主鍵自動增長。
    
    	(7)注意事項:
    		
    		所有字符串必須要加 ‘ ’ 單引號
    		整數和浮點數不用加 ‘ ’
    		日期需要加單引號 ‘ ’
    		字段順序沒有關系,關鍵是 key 與 value 要對應
    		對於自動增長的主鍵不需要插入字段
    
  • 簡單基本的 SQL 語句

    	(1) 數據記錄篩選:
    		
    		sql="select * from 數據表 where 字段名=字段值 order by 字段名 [desc]"
    		sql="select * from 數據表 where 字段名 like '%字段值%' order by 字段名 [desc]"
    		sql="select top 10 * from 數據表 where 字段名=字段值 order by 字段名 [desc]"
    		sql="select top 10 * from 數據表 order by 字段名 [desc]"
    		sql="select * from 數據表 where 字段名 in ('值1','值2','值3')"
    		sql="select * from 數據表 where 字段名 between 值1 and 值2"
    
    	(2) 更新數據記錄:
    		  
    		sql="update 數據表 set 字段名=字段值 where 條件表達式"
    		sql="update 數據表 set 字段1=值1,字段2=值2 …… 字段n=值n where 條件表達式"
    		 
    	(3) 刪除數據記錄:
    		  
    		sql="delete from 數據表 where 條件表達式"
    		sql="delete from 數據表" (將數據表所有記錄刪除)
    
    	(4) 添加數據記錄:
    		  
    		sql="insert into 數據表 (字段1,字段2,字段3 …) values (值1,值2,值3 …)"
    		sql="insert into 目標數據表 select * from 源數據表" (把源數據表的記錄添加到目標數據表)
    
    	(5) 數據記錄統計函數:
    		  
    		AVG(字段名) 得出一個表格欄平均值
    		COUNT(*;字段名) 對數據行數的統計或對某一欄有值的數據行數統計
    		MAX(字段名) 取得一個表格欄最大的值
    		MIN(字段名) 取得一個表格欄最小的值
    		SUM(字段名) 把數據欄的值相加
    			  
    		引用以上函數的方法:
    		  
    		sql="select sum(字段名) as 別名 from 數據表 where 條件表達式"
    		  
    		//set rs=conn.excute(sql)
    		用 rs("別名") 獲取統計的值,其它函數運用同上。
    		  
    		查詢去除重復值:select distinct * from table1
    		  
    	(6) 數據表的建立和刪除:
    	  
    	  CREATE TABLE 數據表名稱(字段1 類型1(長度),字段2 類型2(長度) …… )
    

2、iOS 自帶 SQLite 的使用

  • 1、環境配置

    • 在 TARGETS => Build Phases => Link Binary With Libraries => 中添加:libsqlite3.0.tbd

      SQLite1

    • 或者在 TARGETS => Build Settings => Linking => Other Linker Flags 中添加 -l< 所需 dylib 的名稱 >:-lsqlite3.0

      SQLite2

  • 2、使用

    • 添加頭文件:

      	#import "sqlite3.h"
      
    • 配置數據庫路徑

      	// 設置數據庫文件路徑
      	NSString *databaseFilePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, 
      	                                                                  NSUserDomainMask, 
      	                                                                  YES).lastObject 
      	                              stringByAppendingPathComponent:@"mydb.sqlite"];
          
      	// 創建數據庫句柄
      	sqlite3 *db;
          
      	// 錯誤記錄
      	char *error;
      
  • 3、打開數據庫

    	// 數據庫文件不存在時,自動創建文件
    	
    	if (sqlite3_open([databaseFilePath UTF8String], &db) == SQLITE_OK) {
    	
    		NSLog(@"sqlite dadabase is opened.");
    	} else {
    	
    		NSLog(@"sqlite dadabase open fail.");
    	}
    
  • 4、創建數據表

    	/*
    		sql 語句,專門用來操作數據庫的語句。
    		create table if not exists 是固定的,如果表不存在就創建。
    		myTable() 表示一個表,myTable 是表名,小括號里是字段信息。
    		字段之間用逗號隔開,每一個字段的第一個單詞是字段名,第二個單詞是數據類型,primary key 代表主鍵,autoincrement 是自增。
    	*/
    
    	// create table if not exists 表名(主鍵名 主鍵類型 primary key autoincrement, 鍵名 鍵類型, 鍵名 鍵類型, ...)
    	NSString *createSql = @"create table if not exists myTable(id integer primary key autoincrement, name text, age integer, address text)";
    	    
    	if (sqlite3_exec(db, [createSql UTF8String], NULL, NULL, &error) == SQLITE_OK) {
    	
    		NSLog(@"create table is ok.");
    	} else {
    	
    		NSLog(@"error: %s", error);
    	    
    		// 每次使用完畢清空 error 字符串,提供給下一次使用
    		sqlite3_free(error);
    	}
    
  • 5、插入記錄

    	// insert into 表名(鍵名, 鍵名, ...) values('鍵值', '鍵值', '...')
    	NSString *insertSql = @"insert into myTable(name, age, address) values('小新', '8', '東城區')";
    	    
    	if (sqlite3_exec(db, [insertSql UTF8String], NULL, NULL, &error) == SQLITE_OK) {
    	
    		NSLog(@"insert operation is ok.");
    	} else {
    	
    		NSLog(@"error: %s", error);
    		    
    		// 每次使用完畢清空 error 字符串,提供給下一次使用
    		sqlite3_free(error);
    	}
    
  • 6、刪除記錄

    	// delete from 表名 查詢條件(where id = 2)
    	NSString *deleteSql = @"delete from myTable where id = 2";
    	    
    	if (sqlite3_exec(db, [deleteSql UTF8String], NULL, NULL, &error) == SQLITE_OK) {
    	
    		NSLog(@"delete operation is ok.");
    	} else {
    	
    		NSLog(@"error: %s", error);
    		    
    		// 每次使用完畢清空 error 字符串,提供給下一次使用
    		sqlite3_free(error);
    	}
    
  • 7、修改記錄

    	// update 表名 set 鍵名 = '鍵值', 鍵名 = '鍵值', ... 查詢條件(where id = 3)
    	NSString *updateSql = @"update myTable set name = '小白', age = '10', address = '西城區' where id = 3";
    	    
    	if (sqlite3_exec(db, [updateSql UTF8String], NULL, NULL, &error) == SQLITE_OK) {
    	
    		NSLog(@"update operation is ok.");
    	} else {
    	
    		NSLog(@"error: %s", error);
    		    
    		// 每次使用完畢清空 error 字符串,提供給下一次使用
    		sqlite3_free(error);
    	}
    
  • 8、查詢記錄

    	sqlite3_stmt *statement;
    	    
    	// select 鍵名, 鍵名, ... from 表名,select * from 表名:查詢所有 key 值內容
    	NSString *selectSql = @"select id, name, age, address from myTable";
    	    
    	if (sqlite3_prepare_v2(db, [selectSql UTF8String], -1, &statement, nil) == SQLITE_OK) {
    		    
    		while(sqlite3_step(statement) == SQLITE_ROW) {
    			    
    			// 查詢 id 的值
    			int _id = sqlite3_column_int(statement, 0);
    			
    			// 查詢 name 的值
    			NSString *name = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
    			
    			// 查詢 age
    			int age = sqlite3_column_int(statement, 2);
    			
    			// 查詢 address 的值
    			NSString *address = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 3)];
    			    
    			NSLog(@"id: %i, name: %@, age: %i, address: %@", _id, name, age, address);
    		}
    	} else {
    		NSLog(@"select operation is fail.");
    	}
    	    
    	sqlite3_finalize(statement);
    
  • 9、關閉數據庫

    	if (sqlite3_close(db) == SQLITE_OK) {
    	
        	NSLog(@"sqlite dadabase is closed.");
    	} else {
    	
        	NSLog(@"sqlite dadabase close fail.");
    	}
    

3、fmdb 的使用

  • iOS 中原生的 SQLite API 在使用上相當不友好,在使用時,非常不便。於是,就出現了一系列將 SQLite API 進行封裝的庫,例如 fmdb、PlausibleDatabase、sqlitepersistentobjects 等,fmdb 是一款簡潔、易用的封裝庫。

  • fmdb 同時兼容 ARC 和非 ARC 工程,會自動根據工程配置來調整相關的內存管理代碼。

  • fmdb 常用類:

    • FMDatabase : 一個單一的 SQLite 數據庫,用於執行 SQL 語句。
    • FMResultSet :執行查詢一個 FMDatabase 結果集,這個和 Android 的 Cursor 類似。
    • FMDatabaseQueue :在多個線程來執行查詢和更新時會使用這個類。
  • 除了查詢操作,fmdb 數據庫操作都執行 executeUpdate 方法,這個方法返回 BOOL 型。后面跟的參數類型必須是對象類型,FMDataBase 對象會將傳過來的參數,轉化成與數據庫字段相匹配的類型,再進行后續處理。

  • fmdb 數據庫中查詢操作使用 executeQuery,並涉及到 FMResultSet,FMResultSet 提供了多個方法來獲取不同類型的數據。

  • 1、環境配置

    • 將 第三方框架 fmdb 添加到工程中,並在 TARGETS => Build Phases => Link Binary With Libraries => 中添加:libsqlite3.0.tbd

      SQLite3

  • 2、使用

    • 添加頭文件:

      	#import "FMDB.h"
      
    • 配置數據庫路徑

      	// 設置數據庫文件路徑
      	NSString *databaseFilePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, 
      	                                                                  NSUserDomainMask, 
      	                                                                  YES).lastObject 
      	                              stringByAppendingPathComponent:@"mydb.sqlite"];
          
      	// 聲明數據庫管理對象
      	@property (nonatomic, strong) FMDatabase *db;
      
      	// 實例化數據庫管理對象
      	/*
      		使用數據庫的路徑初始化
      
      		當數據庫文件不存在時,fmdb 會自己創建一個。
      		如果你傳入的參數是空串:@"" ,則fmdb會在臨時文件目錄下創建這個數據庫,數據庫斷開連接時,數據庫文件被刪除。
      		如果你傳入的參數是 NULL,則它會建立一個在內存中的數據庫,數據庫斷開連接時,數據庫文件被刪除。
      	*/
      	self.db = [[FMDatabase alloc] initWithPath:databaseFilePath];
      
  • 3、打開數據庫

    	if ([self.db open]) {
        
        	NSLog(@"sqlite dadabase is opened.");
    	} else {
        
        	NSLog(@"sqlite dadabase open fail.");
    	}
    
  • 4、創建數據表

    	/*
        	sql 語句,專門用來操作數據庫的語句
        	create table if not exists 是固定的,如果表不存在就創建
        	userInfo() 表示一個表,userInfo 是表名,小括號里是字段信息
        	字段之間用逗號隔開,每一個字段的第一個單詞是字段名,第二個單詞是數據類型,primary key 代表主鍵,autoincrement 是自增
     	*/
    
    	// create table if not exists 表名(主鍵名 主鍵類型 primary key autoincrement, 鍵名 鍵類型, 鍵名 鍵類型, ...)
    	NSString *createSql = @"create table if not exists myTable(id integer primary key autoincrement, name text, age integer, address text)";
    
    	if ([self.db executeUpdate:createSql]) {
        
        	NSLog(@"create table is ok.");
    	} else {
        
        	NSLog(@"create error = %@", self.db.lastErrorMessage);
    	}
    
  • 5、插入記錄

    	// insert into 表名(鍵名, 鍵名, ...) values('鍵值', '鍵值', '...')
    	NSString *insertSql = @"insert into myTable(name, age, address) values('小新', '8', '東城區')";
    
    	if ([self.db executeUpdate:insertSql]) {
        
        	NSLog(@"insert operation is ok.");
    	} else {
        
       	NSLog(@"insert error = %@", self.db.lastErrorMessage);
    	}
    
  • 6、刪除記錄

    	// delete from 表名 查詢條件(where id = 2)
    	NSString *deleteSql = @"delete from myTable where id = 2";
    
    	if ([self.db executeUpdate:deleteSql]) {
        
        	NSLog(@"delete operation is ok.");
    	} else {
        
        	NSLog(@"delete error = %@", self.db.lastErrorMessage);
    	}
    
  • 7、修改記錄

    	// update 表名 set 鍵名 = '鍵值', 鍵名 = '鍵值', ... 查詢條件(where id = 3)
    	NSString *updateSql = @"update myTable set name = '小白', age = '10', address = '西城區' where id = 3";
    
    	if ([self.db executeUpdate:updateSql]) {
        
        	NSLog(@"update operation is ok.");
    	} else {
        
        	NSLog(@"update error = %@", self.db.lastErrorMessage);
    	}
    
  • 8、查詢記錄

    	// select 鍵名, 鍵名, ... from 表名,select * from 表名:查詢所有 key 值內容
    	NSString *selectSql = @"select id, name, age, address from myTable";
    
    	// FMResultSet 查詢結果,執行查詢 SQL 語句
    	FMResultSet *set = [self.db executeQuery:selectSql];
    
    	// 類似於數組的快速遍歷,set 會依次代表所有查詢出來的數據,每次取出一整條數據,根據字段名稱,取出字段的值,將查詢到的數據轉成模型
    	while ([set next]) {
        
        	// 查詢 id 的值
        	int _id = [set intForColumn:@"id"];
        
        	// 查詢 name 的值
        	NSString *name = [set stringForColumn:@"name"];
        
        	// 查詢 age
        	int age = [set intForColumn:@"age"];
        
        	// 查詢 address 的值
        	NSString *address = [set stringForColumn:@"address"];
        
        	NSLog(@"id: %i, name: %@, age: %i, address: %@", _id, name, age, address);
    	}
    
  • 9、關閉數據庫

    	if ([self.db close]) {
        
        	NSLog(@"sqlite dadabase is closed.");
    	} else {
        
        	NSLog(@"sqlite dadabase close fail.");
    	}
    

4、fmdb 多線程操作

  • 如果應用中使用了多線程操作數據庫,那么就需要使用 FMDatabaseQueue 來保證線程安全了。 應用中不可在多個線程中共同使用一個 FMDatabase 對象操作數據庫,這樣會引起數據庫數據混亂。為了多線程操作數據庫安全,fmdb 使用了 FMDatabaseQueue,使用 FMDatabaseQueue 很簡單,首先用一個數據庫文件地址來初使化 FMDatabaseQueue,然后就可以將一個閉包(block)傳入 inDatabase 方法中。在閉包中操作數據庫,而不直接參與 FMDatabase 的管理。

    	// 設置數據庫文件路徑
    	NSString *databaseFilePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                                         NSUserDomainMask,
                                                                         YES).lastObject
                                  	stringByAppendingPathComponent:@"mydb.sqlite"];
    
    	// 打開數據庫
    	FMDatabaseQueue *dbQueue = [FMDatabaseQueue databaseQueueWithPath:databaseFilePath];
    
    	// 隊列 1
    	dispatch_async(dispatch_queue_create("queue1", NULL), ^{
        
        	for (int i = 0; i < 50; ++i) {
            
            	// 對數據庫進行操作,操作完成后會自動關閉數據庫
            	[dbQueue inDatabase:^(FMDatabase *db) {
                
                	NSString *insertSql= [NSString stringWithFormat:
                                      @"insert into myTable(name, age, address) values('%@', '%d', '%@')", @"小新", i, @"東城區"];
                	if ([db executeUpdate:insertSql]) {
                    
    					NSLog(@"%@ insert queue1 %d operation is ok.", [NSThread currentThread], i);
                	} else {
                    
    					NSLog(@"insert error = %@", db.lastErrorMessage);
                	}
            	}];
        	}
    	});
    
    	// 隊列 2
    	dispatch_async(dispatch_queue_create("queue2", NULL), ^{
        
        	for (int i = 0; i < 50; ++i) {
            
            	// 對數據庫進行操作,操作完成后會自動關閉數據庫
            	[dbQueue inDatabase:^(FMDatabase *db) {
                
                	NSString *insertSql= [NSString stringWithFormat:
                                      @"insert into myTable(name, age, address) values('%@', '%d', '%@')", @"小白", i, @"西城區"];
                
                	if ([db executeUpdate:insertSql]) {
                    
                    NSLog(@"%@ insert queue2 %d operation is ok.", [NSThread currentThread], i);
                	} else {
                    
                    NSLog(@"insert error = %@", db.lastErrorMessage);
                	}
            	}];
        	}
    	});
    

5、其他 SQLite 的第三方封裝庫

  • 1、PlausibleDatabase

    • 也是一個數據庫操作的 objective-c 版封裝庫,“SQLite is the initial and primary target, but the API has been designed to support more traditional databases.”
    • 文件較多,一般的接口與 FMDataBase 一樣,此外還支持 SQL 的預編譯和參數綁定。
  • 2、sqlitepersistentobjects

    • 這個開源庫的目標是以面對對象的方式的存儲和加載數據,讓對象本身就有 save 和 load 的功能,屏蔽數據庫的相關操作(創建、更新等),讓使用者在不寫 SQL 語句的狀況下都可以使用 SQLite。應該說是符合 ActiveRecored 標准的。
    • 自從 iOS3.0 支持 Core Data 之后,sqlitepersistentobjects 就停止了更新。不過單從操作數據來說,這個庫還是很優秀的,如果你的數據存儲工作很簡單(light),使用 Core Data 比較顯得凝重的話,sqlitepersistentobjects 也許是個很好的選擇。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM