ios fmdb 存储整型 ios fmdb如何保证线程安全_数据库






1.什么 是 FMDB


FMDB 是 iOS 平台的 SQLite 数据库框架


FMDB 以 OC 的方式封装了 SQLite 的 C 语言 API



FMDB 的优点


使用起来更加面向对象,省去了很多麻烦、冗余的 C 语言代码


对比苹果自带的 Core Data 框架,更加轻量级和灵活


提供了多线程安全的数据库操作方法,有效地防止数据混乱



FMDB 的 github 地址


https:// github.com / ccgus / fmdb




2. FMDB 有三个主要的类



FMDatabase



一个 FMDatabase 对象就代表 一个单独的 SQLite 数据库



用来执行 SQL 语句





FMResultSet



使用 FMDatabase 执行查询后 的 结果集





FMDatabaseQueue



用于 在多线程中执行多个查询或更新, 它 是线程安全的






使用之前一样需要导入sqlite3.dylib;



3.fmdb创建数据库


通过指定 SQLite 数据库文件路径来创建 FMDatabase 对象



FMDatabase *db = [FMDatabase databaseWithPath:path];
if (![db open]) {
NSLog(@"数据库打开失败!");
}





文件路径有三种情况



  • 具体文件路径,如果不存在会自动创建





  • 空字符串@"",会在临时目录创建一个空的数据库,当FMDatabase连接关闭时,数据库文件也被删除





  • nil,会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁









3.执行更新



在 FMDB 中,除查询以外的所有操作,都称为“更新”



create 、 drop 、 insert 、 update 、 delete 等





使用 executeUpdate : 方法执行更新



BOOL)executeUpdate:( NSString*)sql, ...



BOOL)executeUpdateWithFormat:( NSString*)format,...



BOOL)executeUpdate:( NSString*)sql withArgumentsInArray:( NSArray*)arguments





示例



[db executeUpdate:@"UPDATE t_student SET age = ? WHERE name = ?;",@20,@"Jack"]








4.查询



查询方法



FMResultSet *)executeQuery:( 
   NSString*)sql, ... 

FMResultSet *)executeQueryWithFormat:( 
   NSString*)format,... 

FMResultSet *)executeQuery:( 
   NSString *)sql withArgumentsInArray:( 
   NSArray*)arguments 
  
示例
 
// 查询数据
FMResultSet *rs = [db  executeQuery:@"SELECT* FROM t_student"];
// 遍历结果集
while ([rs next]) {
NSString*name = [rs  stringForColumn:@"name"];
int age= [rs  intForColumn:@"age"];
doublescore = [rs  doubleForColumn:@"score"];
}






===============






一:FMDB的线程安全:(以读取图片为例)


1.没有线程安全的执行方式:


//************** 数据库保存图片  ******************//

FMDatabase *database = [FMDatabase databaseWithPath:[self getDatabasePath]];

//打开数据库

[database open];

NSString *sql =  @"create table if not exists Test (id integer primary key autoincrement,name text,image blob);";

//创建表

[database executeUpdate:sql];

//把UIImage对象转化为NSData

NSData *data = UIImagePNGRepresentation([UIImageimageNamed:@"user_browse"]);

//写入数据

sql = @"insert into Test (name,image) values (?,?)";

[database executeUpdate:sql,@"张三",data];

//读取显示

sql = @"select * from Test;";

FMResultSet *resultSet = [database executeQuery:sql];

while (resultSet.next)

{
    
    //[resultSet dataForColumn:@"image"];
    
    NSData *imageData = [resultSet dataForColumnIndex:2];
    
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 300, 300)];
    
    imageView.image = [UIImage imageWithData:imageData];
    
    [self.view addSubview:imageView];
    
}

2,使用线程队列


//************** 数据库线程安全 ***********//
FMDatabaseQueue *queue = [[FMDatabaseQueue alloc] initWithPath:[self getDatabasePath]];

[queue inDatabase:^(FMDatabase *db) {
    
    //线程安全的
    
    __block NSString *sql =@"create table if not exists Test (id integer primary key autoincrement,name text,image blob);";
    
    //创建表
    
    [database executeUpdate:sql];
    
}];

//插入数据

[queue inDatabase:^(FMDatabase *db) {
    
    //写入数据
    
    sql = @"insert into Test (name,image) values (?,?)";
    
    [database executeUpdate:sql,@"张三",data];
    
}];

//读取
[queue inDatabase:^(FMDatabase *db) {
    //读取显示
    sql = @"select * from Test;";
    FMResultSet *resultSet = [database executeQuery:sql];
    while (resultSet.next)
    {
        //[resultSet dataForColumn:@"image"];
        
        NSData *imageData = [resultSet dataForColumnIndex:2];
        UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 300, 300)];
        
        imageView.image = [UIImage imageWithData:imageData];
        [self.view addSubview:imageView];
    }
}];

分析一下线程安全下的FMDB的实现:

在当使用FMDBDatabaseQueue创建数据库时,会使用GCD创建一个线程队列:


