iOS开发经验(10)-数据库

目录

  1. 数据库
1. 数据库-FMDB

一、简介

  1. FMDB是iOS平台的SQLite数据库框架,它是以OC的方式封装了SQLite的C语言API,但是不能跨平台,它相对于cocoa自带的C语言框架有如下的优点:

使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码;
对比苹果自带的Core Data框架,更加轻量级和灵活;
提供了多线程安全的数据库操作方法,有效地防止数据混乱;

2.核心类

使用之前一样需要导入sqlite3.dylib,FMDB有三个主要的类:

FMDatabase: 一个FMDatabase对象就代表一个单独的SQLite数据库,用来执行SQL语句
FMResultSet: 使用FMDatabase执行查询后的结果集。注意:即使操作结果只有一行,也需要先调用 FMResultSet 的 next 方法。
FMDatabaseQueue: 用于在多线程中执行多个查询或更新,它是线程安全的

二、常用的sql语句及组合条件
关键字
注意:sql不区分大小写,数据库中不可以使用关键字来命名表、字段

在FMDB中,除查询(SELECTE)以外的所有操作都是"更新",包括CREATE、UPDATE、INSERT、WHERE、DELETE、DESC、ALTER、COMMIT、BEGIN、ORDER、BY、GROUP、DETACH、DROP、END、EXPLAIN、VACUUM、REPLACE。

数据参数
编写SQL语句时,应该使用? 作为变量值的占位符,? 是可以被SQLite识别的字符:
基本数据类型,如NSInteger 应该包装成NSNumber ,当对象可能是nil空时,应该使用[NSNull null];
通常情况下,你可以按照标准的SQL语句,用?表示执行语句的参数,然后,可以我们可以调用executeUpdate方法来将?所指代的具体参数传入,通常是用变长参数来传递进去的,如下:

NSString * sql=@"insert into User (name, password) values (?, ?)";
[db executeUpdate:sql,user.name,user.password];

这里需要注意的是,参数必须是NSObject的子类,所以象int,double,bool这种基本类型,需要封装成对应的包装类才行。

在SQLite3中可以不指定字段的数据类型,SQLite3会自动推断类型。
实际上SQLite是无类型的。即不管你在创表时指定的字段类型是什么,存储是依然可以存储任意类型的数据。而且在创表时也可以不指定字段类型。SQLite之所以什么类型就是为了良好的编程规范和方便开发人员交流,所以平时在使用时最好设置正确的字段类型!主键必须设置成integer。

**常用的格式有: **

文本: Text
整形: Integer
二进制数据: Blob
浮点型: Real Float、Double
布尔型: Boolean

时间型: Time
日期型: Date
时间戳: TimeStamp

以下这些修饰符都可以使用:

%@, %c, %s, %d, %D,%i, %u, %U, %hi, 
%hu, %qi, %qu, %f, %g, %ld, %lu, %lld, %llu

除此之外的修饰符可能导致无法预知的结果。
一些情况下,你需要在SQL语句中使用 % 字符,你应该使用 %%。

字段可以添加约束:

  • 主键:primary key。主键的值必须唯一,用于标识每一条记录,如学生的学号;主键同时也是一个索引,通过主键查找记录速度较快;主键如果是整数类型,该列的值可以自动增长;
  • 自增:autoincrement;
  • 非空:Not Null。字段值不能为空,否则报错;
  • 唯一:Unique。约束此字段值唯一。主键默认唯一;
  • 条件检查:Check。该字段值必须符合条件才能存入;
  • 默认值:Default。

补充:建立索引
如果资料表有相当多的资料,我们便会建立索引来加快速度。好比说:

create index film_title_index on film(title); 

意思是针对film资料表的name字段,建立一个名叫film_name_index的索引。这个指令的语法为

create index index_name on table_name(field_to_be_indexed); 

一旦建立了索引, sqlite3 会在针对该字段作查询时,自动使用该索引。这一切的操作都是在幕后自动发生的,无须使用者特别指令。

