目录
一. 数据库的介绍
- 常用的数据库
- 数据库存储数据的方式
- 表的操作
二. sqlite3(FMDatabase)
- 创建导航, 在视图控制器中创建tableView
- 创建模型类
- 创建数据库管理的单例类, 其中包含"初始化数据库对象"的方法
- 增: 向数据库中添加一条数据
- 查: 查询数据
- 改: 修改数据
- 左滑删除
三. 数据库的事务(Transaction)
- 一个数据库事务通常包含了一个序列的对数据库的读/写操作。它的存在包含有以下两个目的:
- ACID性质
- 假设我们要向数据库中添加10000条简单数据
- 是否使用Transaction
- 使用事务的测试耗时为0.1s左右, 未使用事务的耗时约为30s
四. EGORefresh
在Day15的爱限免项目的"LimitFreeController.m"中添加上拉下拉刷新功能
一. 数据库的介绍
数据库是用来存储数据的文件
在没有网络的情况下, 需要显示数据, 在客户端本地加一个数据库文件, 用来存储数据到本地
数据库文件存储的数据相对比较多, 数据的组织更有规律, 操作起来更方便
-
常用的数据库
客户端: SQLite3
服务器: Oracle(Sun), Sybase, SQLserver, MySQL, DB2
-
数据库存储数据的方式
表(table): 用来存储同类数据, 通常对应一个模型类
记录: 表格中的一行
-
表的操作
- 创建表格:
create table student (name varchar(255), age integer);
表格名字:student
表格中有两个字段, 字段名字是name和age
字段类型:name类型varchar(255) -> 字符串 age类型integer -> int - 增:
insert into student(name,age) values("张三",30) - 删:
delete from student where name = "张三" - 改:
update student set age=40 where name = "张三" - 查:
select * from student where name = "张三"
- 创建表格:
二. sqlite3(FMDatabase)
sqlite3数据库文件是C语言, 使用起来较为麻烦
FMDatabase 用对象的方式来操作数据库(需要采用非ARC编译, 链接libsqlite3.dylib)
-
创建导航, 在视图控制器中创建tableView
-
创建模型类
-
创建数据库管理的单例类, 其中包含"初始化数据库对象"的方法
-
DBManager.m
#import "DBManager.h" #import "FMDatabase.h" @implementation DBManager { // 数据库对象 FMDatabase *_database; } // 获取单例 + (DBManager *)sharedInstance { static DBManager *manager = nil; @synchronized(self) { if (nil == manager) { manager = [[DBManager alloc] init]; } } return manager; } // 重新实现初始化方法 - (instancetype)init { self = [super init]; if (self) { // 初始化数据库对象 [self createDataBase]; } return self; } // 初始化数据库对象 - (void)createDataBase { NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/user.db"]; NSLog(@"path: %@", path); // 1. 初始化数据库对象 _database = [[FMDatabase alloc] initWithPath:path]; // 2. 打开数据库 BOOL ret = [_database open]; if (!ret) { NSLog(@"数据库打开失败"); } else { // 成功打开数据库 // 创建表 #warning if not exists // if not exists 表格不存在时才创建 // primary key 表示是主键, 主键的值唯一 // autoincrement 主键值会自动增长, 不需要代码设置值 NSString *createSql = @"create table if not exists user(userId integer primary key autoincrement,username varchar(255),age integer,headImage blob)"; BOOL flag = [_database executeUpdate:createSql]; if (!flag) { NSLog(@"创建表失败: %@", _database.lastErrorMessage); } } }
-
-
增: 向数据库中添加一条数据
-
DBManager.m
- (void)addUserModel:(UserModel *)model
{
// sql
// "?": 占位符, 表示需要一个参数
NSString *insertSql = @"insert into user(username,age,headImage) values (?,?,?)";
// 数据库存储图片的是二进制类型
NSData *data = UIImagePNGRepresentation(model.headImage);
BOOL ret = [_database executeUpdate:insertSql, model.username, @(model.age), data];
if (!ret) {
NSLog(@"添加记录失败: %@", _database.lastErrorMessage);
}
} -
DetailViewController.m中点击按钮"保存"调用的方法
- (void)saveAction:(id)sender
{
// 获取对象属性值内容
UserModel *model = [[UserModel alloc] init];
model.username = _nameTextField.text;
model.age = _ageTextField.text.intValue;
#warning 获取背景图片
model.headImage = [_imageBtn backgroundImageForState:UIControlStateNormal];// 保存 // 添加 DBManager *manager = [DBManager sharedInstance]; [manager addUserModel:model]; }
-
-
查: 查询数据
-
DBManager.m
- (NSArray *)searchAllUsers
{
// sql
// select * from user where userId = 10;
NSString *selectSql = @"select * from user";// 执行查询操作 FMResultSet *rs = [_database executeQuery:selectSql]; // 遍历结果集里面的数据, 放到数组中 NSMutableArray *array= [NSMutableArray array]; while ([rs next]) { // 获取当前的这一条记录 // 创建对象 UserModel *model = [[UserModel alloc] init]; model.userId = [rs intForColumn:@"userId"]; model.username = [rs stringForColumn:@"username"]; model.age = [rs intForColumn:@"age"]; // 头像(图片的二进制数据) NSData *data = [rs dataForColumn:@"headImage"]; model.headImage = [UIImage imageWithData:data]; [array addObject:model]; } return array; }
-
ViewController.m
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];// 查询数据 NSArray *array = [[DBManager sharedInstance] searchAllUsers]; _dataArray = [NSMutableArray arrayWithArray:array]; // 刷新表格 [self.tableView reloadData]; }
-
-
改: 修改数据
-
DBManager.m
- (void)updateUserId:(int)userId model:(UserModel *)model
{
// 判断数据是否存在
BOOL flag = [self isUserExists:userId];
if (flag == 0) {
NSLog(@"数据不存在");
return;
}// sql NSString *sql = @"update user set username=?,age=?,headImage=? where userId=?"; // 修改 NSData *data = UIImagePNGRepresentation(model.headImage); BOOL ret = [_database executeUpdate:sql,model.username,@(model.age),data,@(userId)]; if (!ret) { NSLog(@"修改数据失败: %@", _database.lastErrorMessage); } } // 判断一条记录是否存在 - (BOOL)isUserExists:(int)userId { // sql NSString *sql = @"select * from where userId=?"; FMResultSet *rs = [_database executeQuery:sql, @(userId)]; if ([rs next]) { return YES; } return NO; }
-
DetailViewController.h中添加属性
@property (nonatomic, strong) UserModel *model;
在DetailViewController.m中:
- (void)viewDidLoad {
………………………………………………
// 如果是修改界面
if (self.model) {
_nameTextField.text = self.model.username;
_ageTextField.text = [NSString stringWithFormat:@"%d", self.model.age];
[_imageBtn setBackgroundImage:self.model.headImage forState:UIControlStateNormal];
}
}-
保存按钮调用的方法需要修改
- (void)saveAction:(id)sender
{
………………………………………………// 保存 if (self.model) { // 修改 [[DBManager sharedInstance] updateUserId:self.model.userId model:model]; } else { // 添加 DBManager *manager = [DBManager sharedInstance]; [manager addUserModel:model]; } }
-
-
ViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// 进入修改界面
DetailViewController *dCtrl = [[DetailViewController alloc] init];
dCtrl.model = self.dataArray[indexPath.row];
[self.navigationController pushViewController:dCtrl animated:YES];
}
-
-
左滑删除
-
DBManager.m中
// 删除一条数据 - (void)deleteUserId:(int)userId { // sql NSString *deleteSql = @"delete from user where userId = ?"; // 执行删除操作 BOOL ret = [_database executeUpdate:deleteSql, @(userId)]; if (!ret) { NSLog(@"删除数据失败: %@", _database.lastErrorMessage); } }
-
ViewController.m中
// 左滑删除 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { // 数据库删除 UserModel *model = self.dataArray[indexPath.row]; [[DBManager sharedInstance] deleteUserId:model.userId]; // 删除数据 [_dataArray removeObjectAtIndex:indexPath.row]; [_tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; }
-
三. 数据库的事务(Transaction)
摘录自维基百科
1. 一个数据库事务通常包含了一个序列的对数据库的读/写操作。它的存在包含有以下两个目的:
- 为数据库操作序列提供了一个从失败中恢复到正常状态的方法,同时提供了数据库即使在异常状态下仍能保持一致性的方法。
- 当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
当事务被提交给了DBMS(数据库管理系统),则DBMS(数据库管理系统)需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态;同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。
但在现实情况下,失败的风险很高。在一个数据库事务的执行过程中,有可能会遇上事务操作失败、数据库系统/操作系统失败,甚至是存储介质失败等情况。这便需要DBMS对一个执行失败的事务执行恢复操作,将其数据库状态恢复到一致状态(数据的一致性得到保证的状态)。为了实现将数据库状态恢复到一致状态的功能,DBMS通常需要维护事务日志以追踪事务中所有影响数据库数据的操作。
2. ACID性质
并非任意的对数据库的操作序列都是数据库事务。数据库事务拥有以下四个特性,习惯上被称之为ACID特性。
- 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
-
一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
-
隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
-
持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。
-
假设我们要向数据库中添加10000条简单数据
#import "ViewController.h" #import "FMDatabase.h" @interface ViewController () { FMDatabase *_database; } @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //数据库的事务 //有时候操作很多条sql语句时,会执行很长的时间,我们把这些语句放到一个事务中,可以提高效率 NSString *path = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/test.db"]; _database = [[FMDatabase alloc] initWithPath:path]; BOOL flag = [_database open]; if (!flag) { NSLog(@"打开数据库失败"); }else{ //创建表 NSString *sql = @"create table if not exists user(username varchar(255),age integer)"; BOOL ret = [_database executeUpdate:sql]; if (!ret) { NSLog(@"创建表失败"); } } // 插入数据(transaction) NSDate *date1 = [NSDate date]; [self insertDataWithNum:10000 isUseTransaction:YES]; NSDate *date2 = [NSDate date]; NSTimeInterval time = [date2 timeIntervalSinceDate:date1]; NSLog(@"%lf", time); // 插入数据(非transaction) NSDate *date3 = [NSDate date]; [self insertDataWithNum:10000 isUseTransaction:NO]; NSDate *date4 = [NSDate date]; NSTimeInterval time2 = [date4 timeIntervalSinceDate:date3]; NSLog(@"%lf", time2); }
-
是否使用Transaction
/* @param num:插入的条数 @param useTransaction:是否使用事物 */ - (void)insertDataWithNum:(int)num isUserTransaction:(BOOL)useTransaction { if (useTransaction) { //使用事物 BOOL isError = NO; @try { //开启事物 [_database beginTransaction]; //执行插入操作 for (int i=0; i<num; i++) { NSString *name = [NSString stringWithFormat:@"第%d个用户",i]; int age = arc4random()%20+20; NSString *insertSql = @"insert into user (username,age) values (?,?)"; BOOL ret = [_database executeUpdate:insertSql,name,@(age)]; if (!ret) { NSLog(@"%@",_database.lastErrorMessage); } } } @catch (NSException *exception) { //出错 isError = YES; //回滚 [_database rollback]; } @finally { if (!isError) { //提交事物 NSLog(@"success"); [_database commit]; } } }else{ //不使用事物 for (int i=0; i<num; i++) { NSString *name = [NSString stringWithFormat:@"第%d个用户",i]; int age = arc4random()%20+20; NSString *insertSql = @"insert into user (username,age) values (?,?)"; BOOL ret = [_database executeUpdate:insertSql,name,@(age)]; if (!ret) { NSLog(@"%@",_database.lastErrorMessage); } } } } @end
-
使用事务的测试耗时为0.1s左右, 未使用事务的耗时约为30s
四. EGORefresh
-
在Day15的爱限免项目的"LimitFreeController.m"中添加上拉下拉刷新功能
@interface LimitFreeController () <…………………………………………………………, EGORefreshTableDelegate> ………………………………………………………… // 下拉刷新 @property (nonatomic, strong) EGORefreshTableHeaderView *headView; @property (nonatomic, strong) EGORefreshTableFooterView *footView; @property (nonatomic, assign) NSInteger curPage; // 是否正在加载 @property (nonatomic, assign) BOOL isLoading; @end @implementation LimitFreeController - (void)viewDidLoad { ………………………………………………………… _curPage = 1; } - (void)downloadData { self.isLoading = YES; NSURLConnection *conn = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:kUrl, self.curPage]]] delegate:self]; // 遵守协议, 实现方法 } // 创建表格视图 - (void)createTableView { ………………………………………………………… // 下拉刷新 _headView = [[EGORefreshTableHeaderView alloc] initWithFrame:CGRectMake(0, -_tbView.bounds.size.height, _tbView.bounds.size.width, _tbView.bounds.size.height)]; _headView.delegate = self; [_tbView addSubview:self.headView]; _footView = [[EGORefreshTableFooterView alloc] initWithFrame:CGRectZero]; _footView.delegate = self; [self.tbView addSubview:self.footView]; } // 修改footer的frame - (void)resetFooterFrame { CGFloat height = MAX(self.tbView.bounds.size.height, self.tbView.contentSize.height); self.footView.frame = CGRectMake(0, height, self.tbView.bounds.size.width, 0); } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self resetFooterFrame]; } #pragma mark - EGORefreshTable代理 - (BOOL)egoRefreshTableDataSourceIsLoading:(UIView *)view { return self.isLoading; } - (NSDate *)egoRefreshTableDataSourceLastUpdated:(UIView *)view { return [NSDate date]; } // 触发刷新数据的操作 - (void)egoRefreshTableDidTriggerRefresh:(EGORefreshPos)aRefreshPos { if (aRefreshPos == EGORefreshHeader) { // 下拉刷新 self.curPage = 1; [self downloadData]; } else if (aRefreshPos == EGORefreshFooter) { // 上拉 self.curPage++; [self downloadData]; } } - (void)scrollViewDidScroll:(UIScrollView *)scrollView { [self.headView egoRefreshScrollViewDidScroll:scrollView]; [self.footView egoRefreshScrollViewDidScroll:scrollView]; } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { [self.headView egoRefreshScrollViewDidEndDragging:scrollView]; [self.footView egoRefreshScrollViewDidEndDragging:scrollView]; } ………………………………………………………… ………………………………………………………… ………………………………………………………… // 下载结束调用 - (void)connectionDidFinishLoading:(NSURLConnection *)connection { // 处理下载数据 // JSON解析 if (self.curPage == 1) { [self.dataArray removeAllObjects]; } ………………………………………………………… [self.tbView reloadData]; [self.headView egoRefreshScrollViewDataSourceDidFinishedLoading:self.tbView]; [self.footView egoRefreshScrollViewDataSourceDidFinishedLoading:self.tbView]; [self resetFooterFrame]; self.isLoading = NO; } @end
网友评论