。。。
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@",self] UTF8String],NULL);
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridgevoid *)self,NULL);
_openFlags = openFlags;
。。。
然后在读取时调用[queue inDatabase:^(FMDatabase *db)方法,在block中会锁定当前数据库
         
         dispatch_sync(_queue, ^() {
    FMDatabase *db = [self database];
    block(db);
    ……
}










=================================



swift中用databaseQueue操作






SQLiteManager.swift中:



import
 
private let dbName ="my.db"
class
     
     
     //全局访问的单例对象
static let shareSQLiteManager:SQLiteManager =SQLiteManager()
     //全局操作数据库队列的唯一对象
    let queue:FMDatabaseQueue
     //为了确保单例对象的唯一性
     //私有化构造函数
    private  init() {
        //得到fmdb操作数据库的对象  FMdataBaseQueue数据库队列对象
//
         
         //1.sqlite做一个一个嵌入式的数据库需要存放在沙盒路径中
        let path = (NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask,true).last!asNSString).stringByAppendingPathComponent(dbName)
print(path)
         //如果有存在数据库就直接创建数据库操作的串行队列如果没有数据库就先创建数据库在创建数据库操作的串行队列
         //嵌入式的数据一旦打开就不会断开链接 为了避免频繁的打开链接或者断开链接带来的效率低下的问题
         //服务器的数据库就不同一旦操作完毕后就会断开链接
queue = FMDatabaseQueue(path: path)
         
//创建数据表
        createTable()
     }
     
    //创建数据表
private func
        //执行sql创建数据表
        let sql ="CREATE TABLE IF NOT EXISTS T_Person (" +
        "id integer PRIMARY KEY AUTOINCREMENT NOT NULL," +
        "name text," +
        "age integer) ;"
print(sql)
queue.inDatabase { (db) ->Voidin
            //执行sql创建数据表 executeStatements可以执行多条sql语句
if db.executeStatements(sql) {
print("创建数据表成功")
else
print("创建数据表失败")
             }
         }
     }
 }
 
  
 
 
在viewcontroller中:
 
 
 
  
import
 
class ViewController:  UIViewController
 
override func
super.viewDidLoad()
        batchUpdate()
 //        insert2()
 //        update()
 //        delete()
     }
     
override func touchesEnded(touches:Set<UITouch>, withEvent event:UIEvent?) {
 //        insert2()
select()
     }
     
     
    //查询数据
func
        let sql ="SELECT id,name,age FROM T_Person;"
SQLiteManager.shareSQLiteManager.queue.inDatabase { (db) ->Voidin
guard  let rs = db.executeQuery(sql)  else
//没有查询到数据
return
             }
             
//一定查询到数据
            //获取结果集的每一条(行)数据
while rs.next() {
let id = rs.intForColumn("id")
let name = rs.stringForColumn("name")
let age = rs.intForColumn("age")
                 
print(id,name,age)
             }
         }
     }
     
     
    //删除数据
func
         
        //需要加条件限制
         
        let sql ="DELETE FROM T_Person where id < ?;"
SQLiteManager.shareSQLiteManager.queue.inDatabase { (db) ->Voidin
executeUpdate(sql,10)
         }
     }
     
     
    //更新数据
func
         
         //在更新数据的时候注意需要加限制条件否则后果很严重 
         //然后作为客户端不需要面临这种
        let sql ="UPDATE T_Person SET name = ?, age = ? where id = 1;"
SQLiteManager.shareSQLiteManager.queue.inDatabase { (db) ->Voidin
executeUpdate(sql,  "哈哈",80)
         }
     }
     
     //不会出现数据污染的问题
func
        let sql ="INSERT INTO T_Person (name,age) VALUES (?, ?);"
SQLiteManager.shareSQLiteManager.queue.inTransaction { (db, rollback) ->Voidin
            //在一个事务中进行数据的更新
            let start =CFAbsoluteTimeGetCurrent()
for i  in 0..<100000
let name =  "思思" +"\(i)"
let age =  10 + random() %  20
let resut =  db.executeUpdate(sql, name,age)
                 
 //                if i == 90000 {
回滚事务
需要进行回滚是
 //                    rollback.memory = true
 //                }
                 
if
memory =  true
                 }
             }
             
let delta =  CFAbsoluteTimeGetCurrent() - start
print(delta)
         }
     }
     
     
     //插入大批量的数据耗时太长如果发生断电停网就会导致数据遭到污染
     //引入事务解决风险 事务
     
     //第二个方法更加好用 可以将插入的对象和sql语句分离
func
         //使用占位符来添加数据 占位符不需要添加 ''
        let sql ="INSERT INTO T_Person (name,age) VALUES (?, ?);"
SQLiteManager.shareSQLiteManager.queue.inDatabase { (db) ->Voidin
            //values  表示的是可变参数
//插入十万条数据
//记录开始的时间
            let start =CFAbsoluteTimeGetCurrent()
for i  in 0..<100000
let name =  "思思" +"\(i)"
let age =  10 + random() %  20
executeUpdate(sql, name,age)
             }
             
let delta =  CFAbsoluteTimeGetCurrent() - start
print(delta)
             
         }
     }
 
func
        //插入数据 字符串需要加
        //sql语句的结束加上分号
        let sql ="INSERT INTO T_Person (name, age) VALUES ('思思',18);"