创建表单

//integer 整型 real 浮点数 text 文本字符串 blob 二进制数据
create table if not exists t_student(id integer,name text,age integer,score real);

删除表格

drop table if exists t_student ;

插入数据

//数据中的字符串类型应该用单引号''括住
insert into t_student (id,name,age,score) values (2010,'xbk',26,100);

更新数据

update t_student set age = 18,id = 2014,score = 99.0 where name = 'xbk';

删除数据
提示:
如果ID设置为逐渐,且设置为自动增长的话,那么把表中的数据删除后,重新插入新的数据,ID的编号不是从0开始,而是接着之前的ID进行编号。

//删除所有分数大于等于60分的学生数据信息
delete from t_student where score >= 60.0;

查询数据

//查询所有分数不低于60的学生信息
select * from t_sudent where score >= 60.0;
//查询特定字段 
select name,age from t_student where score > 60;

记录数据的数量

//示例
select count (age) from t_student ;
select count ( * ) from t_student where score >= 60;

排序
查询出来的结果可以用order by进行排序:

select * from t_student order by age ;

默认是按照升序排序(由小到大),也可以变为降序(由大到小)

select * from t_student order by age desc ;  //降序
select * from t_student order by age asc ;   // 升序(默认)

也可以用多个字段进行排序:先按照年龄排序(升序),年龄相等就按照身高排序(降序)

select * from t_student order by age asc, height desc ;

limit
使用limit可以精确地控制查询结果的数量,比如每次只查询10条数据:

//示例
select * from t_student limit 4, 8 ;

可以理解为:跳过最前面4条语句,然后取8条记录。
limit常用来做分页查询,比如每页固定显示5条数据,那么应该这样取数据

第1页:limit 0, 5
第2页:limit 5, 5
第3页:limit 10, 5

第n页:limit 5*(n-1), 5

这条语句的作用相当于select * 
select * from t_student limit 7 ;
表示取最前面的7条记录
from t_student limit 0, 7 ;

简单约束
建表时可以给特定的字段设置一些约束条件,常见的约束有

not null :规定字段的值不能为null
unique :规定字段的值必须唯一
default :指定字段的默认值

尽量给字段设置严格的约束,以保证数据的规范性
示例:

create table t_student (id integer, name text not null unique,    age integer not null default 1) ;
name字段不能为null,并且唯一
age字段不能为null,并且默认为1

主键约束

  1. 简单说明
    如果t_student表中就name和age两个字段,而且有些记录的name和age字段的值都一样时,那么就没法区分这些数据,造成数据库的记录不唯一,这样就不方便管理数据,良好的数据库编程规范应该要保证每条记录的唯一性,为此,增加了主键约束。也就是说,每张表都必须有一个主键,用来标识记录的唯一性。
  2. 什么是主键?
    主键(Primary Key,简称PK)用来唯一地标识某一条记录。例如t_student可以增加一个id字段作为主键,相当于人的身份证
    主键可以是一个字段或多个字段。
  3. 主键的设计原则
    主键应当是对用户没有意义的 ;
    永远也不要更新主键;
    主键不应包含动态变化的数据;
    主键应当由计算机自动生成;
  4. 主键的声明
    在创表的时候用primary key声明一个主键:
integer类型的id作为t_student表的主键
create table t_student (id integer primary key, name text, age integer) ;

只要声明为primary key,就说明是一个主键字段
主键字段默认就包含了not null 和 unique 两个约束
说明:如果想要让主键自动增长(必须是integer类型),应该增加autoincrement

create table t_student (id integer primary key autoincrement, name text, age integer) ;

外键约束
利用外键约束可以用来建立表与表之间的联系
外键的一般情况是:一张表的某个字段,引用着另一张表的主键字段
新建一个外键:

create table t_student (id integer primary key autoincrement, name text, age integer, class_id integer, constraint fk_student_class foreign key (class_id) references t_class (id));

