iOS开发经验(10)-数据库
目录
- 数据库
1. 数据库-FMDB
一、简介
- 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
主键约束
- 简单说明
如果t_student表中就name和age两个字段,而且有些记录的name和age字段的值都一样时,那么就没法区分这些数据,造成数据库的记录不唯一,这样就不方便管理数据,良好的数据库编程规范应该要保证每条记录的唯一性,为此,增加了主键约束。也就是说,每张表都必须有一个主键,用来标识记录的唯一性。 - 什么是主键?
主键(Primary Key,简称PK)用来唯一地标识某一条记录。例如t_student可以增加一个id字段作为主键,相当于人的身份证
主键可以是一个字段或多个字段。 - 主键的设计原则
主键应当是对用户没有意义的 ;
永远也不要更新主键;
主键不应包含动态变化的数据;
主键应当由计算机自动生成; - 主键的声明
在创表的时候用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语句的种类(简单了解)
- 数据定义语句(DDL:Data Definition Language)
包括create和drop等操作
在数据库中创建新表或删除表(create table或 drop table)。 - 数据操作语句(DML:Data Manipulation Language)
包括insert、update、delete等操作
上面的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的类方法:
- 如果该路径下已经存在该数据库,直接获取该数据库;
- 如果不存在就创建一个新的数据库;
- 如果传@"",会在临时目录创建一个空的数据库,当数据库关闭时,数据库文件也被删除;
- 如果传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;
}
}
}];
}
数据库优化几个方面:
- sql语句优化
- 指定合适的数据类型
- 创建事务
- 改变sql语句,做分表查询,内存组合数据
- 不改变sql语句,添加索引,加快查询速度。
- 区分对待数据库串行模式和串行数据库操作队列