SQLiteManager.shareSQLiteManager.queue.inDatabase { (db) ->Voidin
executeStatements(sql)
         }
         
     }
 
 }
 
 
  
 
 

   ========================= 
 
 
 
 
  
 
 
OC版的
 
 
 
创建数据库 -->  一次*/
 
static FMDatabase *_db;
 
void)initialize
 {
     
    //1.  获取路径
    NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES) [0]stringByAppendingPathComponent:@"deal.sqlite"];
     
NSLog(@"filePath : %@", filePath);
     
    //2.  创建数据库
_db = [FMDatabasedatabaseWithPath:filePath];
     //判断数据库如果没有打开,就直接返回
if (![_dbopen]) {
return;
     }
     
    //3.  创建表
    //Xcode没有智能提示
     // 数据库的常见语句都是固定的写法,不需要强制记忆.会用会改就行
    //2-9698598 deal_id
    //FMDB, sql语句之后可以不加;
    [_dbexecuteUpdate:@"CREATE TABLE IF NOT EXISTS t_collect_deal(id integer PRIMARY KEY, deal blob NOT NULL, deal_id text NOT NULL);"];
 }
 
 
添加收藏*/
void)addCollectDealModel:(HMDealModel
 {
    //1.  模型转换Data
    //  实现了归档方法NSKeyedArchiver ,  自定义类实现NSCoding协议,实现归结档方法 -->使用框架的宏定义来搞定
NSData *data = [NSKeyedArchiverarchivedDataWithRootObject:dealModel];
     
    //2.  添加数据
    [_dbexecuteUpdateWithFormat:@"INSERT INTO t_collect_deal(deal,deal_id)  VALUES (%@,%@)",data,dealModel.deal_id];
 }
 
移除收藏*/
void)removeCollectDealModel:(HMDealModel
 {
    [_dbexecuteUpdateWithFormat:@"DELETE FROM t_collect_deal WHERE deal_id=%@",dealModel.deal_id];
 }
 
之前是否收藏*/
BOOL)isCollectDealModel:(HMDealModel
 {
    //  根据dealModel_dealID .  如果发现dealID在数据库中存在1个 -->数据
    //AS  给查询出来的count东西,起个名字
     
    //1.  查询dealID所在的个数
    FMResultSet *set = [_dbexecuteQueryWithFormat:@"SELECT count(*) AS dealCount FROM t_collect_deal WHERE deal_id=%@",dealModel.deal_id];
     
    //2.  调用next方法,才可以指向下一条数据
next];
     
    //3.  判断个数
    // intForColumn :  指定一个列名,返回个数值
return [set intForColumn:@"dealCount"] ==1;
 }
 
/** 根据指定的页码返回指定的数据*/
NSArray *)collectWithPage:(NSInteger)page
 {
    //1.  每页返回多少条
NSInteger count =  20;
     
    //2.  索引位置
NSInteger index = (page -1) * count;
     
    //总的就是 100 5页
    // count = 20
    // page 1   索引0  1~20返回
    // page 2   索引20 21~40返回
    // page 3   索引40 41~60返回
     
     // id逆序返回,还要设置查询条件
    //ORDER BY  排序
    //DESC  逆序
    //3.  获取查询的值
    FMResultSet *set = [_dbexecuteQueryWithFormat:@"SELECT * FROM t_collect_deal ORDER BY id DESC LIMIT %ld,%ld",index, count];
     
    //4.  遍历获取
    NSMutableArray *tempArray = [NSMutableArrayarray];
while (set.next) {
        //objectForColumnName:  告诉我列名, 会返回对应的对象
HMDealModel *dealModel = [NSKeyedUnarchiverunarchiveObjectWithData:[setobjectForColumnName:@"deal"]];
addObject:dealModel];
     }
return
 }
 
 
/** 数据库的总个数*/
NSInteger)collectTotalCount
 {
    //1.  查询dealID所在的个数
    FMResultSet *set = [_dbexecuteQueryWithFormat:@"SELECT count(*) AS dealCount FROM t_collect_deal"];
     
    //2.  调用next方法,才可以指向下一条数据
next];
     
    //3.  返回个数值
    // intForColumn :  指定一个列名,返回个数值
return [set intForColumn:@"dealCount"];
 }
 
 
 
 ====================================================
 
 
-(void)open{
//1,拿到数据库文件的路径
self.path =NSHomeDirectory();
self.path = [self.pathstringByAppendingPathComponent:@"Documents/data.db"];
//2,拿到数据库对象,打开数据库,如果这个数据库不存在,就会自动创建
FMDatabase* db = [FMDatabasedatabaseWithPath:self.path];
BOOL res = [db open];
if (res == NO) {
    NSLog(@"打开失败");
    return;
}else{
    NSLog(@"数据库打开成功");
}
//如果表不存在,创建
res = [db executeUpdate:@"create table if not exists Students(name,score,image)"];//执行sql语句

if (res == NO) {
    NSLog(@"创建失败");
    [db close];//关闭数据库
    return;
}else  if(res==YES){
    NSLog(@"创建成功");
}
//4,表创建成功之后,就进行增删改查的操作:
//1>,插入数据
res = [db executeUpdate:@"insert into Students values (?,?,?)",@2,1,1];
if (res == NO) {
    NSLog(@"插入失败");
}
[db close];
}
//2>,删除操作:
- (void)del:(id)sender{
    FMDatabase* db = [FMDatabasedatabaseWithPath:self.path];
    //打开数据库
    BOOL res = [dbopen];
     //如果失败,退出方法
    if (res ==NO) {
        NSLog(@"打开失败");
        return;
    }
    //删除
    res = [db executeUpdate:@"delete from Students where name=?",1];
    if (res ==NO) {
        NSLog(@"删除失败");
    }
    [db close];
}
//3>,更新操作:
- (void)update:(id)sender{
    FMDatabase* db = [FMDatabasedatabaseWithPath:self.path];
    BOOL res = [dbopen];
    if (res ==NO) {
        NSLog(@"打开失败");
        return;
    }
    
   
    res = [db executeUpdate:@"update Students set score=? where name=?",1,1];
    if (res ==NO) {
        NSLog(@"修改失败");
    }
    [db close];
}