t_student表中有一个叫做fk_t_student_class_id_t_class_id的外键
这个外键的作用是用t_student表中的class_id字段引用t_class表的id字段

表连接查询

表连接查询:需要联合多张表才能查到想要的数据
表连接的类型
内连接:inner join 或者 join  (显示的是左右表都有完整字段值的记录)
左外连接:left outer join (保证左表数据的完整性) 
示例
查询0316iOS班的所有学生
select s.name,s.age from t_student s, t_class c where s.class_id = c.id and c.name = ‘0316iOS’;

SQL语句的种类(简单了解)

  1. 数据定义语句(DDL:Data Definition Language)
    包括create和drop等操作
    在数据库中创建新表或删除表(create table或 drop table)。
  2. 数据操作语句(DML:Data Manipulation Language)
    包括insert、update、delete等操作
    上面的3种操作分别用于添加、修改、删除表中的数据。
  3. 数据查询语句(DQL:Data Query Language)
    可以用于查询获得表中的数据
    关键字select是DQL(也是所有SQL)用得最多的操作
    其他DQL常用的关键字有where,order by,group by和having。

条件语句
如果只想更新或者删除某些固定的记录,那就必须在DML语句后加上一些条件,条件语句的常见格式如下:

where 字段 = 某个值 ;   // 不能用两个 =
where 字段 is 某个值 ;   // is 相当于 = 
where 字段 != 某个值 ; 
where 字段 is not 某个值 ;   // is not 相当于 != 
where 字段 > 某个值 ; 
where 字段1 = 某个值 and 字段2 > 某个值 ;  // and相当于C语言中的 &&
where 字段1 = 某个值 or 字段2 = 某个值 ;  //  or 相当于C语言中的 ||
//示例
将t_student表中分数在60-80之间 并且 姓名不等于xbk的记录,分数都改为 100
update t_student set score = 100 where score >= 60 and score <=80 and name != ‘xbk’ ;

创建数据库
创建数据库使用到的是FMDatabase的类方法:

  1. 如果该路径下已经存在该数据库,直接获取该数据库;
  2. 如果不存在就创建一个新的数据库;
  3. 如果传@"",会在临时目录创建一个空的数据库,当数据库关闭时,数据库文件也被删除;
  4. 如果传nil,会在内存中临时创建一个空的数据库,当数据库关闭时,数据库文件也被删除;

使用executeUpdate:方法执行更新
返回值为BOOL类型。若返回false则说明发生了错误,可以调用lastErrorMessage和lastErrorCode方法以查看错误信息。

执行更新的SQL语句,不确定的参数用?来占位。字符串里面的"?",依次用后面的参数替代,必须是对象,不能是int等基本类型。
- (BOOL)executeUpdate:(NSString *)sql,... ;
 执行更新的SQL语句,可以使用字符串的格式化进行构建SQL语句。不确定的参数用%@、%d等来占位
- (BOOL)executeUpdateWithFormat:(NSString*)format,... ;
 执行更新的SQL语句,字符串中有"?",依次用arguments的元素替代
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments;

创建并打开数据库
在和数据库交互 之前,数据库必须是打开的。如果资源或权限不足无法打开或创建数据库,都会导致打开失败。

+ (FMDatabase *)openDataBase
{
    if (sqlite) {
        return sqlite;
    }
     //获取Document文件夹下的数据库文件,没有则创建
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    
    NSString *databasePath = [path stringByAppendingPathComponent:@"DB.sqlite"];
    
    NSLog(@"数据库路径 = %@",databasePath);
    
    //注意:这里创建数据库,并不会打开数据库,和SQLite有点区别
    sqlite = [FMDatabase databaseWithPath:databasePath];
    
    // 通过路径查询到文件 如果文件存在,打开;不存在先进行创建,再打开
    if ([sqlite open]) {
        //创建表单-创建sql语句
        NSString *sql = @"CREATE TABLE IF NOT EXISTS STUDENT(stu_ID INTEGER PRIMARY KEY,name TEXT NOT NULL,sex TEXT NOT NULL DEFAULT 妖怪,age INTEGER DEFAULT 18,store FLOAT)";
        //        NSString *sql = @"CREATE TABLE IF NOT EXISTS STUDENT(name TEXT,sex TEXT DEFAULT 妖怪,age INTEGER DEFAULT 18,store FLOAT)";
        
        
        //执行sql语句,创建表(FMDB中只有update和query操作,除了查询其他都是update操作)
        bool result = [sqlite executeUpdate:sql];
        if (result) {
            NSLog(@"成功创表");
            
        }else
        {
            NSLog(@"创表失败");
            
        }
    }
    
    return sqlite;
}

对表的属性怎么删改
语法:

BOOL result = [db executeUpdate:sql]

1.如需在表中添加列,请使用下列语法:

ALTER TABLE table_name ADD column_name datatype

2.要删除表中的列,请使用下列语法:

ALTER TABLE table_name DROP COLUMN column_name

3.更新表中列的属性,请使用下列语法:

ALTER TABLE table_name MODIFY COLUMN column_name datatype

插入数据

插入数据类型必须为NSObject 的子类
基本类型需要封装为对应的包装类
支持占位符,后添加再数据

-(void)addData{
    NSDictionary *dic = @{
                          @"code": @0,
                          @"msg": @"success",
                          @"data": @{
                                  @"course": @{
                                          @"id": @24,
                                          @"crowdId": @4,
                                          @"ccode": @"KIa8nNpVmc",
                                          @"cname": @"健健康康",
                                          @"coverImg": @"http://ocd2lp9uj.bkt.clouddn.com/FaceQ1445612150222.jpg",
                                          @"description": @"淋漓尽致",
                                          @"speaker": @2,
                                          @"speakerName": @"刘欣成",
                                          @"speakerHeadIcon": @"http://ocd2lp9uj.bkt.clouddn.com/FaceQ1445612150222.jpg",
                                          @"startTime": @"2016-10-15 11:21:44",
                                          @"endTime": @"2016-10-15 11:26:50",
                                          @"liveStatus": @"2",
                                          @"saveStatus": @"2"
                                          }
                                  }
                          };
    NSString *json = nil;
    NSError *error;
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dic
                                                       options:NSJSONWritingPrettyPrinted
                                                         error:&error];
    json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

    
    if ([sqlite open]){
        NSString *insertSql = @"insert into 't_down_course'(fileName,courseInfo) values(?,?)";
        BOOL result = [sqlite executeUpdate:insertSql,@"fileName",json];
        if (result){
            NSLog(@"添加数据成功");
        }else{
            NSLog(@"添加数据失败");
        }
        [sqlite close];
    }else{
        NSLog(@"打开数据库 --- 失败");
    }
    
    
    //插入也支持以字典的方式
    
    //字典1
    NSString *comment = @"good";
    int identifier = 20;

    NSString *name = @"name";

    NSString *date = @"date";

    NSDictionary *arguments = @{
                                @"identifier": @(identifier),
                                @"name": name,
                                @"date": date,
                                @"comment": comment ?: [NSNull null]};
    BOOL success = [sqlite executeUpdate:@"INSERT INTO authors (identifier, name, date, comment) VALUES (:identifier, :name, :date, :comment)" withParameterDictionary:arguments];
    if (!success) {
        NSLog(@"error = %@", [sqlite lastErrorMessage]);
    }
    
    //字典2
    NSDictionary *argsDict = [NSDictionary dictionaryWithObjectsAndKeys:@"My Name", @"name", nil];
    [sqlite executeUpdate:@"INSERT INTO myTable (name) VALUES (:name)" withParameterDictionary:argsDict];
}

关闭数据库
每次操作完成,应该关闭数据库连接来释放SQLite使用的资源。