//4>,查询操作:
- (void)fetch:(id)sender{
    FMDatabase* db = [FMDatabasedatabaseWithPath:self.path];
    BOOL res = [dbopen];
    if (res ==NO) {
        NSLog(@"打开失败");
        return;
    }
    
    FMResultSet* set = [dbexecuteQuery:@"select * from Students"];//FMResultSet相当于游标集
     //创建数组,保存所有学生信息
//    NSMutableArray* array = [NSMutableArray arrayWithCapacity:0];
    //遍历Students表
    while ([setnext]) {//有下一个的话,就取出它的数据,然后关闭数据库
        //name
        NSString* name = [setstringForColumn:@"name"];
        //score
        int score = [setintForColumnIndex:1];
        //data
        int  data = [setintForColumnIndex:2];
        NSLog(@"--%@--%d--%d",name,score,data);
    }
    [db close];
    
}
 
  

 
  
 
 
  =============fmdb实例======= 
 
 
  
/*
 * $description: 数据库操作类 -内置数据库
 */
#import <Foundation/Foundation.h>

@interface SPDataBuildInBaseManager :NSObject



+(SPDataBuildInBaseManager*)shareInstance;


-(void)insertAddress:(NSArray*)addressArray;

/**
 *  获取某个级别的地址
 *
 *  @param level level description
 *
 *  @return return value description
 */
-(NSMutableArray*)queryAddressByLevel:(NSString*)level;


/**
 *  获取下级地址信息
 *
 *  @param level level description
 *
 *  @return return value description
 */
-(NSMutableArray*)queryAddressByParentID:(NSString*)parentID;

/**
 *  根据ID,获取省市县数据,空格分隔
 *
 *  @param level level description
 *
 *  @return return value description
 */
-(NSString*)queryFirstAddress:(NSString*)provinceID cityID:(NSString*)cityID districtID:(NSString*)districtID;

@end
 
  

 
 
 
  
/* * $description: 购物车操作类
 *选择省市区
 */
#import "SPDataBuildInBaseManager.h"
#import "SPMobileApplication.h"
#import "SPRegionModel.h"
#import <FMDB.h>


static SPDataBuildInBaseManager *dataBaseManager =nil;


@implementation SPDataBuildInBaseManager
{
 
}

/*************/
-(id)init{
    if (self = [superinit]) {
        //初始化数据
        [selfinitData ];
    }
    returnself ;
}

+(SPDataBuildInBaseManager*)shareInstance{
    @synchronized(self){//为了确保多线程情况下,仍然确保实体的唯一性
        if (!dataBaseManager) {
            dataBaseManager = [[selfalloc]init ];//该方法会调用 allocWithZone
        }
    }
    
    returndataBaseManager ;
}

+(id)allocWithZone:(struct_NSZone *)zone{
    @synchronized(self){
        if (!dataBaseManager) {
            dataBaseManager = [superallocWithZone:zone];//确保使用同一块内存地址
            returndataBaseManager;
        }
    }
    returnnil;
}

- (id)copyWithZone:(NSZone *)zone{
    returnself;//确保copy对象也是唯一
}

-(void)initData{
    
     //1.获得数据库文件的路径
    NSString *doc=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)lastObject];
    NSString *fileName=[docstringByAppendingPathComponent:@"tpshop.sqlite"];
    
    //2.获得数据库
    FMDatabase *db=[FMDatabasedatabaseWithPath:fileName];
    
    //3.打开数据库
    if ([dbopen]) {
        //4.创表
        BOOL result=[dbexecuteUpdate:@"CREATE TABLE IF NOT EXISTS sp_address (id integer, name text NOT NULL, parent_id integer NOT NULL , level integer NOT NULL);"];
        
    }
    
    [db close];
    
}

-(NSString*)databasePath{
    
    NSString *doc=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES)lastObject];
    NSString *fileName=[docstringByAppendingPathComponent:@"tpshop.sqlite"];
    
    return fileName;
}

/**
 *  打开数据库
 *
 *  @return FMDatabase
 */
-(FMDatabase*)openDB{
    
     //1.获得数据库文件的路径
    NSString *fileName = [selfdatabasePath];
    
    //2.获得数据库
    FMDatabase *db=[FMDatabasedatabaseWithPath:fileName];
    
    if ([dbopen]) {
        return db;
    }
    
    returnnil;
}