+ (FMDatabase *)closeDataBase
{
    [sqlite close];
    return sqlite;
}

插入

+ (void)insertAction:(DataBaseModel *)model
{
    sqlite = [self openDataBase];
    if ([sqlite open]) {
        //1. 直接使用完整的SQL更新语句 */

        //1.1
        bool result = [sqlite executeUpdate:@"INSERT INTO STUDENT (name,sex,age,store) VALUES (?,?,?,?);", model.name,model.sex,model.age,model.score];
        //1.2
        [sqlite executeUpdate:@"INSERT INTO STUDENT (name,sex,age,store) VALUES 'liuting','男',20,29);"];
        
        
        // 2.使用不完整的SQL更新语句,里面含有待定字符串"?",需要后面的参数进行替代
        
        //2.1
        NSString *sql = @"INSERT INTO STUDENT (name,sex,age,store) VALUES (?,?,?,?);";
        [sqlite executeUpdate:sql,@"liuting1",@"男",@30,@10];
        
        // 2.2 使用不完整的SQL更新语句,里面含有待定字符串"?",需要数组参数里面的参数进行替代
        [sqlite executeUpdate:sql
           withArgumentsInArray:@[@"liuting1",@"男",@30,@10]];
        
        
        // 3.SQL语句字符串可以使用字符串格式化,这种我们应该比较熟悉
        
        [sqlite executeUpdateWithFormat:@"INSERT INTO STUDENT (name,sex,age,store) VALUES(%@,%@,%@,%@);",@"liuting1",@"男",@30,@10];
        
        
        
        if (result) {
            NSLog(@"插入成功");
            
        }else
        {
            NSLog(@"插入失败");
        }
    }
    [self closeDataBase];
}

修改

+ (void)modifyAction:(DataBaseModel *)model
{
    
    sqlite = [self openDataBase];
    if ([sqlite open]) {
        /*
        NSString *modifySql = @"UPDATE STUDENT SET store = '20' WHERE name = '哈4'";

        bool result = [sqlite executeUpdate:modifySql];
        */
        
        /*
        bool result = [sqlite executeUpdate:@"UPDATE STUDENT SET store = ? WHERE name = ?;", @20, @"哈5"];
        */
        BOOL result = [sqlite executeUpdate:@"UPDATE STUDENT SET name = 'liwx' WHERE age > 12 AND age < 15;"];
        
        
        if (result) {
            NSLog(@"修改成功");
            
        }else
        {
            NSLog(@"修改失败");
            
        }
    }
    [self closeDataBase];
    
}

删除

+ (void)deleteAction:(DataBaseModel *)model
{
    NSString *deleteSql = @"DELETE FROM STUDENT WHERE name = '哈5'";
    
    
    if (model.name == nil) {
        deleteSql = @"DELETE FROM STUDENT";
    }
    
    sqlite = [self openDataBase];
    if ([sqlite open]) {
        
        /* 1
        BOOL result = [sqlite executeUpdate:deleteSql];
         */
        
        
        /* 2
        BOOL result = [sqlite executeUpdate:@"DELETE FROM STUDENT WHERE age > 20 AND age < 25;"];
         */
        
        /* 3
        
        BOOL result = [sqlite executeUpdateWithFormat:@"DELETE FROM STUDENT WHERE name = %@",model.name];
         */

        
        //4
        BOOL result = [sqlite executeUpdate:@"DELETE FROM STUDENT WHERE age > ? AND age < ?;",model.age];

        if (result) {
            NSLog(@"删除成功");
            
        }else
        {
            NSLog(@"删除失败");
            
        }
    }
    [self closeDataBase];
}

查询
处理结果FMResultSet的常用方法,FMDB提供如下多个方法来获取不同类型的数据:

/* 获取下一个记录 */
- (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;

上述每一个方法都对应着一个{type}ForColumnIndex:方法,该方法通过列的索引来获取数据,与通过列名获取数据效果一样。
这些方法也都有一个{类型} ForColumnIndex:,用于检索数据基于列的位置的结果,而不是列的名称。

NSString* name = [rs stringForColumnIndex:0];

这些方法也都包括{type}ForColumnIndex 的这样子的方法,参数是查询结果集的列的索引位置。
你无需调用 [FMResultSet close]来关闭结果集, 当新的结果集产生,或者其数据库关闭时,会自动关闭。

查询方法也有3种


 // 全部查询
 - (FMResultSet *)executeQuery:(NSString*)sql, ...

 // 条件查询
 - (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...

 - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments
+ (void)quaryAction_one:(DataBaseModel *)model
{
    NSMutableArray *arrM = [NSMutableArray array];
    
    NSString *querySql = [NSString stringWithFormat:@"SELECT * FROM STUDENT WHERE name LIKE '%%%@%%' OR store LIKE '%%%@%%'", model.name, model.score];
    
    
    if (model.name == nil) {
        //查询全部
        querySql = @"SELECT * FROM STUDENT;";
    }
    
    sqlite = [self openDataBase];
    if ([sqlite open]) {
        //条件查询
        
        /*
        FMResultSet *result = [sqlite executeQuery:@"SELECT id, name, age FROM STUDENT WHERE age > 25;"];
         */
        
        /*
        FMResultSet *resultSet = [sqlite executeQuery:@"SELECT *FORM STUDENT WHERE name = ?",@"哈8"];
         */
        /*
        FMResultSet *resultSet = [sqlite executeQueryWithFormat:@"SELECT *FORM STUDENT WHERE name = %@",model.name];
         */
        
        //1.执行查询
        FMResultSet *set = [sqlite executeQuery:querySql];
        
        
        //2.遍历结果集
        while ([set next]) {
            
            //NSString *name1 = [result stringForColumnIndex:1];
            
            NSString *name = [set stringForColumn:@"name"];
            NSString *sex = [set stringForColumn:@"sex"];
            
            NSNumber *age = [NSNumber numberWithInteger:[[set stringForColumn:@"age"] integerValue]] ;
            NSNumber *store =  [NSNumber numberWithInteger:[[set stringForColumn:@"store"] floatValue]] ;;
            
            DataBaseModel *modal = [DataBaseModel initializeWithName:name Sex:sex Age:age Score:store];
            [arrM addObject:modal];
        }

        for (DataBaseModel *aaa in arrM) {
            NSLog(@"%@,%@",aaa.name,model.score);
        }
    }
    [self closeDataBase];
}

** FMDatabaseQueue-线程安全**
FMDatabase这个类是线程不安全的,如果在多个线程中同时使用一个FMDatabase实例,会造成数据混乱等问题
为了保证线程安全,FMDB提供方便快捷的FMDatabaseQueue类。
如果应用中使用了多线程操作数据库,那么就需要使用FMDatabaseQueue来保证线程安全了。 应用中不可在多个线程中共同使用一个FMDatabase对象操作数据库,这样会引起数据库数据混乱。 为了多线程操作数据库安全,FMDB使用了FMDatabaseQueue,使用FMDatabaseQueue很简单,首先用一个数据库文件地址来初使化FMDatabaseQueue,然后就可以将一个闭包(block)传入inDatabase方法中。 在闭包中操作数据库,而不直接参与FMDatabase的管理。
FMDatabaseQueue虽然看似一个队列,实际上它本身并不是,它通过内部创建一个Serial的dispatch_queue_t来处理通过inDatabase和inTransaction传入的Blocks,所以当我们在主线程(或者后台)调用inDatabase或者inTransaction时,代码实际上是同步的。FMDatabaseQueue这么设计的目的是让我们避免发生并发访问数据库的问题,因为对数据库的访问可能是随机的(在任何时候)、不同线程间(不同的网络回调等)的请求。内置一个Serial队列后,FMDatabaseQueue就变成线程安全了,所有的数据库访问都是同步执行,而且这比使用@synchronized或NSLock要高效得多。

FMDatabaseQueue,后台会建立系列化的GCD队列,并执行你传给GCD队列的块。这意味着你从多线程同时调用调用方法,GDC也会按它接收的块的顺序来执行。

但是这么一来就有了一个问题:如果后台在执行大量的更新,而主线程也需要访问数据库,虽然要访问的数据量很少,但是在后台执行完之前,还是会阻塞主线程。

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数据库操作
}];

3.本文的使用实例
创建并打开数据库

+ (void)initialize
{
    if (sqlite) {
        return ;
    }
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    
    NSString *databasePath = [path stringByAppendingPathComponent:@"DB_queue.sqlite"];
    
    NSLog(@"数据库路径 = %@",databasePath);
    
    // 创建一个FMDatabaseQueue对象
    // 只要创建数据库队列对象, FMDB内部就会自动给我们加载数据库对象
    
    sqlite = [FMDatabaseQueue databaseQueueWithPath:databasePath];
    
    // 会通过block传递队列中创建好的数据库
    [sqlite inDatabase:^(FMDatabase *db) {
        
        BOOL success = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS STUDENT(stu_ID INTEGER PRIMARY KEY,name TEXT NOT NULL,sex TEXT NOT NULL DEFAULT 妖怪,age INTEGER DEFAULT 18,store FLOAT)"];
        
        if (success) {
            NSLog(@"创建表成功");
        } else {
            NSLog(@"创建表失败");
        }
    }];
}

插入

+ (void)insertAction:(DataBaseModel *)model
{
    [sqlite inDatabase:^(FMDatabase *db) {
        bool result = [db executeUpdate:@"INSERT INTO STUDENT (name,sex,age,store) VALUES (?,?,?,?);", model.name,model.sex,model.age,model.score];
        if (result) {
            NSLog(@"插入成功");
        } else {
            NSLog(@"插入失败");
        }
    }];
}

修改数据

+ (void)modifyAction:(DataBaseModel *)model
{
    [sqlite inDatabase:^(FMDatabase *db) {
        
//        BOOL result = [db executeUpdate:@"UPDATE STUDENT SET name = 'liwx' WHERE age > 12 AND age < 15;"];
        BOOL result = [db executeUpdate:@"UPDATE STUDENT SET name = ? WHERE age > 12 AND age < 15;",@"哈哈哈"];

        // 判断是否SQL是否执行成功
        // 判断是否SQL是否执行成功
        if (result) {
            NSLog(@"修改成功");
        } else {
            NSLog(@"修改失败");
        }
    }];
}

删除数据

+ (void)deleteAction:(DataBaseModel *)model
{
    [sqlite inDatabase:^(FMDatabase *db) {
        
//        BOOL result = [db executeUpdate:@"DELETE FROM STUDENT WHERE age > 20 AND age < 25;"];
        BOOL result = [db executeUpdate:@"DELETE FROM STUDENT WHERE name = ?;",@"哈1"];

        
        // 判断是否SQL是否执行成功
        if (result) {
            NSLog(@"删除成功");
        } else {
            NSLog(@"删除失败");
        }
    }];
}

查询

+ (void)quaryAction:(DataBaseModel *)model
{
//    这些方法都有一个 {type}ForColumnIndex: 变体,是基于列的位置来查询数据。
//    
//    通常情况下,一个 FMResultSet 没有必要手动 -close,因为结果集合 (result set) 被释放或者源数据库关闭会自动关闭。
//    

    [sqlite inDatabase:^(FMDatabase *db) {
        
//        FMResultSet *result = [db executeQuery:@"SELECT store, name, age FROM STUDENT WHERE age > 25;"];
        FMResultSet *result = [db executeQuery:@"SELECT store, name, age FROM STUDENT WHERE age > ?;",@25];

        
        while ([result next]) {
            float store = [result intForColumnIndex:0];
            NSString *name = [result stringForColumnIndex:1];
            int age = [result intForColumn:@"age"];
            
            NSLog(@"ID: %.2f, name: %@, age: %zd", store, name, age);
        }
    }];
}