/*************/

/**
 *  地址信息保存到本地数据库
 *
 *  @param addressArray addressArray description
 */
-(void)insertAddress:(NSArray*)addressArray{
    
    NSString *databasePath = [selfdatabasePath];
    
    FMDatabaseQueue * queue = [FMDatabaseQueuedatabaseQueueWithPath:databasePath];
    dispatch_queue_t queue_t =dispatch_queue_create("queue1",NULL);
    
    dispatch_async(queue_t, ^{
        
        [queue inDatabase:^(FMDatabase *db) {
            
            if (db) {
                //1. 先删除旧数据
                NSString* deleteSQL =@"delete from sp_address";
                [db executeUpdate:deleteSQL];
                
                //2. 插入新数据
                for (NSDictionary* dictin addressArray) {
                    NSString* name = [dictobjectForKey:@"name"];
                    NSString* addrId = [dictobjectForKey:@"id"];
                    NSString* parent_id = [dictobjectForKey:@"parent_id"];
                    NSString* level = [dictobjectForKey:@"level"];
                    
                    NSString* insertSQL =@"insert into sp_address(id , name , parent_id , level)values(?,?,?,?)";
                    
                    BOOL res = [dbexecuteUpdate:insertSQL , addrId , name , parent_id , level];
                    
                }
            }
            //关闭连接
            //[db close];
            
        }];
    });
}

/**
 *  获取某个级别的地址
 *
 *  @param level level description
 *
 *  @return return value description
 */
-(NSMutableArray*)queryAddressByLevel:(NSString*)level{
    
    FMDatabase *db = [selfopenDB];
    NSMutableArray* array = [NSMutableArrayarray];
    if (db) {
        NSString* sql = [NSStringstringWithFormat:@"select id , name , parent_id , level from sp_address where level = %@ " , level];
        FMResultSet * rs = [dbexecuteQuery:sql];
        while ([rsnext]) {
            NSString* regionID = [rsstringForColumn:@"id"];
            NSString * name = [rsstringForColumn:@"name"];
            NSString * parentID = [rsstringForColumn:@"parent_id"];
            NSString * level = [rsstringForColumn:@"level"];
            SPRegionModel* address = [[SPRegionModelalloc]init];//
            NSLog(@"refion--%@---name--%@---parentID--%@--level---%@",regionID,name,parentID,level);
            address.regionID = regionID;
            address.name = name;
            address.parentID = parentID;
            address.level = level;
            [array addObject:address];
            
        }
        [db close];
        
    }
    
    return array;
    
}

/**
 *  获取下级地址信息
 *
 *  @param level level description
 *
 *  @return return value description
 */
-(NSMutableArray*)queryAddressByParentID:(NSString*)parentID{
    
    FMDatabase *db = [selfopenDB];
    NSMutableArray* array = [NSMutableArrayarray];
    if (db) {
        NSString* sql = [NSStringstringWithFormat:@"select id , name , parent_id , level from sp_address where parent_id = %@ " , parentID];
        FMResultSet * rs = [dbexecuteQuery:sql];
        
        while ([rsnext]) {
            
            NSString* regionID = [rsstringForColumn:@"id"];
            NSString * name = [rsstringForColumn:@"name"];
            NSString * parentID = [rsstringForColumn:@"parent_id"];
            NSString * level = [rsstringForColumn:@"level"];
            SPRegionModel* address = [[SPRegionModelalloc]init];
            
            address.regionID = regionID;
            address.name = name;
            address.parentID = parentID;
            address.level = level;
            [array addObject:address];
            
        }
        
        [db close];
        
    }
    
    return array;
    
}


/**
 *  根据ID,获取省市县数据,空格分隔
 *
 *  @param level level description
 *
 *  @return return value description
 */
-(NSString*)queryFirstAddress:(NSString*)provinceID cityID:(NSString*)cityID districtID:(NSString*)districtID{
    
    FMDatabase *db = [selfopenDB];
    NSMutableArray* array = [NSMutableArrayarray];
    if (db) {
        
        NSString* sql = [NSStringstringWithFormat:@"select  name  from sp_address where  id IN(%@ , %@ , %@)" , provinceID , cityID , districtID];
        
        FMResultSet * rs = [dbexecuteQuery:sql];
        
        while ([rsnext]) {
            NSString* name = [rsstringForColumn:@"name"];
            [array addObject:name];
        }
        
        [db close];
        
    }
    
    NSString* firstAddr = [arraycomponentsJoinedByString:@" "];
    
    return firstAddr;
    
}

@end
 
  

 
 
 
  
 

打开数据库,创建表(也叫更新)的方法如下:

//获取Document文件夹下的数据库文件,没有则创建
NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES) objectAtIndex:0];
NSString *dbPath = [docPathstringByAppendingPathComponent:@"user.db"];
NSLog(@"dbPath = %@",dbPath);
//获取数据库并打开
FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
if (![dataBase open]) {
    NSLog(@"打开数据库失败");
    return ;
}


//创建表(FMDB中只有update和query操作,除了查询其他都是update操作)
[dataBase executeUpdate:@"create table if not exists user(name text,gender text,age integer) "];
插入数据的操作:

//常用方法有以下3种:
/* 执行更新的SQL语句,字符串里面的"?",依次用后面的参数替代,必须是对象,不能是int等基本类型 */
- (BOOL)executeUpdate:(NSString *)sql,... ;
/* 执行更新的SQL语句,可以使用字符串的格式化进行构建SQL语句 */
- (BOOL)executeUpdateWithFormat:(NSString*)format,... ;
/* 执行更新的SQL语句,字符串中有"?",依次用arguments的元素替代 */
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;


/* 1. 直接使用完整的SQL更新语句 */
[database executeUpdate:@"insert into mytable(num,name,sex) values(0,'liuting','m');"];

NSString *sql = @"insert into mytable(num,name,sex) values(?,?,?);";
/* 2. 使用不完整的SQL更新语句,里面含有待定字符串"?",需要后面的参数进行替代 */
[database executeUpdate:sql,@0,@"liuting",@"m"];
/* 3. 使用不完整的SQL更新语句,里面含有待定字符串"?",需要数组参数里面的参数进行替代 */
[database executeUpdate:sql
@"liuting",@"m"]];

/* 4. SQL语句字符串可以使用字符串格式化,这种我们应该比较熟悉 */
[database executeUpdateWithFormat:@"insert into mytable(num,name,sex) values(%d,%@,%@);",0,@"liuting","m"];
本demo采用第二种方法

//常用方法有以下3种:
//    - (BOOL)executeUpdate:(NSString*)sql, ...
//    - (BOOL)executeUpdateWithFormat:(NSString*)format, ...
//    - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments


//插入数据 ???
BOOL inser = [dataBase executeUpdate:@"insert into user values(?,?,?)",_nameTextField.text,_sexTextField.text,_ageTextField.text];


if (inser) {
    UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"信息保存成功" delegate:self cancelButtonTitle:nil otherButtonTitles:@"确定",nil];
    [alert show];
}
[dataBase close];

5.查询


查询方法也有3种,使用起来相当简单:


// 全部查询
- (FMResultSet *)executeQuery:(NSString*)sql, ...
// 条件查询
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
// 演示如下:

//1.执行查询
FMResultSet *result = [database executeQuery:@"SELECT * FROM t_person"];
//2.遍历结果集
while ([result next]) {
    NSString *name = [result stringForColumn:@"name"];
    int age = [result intForColumn:@"age"];
}
/*
 FMResultSet获取不同数据格式的方法
 
 /* 获取下一个记录 */
- (BOOL)next;
/* 获取记录有多少列 */
- (int)columnCount;
/* 通过列名得到列序号,通过列序号得到列名 */
- (int)columnIndexForName:(NSString *)columnName;
- (NSString *)columnNameForIndex:(int)columnIdx;
/* 获取存储的整形值 */
- (int)intForColumn:(NSString *)columnName;
- (int)intForColumnIndex:(int)columnIdx;
/* 获取存储的长整形值 */
- (long)longForColumn:(NSString *)columnName;
- (long)longForColumnIndex:(int)columnIdx;
/* 获取存储的布尔值 */
- (BOOL)boolForColumn:(NSString *)columnName;
- (BOOL)boolForColumnIndex:(int)columnIdx;
/* 获取存储的浮点值 */
- (double)doubleForColumn:(NSString *)columnName;
- (double)doubleForColumnIndex:(int)columnIdx;
/* 获取存储的字符串 */
- (NSString *)stringForColumn:(NSString *)columnName;
- (NSString *)stringForColumnIndex:(int)columnIdx;
/* 获取存储的日期数据 */
- (NSDate *)dateForColumn:(NSString *)columnName;
- (NSDate *)dateForColumnIndex:(int)columnIdx;
/* 获取存储的二进制数据 */
- (NSData *)dataForColumn:(NSString *)columnName;
- (NSData *)dataForColumnIndex:(int)columnIdx;
/* 获取存储的UTF8格式的C语言字符串 */
- (const  unsigned cahr *)UTF8StringForColumnName:(NSString *)columnName;
- (const  unsigned cahr *)UTF8StringForColumnIndex:(int)columnIdx;
/* 获取存储的对象,只能是NSNumber、NSString、NSData、NSNull */
- (id)objectForColumnName:(NSString *)columnName;
- (id)objectForColumnIndex:(int)columnIdx;
*/
本文demo中代码演示:

//查询全部
- (IBAction)query:(id)sender {
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)  objectAtIndex:0];
    NSString *dbPath = [docPathstringByAppendingPathComponent:@"user.db"];
    
    FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
    if (![dataBase open]) {
        NSLog(@"打开数据库失败");
        return ;
    }
    FMResultSet *resultSet = [dataBase executeQuery:@"select * from user"];
    while ([resultSet next]) {
        NSString *name = [resultSet stringForColumn:@"name"];
        NSString *genter = [resultSet stringForColumn:@"gender"];
        int age = [resultSet intForColumn:@"age"];
        NSLog(@"Name:%@,Gender:%@,Age:%d",name,genter,age);
    }
    
    [dataBase close];
}
//条件查询
- (IBAction)queryByCondition:(id)sender {
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)  objectAtIndex:0];
    NSString *dbPath = [docPathstringByAppendingPathComponent:@"user.db"];
    FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
    if (![dataBase open]) {
        return ;
    }
    //    FMResultSet *resultSet = [dataBase executeQuery:@"select *from user where name = ?",@"ZY"];
    FMResultSet *resultSet = [dataBase executeQueryWithFormat:@"select * from user where name = %@",@"zy"];
    while ([resultSet next]) {
        NSString *name = [resultSet stringForColumnIndex:0];
        NSString *gender = [resultSet stringForColumn:@"gender"];
        int age = [resultSet intForColumn:@"age"];
        NSLog(@"Name:%@,Gender:%@,Age:%d",name,gender,age);
    }
    [dataBase close];
}
6 :条件删除的方法

- (IBAction)deleteByCondition:(id)sender
{
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)  objectAtIndex:0];
    NSString *dbPath = [docPathstringByAppendingPathComponent:@"user.db"];
    FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
    if (![dataBase open]) {
        return ;
    }
    BOOL delete = [dataBase executeUpdateWithFormat:@"delete from user where name = %@",@"zy"];
    if (delete) {
        UIAlertView *alert = [[UIAlertViewalloc]initWithTitle:@"提示"message:@"信息删除成功"delegate:selfcancelButtonTitle:nilotherButtonTitles:@"确定",nil];
        [alert show];
    }
    [dataBase close];
}

7.线程安全=========================

产考使用FMDB事务批量更新数据库速度问题里面的代码进行使用

在多个线程中同时使用一个FMDatabase实例是不明智的。不要让多个线程分享同一个FMDatabase实例,它无法在多个线程中同时使用。如果在多个线程中同时使用一个FMDatabase实例,会造成数据混乱等问题。所以,请使用 FMDatabaseQueue,它是线程安全的。以下是使用方法:
1. 创建

NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES).firstObject;
NSString *filePath = [path stringByAppendingPathComponent:@"FMDB.db"];
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:path];
2. 操作数据库

[queue inDatabase:^(FMDatabase*db) {
    //FMDatabase数据库操作
}];
 

- (IBAction)save:(id)sender {
     //获取Document文件夹下的数据库文件,没有则创建
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)  objectAtIndex:0];
    NSString *dbPath = [docPath stringByAppendingPathComponent:@"user.db"];
    NSLog(@"dbPath = %@",dbPath);
     //获取数据库并打开
    //  FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
    
    //多线程安全FMDatabaseQueue可以替代dataBase
    FMDatabaseQueue *dataBasequeue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
    
    [dataBasequeue inDatabase:^(FMDatabase *db) {
        
        
        if (![db open]) {
            NSLog(@"打开数据库失败");
            return ;
        }
        //创建表(FMDB中只有update和query操作,除了查询其他都是update操作)
        [db executeUpdate:@"create table if not exists user(name text,gender text,age integer) "];
        
        //常用方法有以下3种:
        //    - (BOOL)executeUpdate:(NSString*)sql, ...
        //    - (BOOL)executeUpdateWithFormat:(NSString*)format, ...
        //    - (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
        
        
        //插入数据
        BOOL inser = [db executeUpdate:@"insert into user values(?,?,?)",_nameTextField.text,_sexTextField.text,_ageTextField.text];
        if (inser) {
            UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"提示" message:@"信息保存成功" delegate:self cancelButtonTitle:nil otherButtonTitles:@"确定",nil];
            [alert show];
        }
//查询

[self.queue inDatabase:^(FMDatabase * _Nonnull db) {
        if (![db open]) {
            NSLog(@"打开数据库失败");
            return ;
        }
        FMResultSet *result= [db executeQuery:@"select *from user"];
        while ([result next]) {
            
           NSInteger age = [result intForColumn:@"age"];
            NSLog(@"age---%tu",age);
        }
    }];
 
  

 
  
        [db close];
    }];
}
7:而且可以轻松地把简单任务包装到事务里:
之所以将事务放到FMDB中去说并不是因为只有FMDB才支持事务,而是因为FMDB将其封装成了几个方法来调用,不用自己写对应的SQL而已,假如你要对数据库中的Stutent表插入新数据,那么该事务的具体过程是:开始新事物->插入数据->提交事务,那么当我们要往该表内插入500条数据,如果按常规操作处理就要执行500次“开始新事物->插入数据->提交事务”的过程。

好吧,今天的重点来了,举个例子:假如北京的一家A工厂接了上海一家B公司的500件产品的订单,思考一下:A工厂是生产完一件立即就送到B公司还是将500件产品全部生产完成后再送往B公司?答案肯定是后者,因为前者浪费了大量的时间、人力物力花费在往返于北京和上海之间。同样这个道理也能用在我们的数据库操作上,下面是我自己对使用事务和不使用事务的两种测试:
SQLite进行事务的SQL语句:

只要在执行SQL语句前加上以下的SQL语句,就可以使用事务功能了:
开启事务的SQL语句,"begin transaction;"
进行提交的SQL语句,"commit transaction;"
进行回滚的SQL语句,"rollback transaction;"
一: FMDatabase使用事务的方法:

-(void)transaction {
    NSDate *date1 = [NSDatedate];
    
    // 创建表
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)  objectAtIndex:0];
    NSString *dbPath = [docPathstringByAppendingPathComponent:@"mytable1.db"];
    NSLog(@"dbPath = %@",dbPath);
    FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
    
     // 注意这里的判断一步都不能少,特别是这里open的判断
    
    if (![dataBase open]) {
        NSLog(@"打开数据库失败");
        return ;
    }
    
    NSString *sqlStr =@"create table if not exists mytable1(num integer,name varchar(7),sex char(1),primary key(num));";
    BOOL res = [dataBase executeUpdate:sqlStr];
    if (!res) {
        NSLog(@"error when creating mytable1");
        
        [dataBase close];
    }
    
    // 开启事务
    [dataBase beginTransaction];
    BOOL isRollBack =NO;
    @try {
        for (int i = 0; i<500; i++) {
            NSNumber *num = @(i+1);
            NSString *name = [[NSStringalloc] initWithFormat:@"student_%d",i];
            NSString *sex = (i%2==0)?@"f":@"m";
            
            NSString *sql =@"insert into mytable1(num,name,sex) values(?,?,?);";
            BOOL result = [dataBase executeUpdate:sql,num,name,sex];
            if ( !result ) {
                NSLog(@"插入失败!");
                return;
            }
        }
    }
    @catch (NSException *exception) {
        isRollBack = YES;
        // 事务回退
        [dataBase rollback];
    }
    @finally {
        if (!isRollBack) {
            //事务提交
            [dataBase commit];
        }
    }
    
    [dataBase close];
    NSDate *date2 = [NSDatedate];
    NSTimeInterval a = [date2timeIntervalSince1970] - [date1timeIntervalSince1970];
    NSLog(@"FMDatabase使用事务插入500条数据用时%.3f秒",a);
}
二: FMDatabase不使用事务的方法:

//二: FMDatabase不使用事务的方法:

-(void)noTransaction {
    NSDate *date1 = [NSDatedate];
    
    // 创建表
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)  objectAtIndex:0];
    NSString *dbPath = [docPathstringByAppendingPathComponent:@"mytable3.db"];
    NSLog(@"dbPath = %@",dbPath);
    FMDatabase *dataBase = [FMDatabase databaseWithPath:dbPath];
    
     // 注意这里的判断一步都不能少,特别是这里open的判断
    
    if (![dataBase open]) {
        NSLog(@"打开数据库失败");
        return ;
    }
    
    NSString *sqlStr =@"create table if not exists mytable3(num integer,name varchar(7),sex char(1),primary key(num));";
    BOOL res = [dataBase executeUpdate:sqlStr];
    if (!res) {
        NSLog(@"error when creating mytable1");
        
        [dataBase close];
    }
    
    for (int i = 0; i<500; i++) {
        NSNumber *num = @(i+1);
        NSString *name = [[NSStringalloc] initWithFormat:@"student_%d",i];
        NSString *sex = (i%2==0)?@"f":@"m";
        
        NSString *sql =@"insert into mytable3(num,name,sex) values(?,?,?);";
        BOOL result = [dataBase executeUpdate:sql,num,name,sex];
        if ( !result ) {
            NSLog(@"插入失败!");
            return;
        }
    }
    
    
    [dataBase close];
    NSDate *date2 = [NSDatedate];
    NSTimeInterval a = [date2timeIntervalSince1970] - [date1timeIntervalSince1970];
    NSLog(@"FMDatabase不使用事务插入500条数据用时%.3f秒",a);
}

三: FMDatabaseQueue使用事务的方法:

//多线程事务
- (void)transactionByQueue {
    NSDate *date1 = [NSDatedate];
    
    // 创建表
    NSString *docPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)  objectAtIndex:0];
    NSString *dbPath = [docPathstringByAppendingPathComponent:@"mytable2.db"];
    
    
    //多线程安全FMDatabaseQueue可以替代dataBase
    FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:dbPath];
    
    //开启事务
    [queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        
        if(![db open]){
            
            return NSLog(@"事务打开失败");
        }
        
        NSString *sqlStr = @"create table mytable2(num integer,name varchar(7),sex char(1),primary key(num));";
        BOOL res = [db executeUpdate:sqlStr];
        if (!res) {
            NSLog(@"error when creating mytable2 table");
            
            [db close];
        }
        
        for (int i = 0; i<500; i++) {
            NSNumber *num = @(i+1);
            NSString *name = [[NSString alloc] initWithFormat:@"student_%d",i];
            NSString *sex = (i%2==0)?@"f":@"m";
            NSString *sql = @"insert into mytable2(num,name,sex) values(?,?,?);";
            BOOL result = [db executeUpdate:sql,num,name,sex];
            if ( !result ) {
                //当最后*rollback的值为YES的时候,事务回退,如果最后*rollback为NO,事务提交
                *rollback = YES;
                return;
            }
        }
        
        [db close];
    }];
    NSDate *date2 = [NSDatedate];
    NSTimeInterval a = [date2timeIntervalSince1970] - [date1timeIntervalSince1970];
    NSLog(@"FMDatabaseQueue使用事务插入500条数据用时%.3f秒",a);
}