事务
事务,是指作为单个逻辑工作单元执行的一系列操作,要么完整地执行,要么完全地不执行。

想象一个场景,比如你要更新数据库的大量数据,我们需要确保所有的数据更新成功,才采取这种更新方案,如果在更新期间出现错误,就不能采取这种更新方案了,如果我们不使用事务,我们的更新操作直接对每个记录生效,万一遇到更新错误,已经更新的数据怎么办?难道我们要一个一个去找出来修改回来吗?怎么知道原来的数据是怎么样的呢?这个时候就需要使用事务实现。

再举个例子:假如北京的一家A工厂接了上海一家B公司的500件产品的订单,思考一下:A工厂是生产完一件立即就送到B公司还是将500件产品全部生产完成后再送往B公司?答案肯定是后者,因为前者浪费了大量的时间、人力物力花费在往返于北京和上海之间。同样这个道理也能用在我们的数据库操作上。

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

只要在执行SQL语句前加上以下的SQL语句,就可以使用事务功能了:

开启事务的SQL语句,"begin transaction;"
进行提交的SQL语句,"commit transaction;"
进行回滚的SQL语句,"rollback transaction;"

一: FMDatabase使用事务的方法:

//事务
+ (void)transaction {
    
    sqlite = [self openDataBase];
    if ([sqlite open]) {
        // 开启事务
        [sqlite beginTransaction];
        BOOL isRollBack = NO;
        @try {
            for (int i = 0; i<500; i++) {
                NSString *name = [[NSString alloc] initWithFormat:@"student_%d",i];
                NSString *sex = (i%2==0)?@"f":@"m";
                NSNumber *age = @(i+1);

                NSNumber *store = @(i+1);
//
//                NSString *sql = @"INSERT INTO STUDENT (name,sex,age,store) VALUES (?,?,?,?);";
//                BOOL result = [sqlite executeUpdate:sql,name,sex,age,store];
                
//                bool result = [sqlite executeUpdate:@"INSERT INTO STUDENT (name,sex,age,store) VALUES (?,?,?,?);", name,sex,age,store];
                
                
                NSString *sql = @"INSERT INTO STUDENT (name,sex,age,store) VALUES (?,?,?,?);";
                BOOL result = [sqlite executeUpdate:sql,name,sex,age,store];

                
                if (result) {
                    NSLog(@"插入成功");
                    
                }else
                {
                    NSLog(@"插入失败");
                    return;

                }
            }
        }
        @catch (NSException *exception) {
            isRollBack = YES;
            // 事务回退
            [sqlite rollback];
        }
        @finally {
            if (!isRollBack) {
                //事务提交
                [sqlite commit];
            }
        }
    }
    
    [self closeDataBase];
}
//多线程事务
+ (void)transactionByQueue {
    //开启事务
    [sqlite inTransaction:^(FMDatabase *db, BOOL *rollback) {
        for (int i = 0; i<500; i++) {
            NSString *name = [[NSString alloc] initWithFormat:@"student_%d",i];
            NSString *sex = (i%2==0)?@"f":@"m";
            NSNumber *age = @(i+1);
            
            NSNumber *store = @(i+1);
            //
            NSString *sql = @"INSERT INTO STUDENT (name,sex,age,store) VALUES (?,?,?,?);";
            BOOL result = [db executeUpdate:sql,name,sex,age,store];
            if ( !result ) {
                //当最后*rollback的值为YES的时候,事务回退,就回滚数据。如果最后*rollback为NO,事务提交
                *rollback = YES;
                return;
            }
        }
    }];
}

数据库优化几个方面:

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

推荐阅读更多精彩内容