首页 > 代码库 > 基本数据持久性(二) 使用sqlite保存和读取数据
基本数据持久性(二) 使用sqlite保存和读取数据
关于基本数据的持久性,写过一篇文章来简述过(基本数据持久性(一) 使用plist保存和读取数据)。这篇文章将简述采用数据库sqlite的方式来保存数据,并根据查询结果读取数据。
一、工作原理
sqlite采用表存储的方式,表的第一行(也就是我们常说的表头)在sqilte中被称为“字段”。对于标的每一行(除了字段)的信息,都有一个独一无二的列内容可以将表的每一行内容独立区分开(例如本文所示的案例,存储一个学生的信息——学号、姓名、年龄、班级。那么,学号这一列就可以将表的每一行内容独立区分开,因为每个学生的学号都是不一样的),那么我们就把字段中的“学号”称之为“主键”。主键是具有唯一性的。
二、程序的主要功能
1.xib文件如图1所示:
图 1
2. 通过“保存”按钮将4个textField(ID、Name、Age、Class后面的输入框)的内容保存到sqlite文件中。
3.通过“读取”按钮将“查询”后的结果从sqlite中的内容分别读取到4个textField中。如果没有查询到,则弹出来一个alert提示查询失败。因为每一个字段只有一个供显示的窗口,所以为了避免误解,我们只查询主键的值。
三、实现步骤
1.先创建一个Single ViewController的视图,命名为“使用sqlite保存和读取数据”。然后对xib文件进行操作,分别放置上5个Label、5个textField和2个Button,如图1所示。
2.对5个textField分别设置IBOutlet属性,对2个Button分别设置IBAction方法,并将其与xib文件相对应的控件关联。
3.在“ViewController.h”文件中,添加一个UITextFieldDelegate,以调用该协议中的“- (BOOL)textFieldShouldReturn:(UITextField *)textField”方法通过点击键盘的返回键来隐藏键盘(注:因为这个程序比较简单,该步骤不是必须的,可根据需要添加。因为在程序中,还会通过一个“- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event”的方法,使得点击屏幕时隐藏键盘)。做完2、3步骤以后的程序应该与下面的程序代码类似:
01.
#import <UIKit/UIKit.h>
02.
@interface ViewController : UIViewController<UITextFieldDelegate>
03.
@property (retain, nonatomic) IBOutlet UITextField *searchFeild;
//查询文本框
04.
@property (retain, nonatomic) IBOutlet UITextField *stuID;
//学生学号文本框
05.
@property (retain, nonatomic) IBOutlet UITextField *stuName;
//学生姓名文本框
06.
@property (retain, nonatomic) IBOutlet UITextField *stuAge;
//学生年龄文本框
07.
@property (retain, nonatomic) IBOutlet UITextField *stuClass;
//学生班级文本框
08.
- (IBAction)saveData:(id)sender;
//保存按钮触发方法
09.
- (IBAction)loadData:(id)sender;
//读取按钮触发方法
10.
@end
4.因为有textField,所以一般接下来我是先在“ViewController.m”文件里面写隐藏键盘的方法,以免忘记而在测试程序时因为键盘未隐藏而耽误事。在这里有两个方法来隐藏键盘,上面已经叙述过。如果采用“- (BOOL)textFieldShouldReturn:(UITextField *)textField”的方法来隐藏键盘,需要先在“- (void)viewDidLoad”方法里面设置代理,程序代码如下:
01.
- (
void
)viewDidLoad
02.
{
03.
[super viewDidLoad];
04.
_searchFeild.delegate = self;
05.
_stuID.delegate = self;
06.
_stuName.delegate = self;
07.
_stuAge.delegate = self;
08.
_stuClass.delegate = self;
09.
}
另外一种隐藏键盘的方法是当触摸屏幕时(非textField区域和键盘区域)键盘隐藏,因此可以使用“- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event”方法,代码如下:
01.
//点击非textField和键盘的屏幕后隐藏键盘
02.
- (
void
)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
03.
{
04.
[_searchFeild resignFirstResponder];
05.
[_stuID resignFirstResponder];
06.
[_stuName resignFirstResponder];
07.
[_stuAge resignFirstResponder];
08.
[_stuClass resignFirstResponder];
09.
}
5.接下来,就是对sqlite的操作了。未能够使用sqilte,需要先加载一个Framework——libsqlite3.dylib。加载完成后,在ViewController.h文件里面包含刚加载的Framework的头文件"#import <sqlite3.h>",并添加上一个NSCoding的协议,以完成保存和读取数据时编码和解码的工作。然后添加一个sqlite3的指针,以便操作数据。完成后的代码如下所示:
01.
#import <UIKit/UIKit.h>
02.
#import <sqlite3.h>
03.
@interface ViewController : UIViewController<UITextFieldDelegate, NSCoding>
04.
{
05.
sqlite3 *sqliteDB;
//定义一个数据库指针,以便操作数据
06.
}
07.
@property (retain, nonatomic) IBOutlet UITextField *searchFeild;
//查询文本框
08.
@property (retain, nonatomic) IBOutlet UITextField *stuID;
//学生学号文本框
09.
@property (retain, nonatomic) IBOutlet UITextField *stuName;
//学生姓名文本框
10.
@property (retain, nonatomic) IBOutlet UITextField *stuAge;
//学生年龄文本框
11.
@property (retain, nonatomic) IBOutlet UITextField *stuClass;
//学生班级文本框
12.
- (IBAction)saveData:(id)sender;
//保存按钮触发方法
13.
- (IBAction)loadData:(id)sender;
//读取按钮触发方法
14.
@end
接下来,我们就需要创建一个sqlite3的数据库文件,用来保存我们输入的信息;并且要得到该文件的位置,以便我们往这个文件里面保存数据和从这个文件里面读取数据。为此,我们创建一个方法,该方法返回一个NSString类型的值(文件的路径是一串字符串,这个很好理解吧?),当我们点击“保存”或者“读取”按钮时,能够读到这个路径下的数据。我写的这个方法名叫“- (NSString*)dataFilePath”,详细代码如下所示:
01.
//获取sqlite3文件的路径
02.
- (NSString*)dataFilePath
03.
{
04.
//获取文件路径数组
05.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//注意:括号里面的第一个参数是NSDocumentDirectory,而不是NSDocumentationDirectory
06.
//获取上述文件路径数组里面的第一个元素,我们只需要这个元素
07.
NSString *documentPath = [paths objectAtIndex:0];
08.
//获取文件名称
09.
NSString *fileName = [documentPath stringByAppendingPathComponent:@
"data.sqlite3"
];
10.
return
fileName;
11.
}
6.可以把数据库文件(sqlite3)理解成一个“第三方平台”。那么,如果要想使我们的程序运行后能够关联到这个“第三方平台上”,就需要在我们对程序做任何操作之前能够加载成功这个“这个第三方平台”。于是,答案呼之欲出——我们需要在“- (void)viewDidLoad”方法中创建并加载数据库文件。我们创建一个名称为“student”的表来保存我们输入的数据,因此,完成后的“- (void)viewDidLoad”方法的代码应如下所示:
01.
- (
void
)viewDidLoad
02.
{
03.
[super viewDidLoad];
04.
05.
_searchFeild.delegate = self;
06.
_stuID.delegate = self;
07.
_stuName.delegate = self;
08.
_stuAge.delegate = self;
09.
_stuClass.delegate = self;
10.
11.
//加载创建的数据库文件(data.sqlite3)
12.
13.
//1.获取保存文件的路径
14.
NSString *path = [self dataFilePath];
15.
16.
//2.容错测试,判断文件是否存在,如果不存在,弹出警告并关闭文件;否则,继续执行
17.
if
(sqlite3_open([path UTF8String], &sqliteDB) != SQLITE_OK)
18.
{
19.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@
"警告"
message:@
"文件路径不存在"
delegate:nil cancelButtonTitle:@
"取消"
otherButtonTitles: nil];
20.
21.
[alert show];
22.
23.
sqlite3_close(sqliteDB);
24.
25.
[alert release];
//我没有使用ARC,所以alert必须要释放
26.
}
27.
28.
else
//文件存在并打开成功
29.
{
30.
//2.1定义一个char*变量接收错误信息
31.
char
*error;
32.
33.
//2.2sqlite创建表的语句,创建一个名为student的表
34.
//字段名是stuID、stuName、stuAge、stuClass
35.
//类型为text表示为字符串类型
36.
//并将stuID做为主键(PRIMARY KEY)
37.
38.
char
*createSQL =
"CREATE TABLE IF NOT EXISTS student (stuID TEXT PRIMARY KEY, stuName TEXT, stuAge TEXT, stuClass TEXT);"
;
//如果名称为student的表不存在,则创建一个表,且把stuID做为主键(PRIMARY KEY)以区分行的唯一性。注意双引号里面有个分号。
39.
40.
//2.3执行creatSQL语句(数据库指针,将要执行的语句)
41.
if
(sqlite3_exec(sqliteDB, createSQL, NULL, NULL, &error) != SQLITE_OK)
42.
{
43.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@
"警告"
message:@
"创建SQL失败"
delegate:nil cancelButtonTitle:@
"取消"
otherButtonTitles: nil];
44.
45.
[alert show];
46.
47.
[alert release];
//我没有使用ARC,所以alert必须要释放
48.
}
49.
50.
sqlite3_close(sqliteDB);
51.
52.
}
53.
}
7.接下来,我们就来完善“保存”按钮的方法,使得我们能够将我们输入的数据保存到数据库中以供读取。详细的代码如下:
01.
//“保存”按钮的实现
02.
- (IBAction)saveData:(id)sender
03.
{
04.
//1.先打开文件路径
05.
NSString *path = [self dataFilePath];
06.
07.
//2.容错测试,判断文件是否存在,如果不存在,弹出警告并关闭文件;否则,继续执行
08.
if
(sqlite3_open([path UTF8String], &sqliteDB) != SQLITE_OK)
09.
{
10.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@
"警告"
message:@
"文件路径不存在"
delegate:nil cancelButtonTitle:@
"取消"
otherButtonTitles: nil];
11.
12.
[alert show];
13.
14.
sqlite3_close(sqliteDB);
15.
16.
[alert release];
//我没有使用ARC,所以alert必须要释放
17.
}
18.
19.
else
//文件存在并打开成功
20.
{
21.
//2.1定义一个char*类型的变量来描述往在viewDidload中创建的student表中插入或者替换数据的语句
22.
23.
char
*sqlStr =
"INSERT OR REPLACE INTO student(stuID, stuName, stuAge, stuClass) VALUES (?,?,?,?)"
;
24.
25.
//2.2定义一个对sql语句操作的指针
26.
sqlite3_stmt *statement;
27.
28.
//2.3容错检查,若数据保存成功,则执行插入数据的操作
29.
if
(sqlite3_prepare_v2(sqliteDB, sqlStr, -1, &statement, NULL) == SQLITE_OK)
30.
{
31.
//插入数据的位置只能从1开始
32.
sqlite3_bind_text(statement, -1, [_stuID.text UTF8String], 1, NULL);
//在1的位置插入学号
33.
34.
sqlite3_bind_text(statement, -1, [_stuName.text UTF8String], 2, NULL);
//在2的位置插入姓名
35.
36.
sqlite3_bind_text(statement, -1, [_stuAge.text UTF8String], 3, NULL);
//在3的位置插入年龄
37.
38.
sqlite3_bind_text(statement, -1, [_stuClass.text UTF8String], 4, NULL);
//在4的位置插入年级
39.
40.
41.
//判断数据是否插入成功,若不成功,则弹出一个alert
42.
if
(sqlite3_step(statement) != SQLITE_DONE)
43.
{
44.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@
"警告"
message:@
"数据插入失败!"
delegate:nil cancelButtonTitle:@
"取消"
otherButtonTitles: nil];
45.
46.
[alert show];
47.
48.
[alert release];
//我没有使用ARC,所以必须手动释放alert
49.
}
50.
}
51.
52.
else
53.
{
54.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@
"警告"
message:@
"数据保存失败!"
delegate:nil cancelButtonTitle:@
"取消"
otherButtonTitles: nil];
55.
56.
[alert show];
57.
58.
[alert release];
//我没有使用ARC,所以必须手动释放alert
59.
60.
}
61.
62.
//2.4结束指令指针,释放预编译内存 www.it165.net
63.
sqlite3_finalize(statement);
64.
65.
//2.5关闭数据库
66.
sqlite3_close(sqliteDB);
67.
}
68.
69.
i++;
//每保存一条信息,则i的值就加1
70.
}
8.数据已经保存了,那么是否可以读取呢?为此,我们先编写完成“读取”按钮的方法。我在程序里面给它命名为“- (IBAction)loadData:(id)sender”。首先,还是需要先把保存数据的路径给找出来,同时,我们还需要知道数据库里面一共保存了多少行的数据,这样才能使得我们对查询的数据与数据库里面的数据进行比较。如果查询到数据,就把把该数据里面的内容分别赋值给4个表示信息的textField;如果没有查询到信息,就弹出来一个alert。详细代码如下所示:
001.
//“保存”按钮的实现
002.
- (IBAction)saveData:(id)sender
003.
{
004.
//1.先打开文件路径
005.
NSString *path = [self dataFilePath];
006.
007.
//2.容错测试,判断文件是否存在,如果不存在,弹出警告并关闭文件;否则,继续执行
008.
if
(sqlite3_open([path UTF8String], &sqliteDB) != SQLITE_OK)
009.
{
010.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@
"警告"
message:@
"文件路径不存在"
delegate:nil cancelButtonTitle:@
"取消"
otherButtonTitles: nil];
011.
012.
[alert show];
013.
014.
sqlite3_close(sqliteDB);
015.
016.
[alert release];
//我没有使用ARC,所以alert必须要释放
017.
}
018.
019.
else
//文件存在并打开成功
020.
{
021.
//2.1定义一个char*类型的变量来描述往在viewDidload中创建的student表中插入或者替换数据的语句
022.
023.
char
*sqlStr =
"INSERT OR REPLACE INTO student(stuID, stuName, stuAge, stuClass) VALUES (?,?,?,?);"
;
//注意双引号里面有个分号
024.
025.
//2.2定义一个对sql语句操作的指针
026.
sqlite3_stmt *statement;
027.
028.
//2.3容错检查,若数据保存成功,则执行插入数据的操作
029.
if
(sqlite3_prepare_v2(sqliteDB, sqlStr, -1, &statement, NULL) == SQLITE_OK)
030.
{
031.
//插入数据的位置只能从1开始
032.
sqlite3_bind_text(statement, 1, [_stuID.text UTF8String], -1, NULL);
//在1的位置插入学号
033.
034.
sqlite3_bind_text(statement, 2, [_stuName.text UTF8String], -1, NULL);
//在2的位置插入姓名
035.
036.
sqlite3_bind_text(statement, 3, [_stuAge.text UTF8String], -1, NULL);
//在3的位置插入年龄
037.
038.
sqlite3_bind_text(statement, 4, [_stuClass.text UTF8String], -1, NULL);
//在4的位置插入年级
039.
040.
//判断数据是否插入成功,若不成功,则弹出一个alert
041.
if
(sqlite3_step(statement) != SQLITE_DONE)
042.
{
043.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@
"警告"
message:@
"数据插入失败!"
delegate:nil cancelButtonTitle:@
"取消"
otherButtonTitles: nil];
044.
045.
[alert show];
046.
047.
[alert release];
//我没有使用ARC,所以必须手动释放alert
048.
}
049.
}
050.
051.
else
052.
{
053.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@
"警告"
message:@
"数据保存失败!"
delegate:nil cancelButtonTitle:@
"取消"
otherButtonTitles: nil];
054.
055.
[alert show];
056.
057.
[alert release];
//我没有使用ARC,所以必须手动释放alert
058.
059.
}
060.
061.
//2.4结束指令指针,释放预编译内存
062.
sqlite3_finalize(statement);
063.
064.
//2.5关闭数据库
065.
sqlite3_close(sqliteDB);
066.
067.
}
068.
069.
}
070.
//“读取”按钮的实现
071.
- (IBAction)loadData:(id)sender
072.
{
073.
int
j = 0;
//对查询的数据,每和数据库中的主键比较一次就加1
074.
075.
int
result;
//保存数据库的总结果
076.
077.
char
* errmsg = NULL;
//接收错误信息的变量
078.
char
** dbResult;
//是 char ** 类型,两个*号
079.
int
nRow, nColumn;
//数据库中的表格一共有多少行和多少列
080.
081.
//1.先打开文件保存路径
082.
083.
NSString *path = [self dataFilePath];
084.
085.
//2.将result的结果保存为数据库打开与否的状态
086.
result = sqlite3_open([path UTF8String], &sqliteDB);
087.
088.
089.
//3.打开数据库并进行容错检查
090.
091.
if
(sqlite3_open([path UTF8String], &sqliteDB) != SQLITE_OK)
092.
{
093.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@
"警告"
message:@
"文件路径不存在"
delegate:nil cancelButtonTitle:@
"取消"
otherButtonTitles: nil];
094.
095.
[alert show];
096.
097.
sqlite3_close(sqliteDB);
098.
099.
[alert release];
//我没有使用ARC,所以alert必须要释放
100.
}
101.
102.
else
103.
{
104.
//3.1定义查询语句,查询名为student的表
105.
char
*querySQL =
"SELECT * FROM student"
;
106.
107.
//3.2定义语句指令的指针,用这个指针来向SQL语句发出指令
108.
sqlite3_stmt *statement;
109.
110.
//3.3得到数据库的table的结果,其中的nRow的结果就是表格有多少行
111.
result = sqlite3_get_table( sqliteDB, querySQL, &dbResult, &nRow, &nColumn, &errmsg );
112.
113.
//3.3预处理、预编译
114.
if
(sqlite3_prepare_v2(sqliteDB, querySQL, -1, &statement, NULL) == SQLITE_OK)
115.
{
116.
//执行查找语句,遍历出所有的查询结果
117.
while
(sqlite3_step(statement) == SQLITE_ROW)
118.
{
119.
j++;
120.
121.
//NSLog(@"nRow = %i", nRow); //测试表格的行数是否正确
122.
123.
124.
//从数据库中取出第0个字段的内容
125.
//注意,取的时候一定是从0开始的,而不是1
126.
char
*field1 = (
char
*)sqlite3_column_text(statement, 0);
127.
128.
//定义一个变量用于保存从第0个字段读出来的数据
129.
//并转换成字符串的形式
130.
NSString *field1String = [[NSString alloc]initWithUTF8String:field1];
131.
132.
133.
//从数据库中取出第1个字段的内容
134.
char
*field2 = (
char
*)sqlite3_column_text(statement, 1);
135.
136.
//定义一个变量用于保存从第1个字段读出来的数据
137.
//并转换成字符串的形式
138.
NSString *field2String = [[NSString alloc]initWithUTF8String:field2];
139.
140.
141.
//从数据库中取出第2个字段的内容
142.
char
*field3 = (
char
*)sqlite3_column_text(statement, 2);
143.
144.
//定义一个变量用于保存从第2个字段读出来的数据
145.
//并转换成字符串的形式
146.
NSString *field3String = [[NSString alloc]initWithUTF8String:field3];
147.
148.
149.
//从数据库中取出第3个字段的内容
150.
char
*field4 = (
char
*)sqlite3_column_text(statement, 3);
151.
152.
//定义一个变量用于保存从第3个字段读出来的数据
153.
//并转换成字符串的形式
154.
NSString *field4String = [[NSString alloc]initWithUTF8String:field4];
155.
156.
157.
//将需要查询的结果与第0个字段转化为字符串以后的结果比较
158.
//如果在nRow(包含nRow)之前查询到数据,就将结果显示,并退出不再继续比较
159.
if
([_searchFeild.text isEqualToString:field1String])
160.
{
161.
//如果比较的结果为相等,则把数据读入显示的4个textField中
162.
163.
_stuID.text = field1String;
164.
_stuName.text = field2String;
165.
_stuAge.text = field3String;
166.
_stuClass.text = field4String;
167.
168.
return
;
169.
}
170.
171.
//如果上面的那个if一直没有被触发,那么就执行下面这条if语句,弹出查询错误的警告
172.
if
((j == nRow) && ![_searchFeild.text isEqualToString:field1String])
173.
{
174.
NSString *alertStr = [[NSString alloc]initWithFormat:@
"未找到编号为%@的用户"
, _searchFeild.text];
175.
176.
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@
"查询错误"
message:alertStr delegate:self cancelButtonTitle:@
"取消"
otherButtonTitles: nil];
177.
178.
_stuID.text = @
""
;
179.
_stuName.text = @
""
;
180.
_stuAge.text = @
""
;
181.
_stuClass.text = @
""
;
182.
183.
[alert show];
184.
185.
[alertStr release];
//没有使用ARC,必须手动释放alertStr
186.
187.
[alert release];
//没有使用ARC,必须手动释放alert
188.
189.
}
190.
191.
[field1String release];
192.
[field2String release];
193.
[field3String release];
194.
[field4String release];
195.
196.
}
197.
}
198.
199.
//3.4结束指令指针,释放预编译内存
200.
sqlite3_finalize(statement);
201.
202.
//3.5关闭数据库
203.
sqlite3_close(sqliteDB);
204.
}
205.
206.
}
然后,通过Xcode结束程序,再Run一下程序,在“查询”输入框输入要查询的内容(只能是主键的值),点击“读取”按钮,看看效果吧。
9.至此,我们的程序就算是完成了。如果你没有采用ARC来写这个程序,那么,请记得在“- (void)dealloc”方法里面释放你创建的内存——如果你采用的是直接从xib拖动控件到ViewController.h文件里面生成相应的属性和方法,那么Xcode已经为你写好了“- (void)dealloc”方法了,你无须理会。
10.如果你想彻底清除data.sqlite3里面保存的数据,直接将iOS模拟器“还原内容和设置”即可。
四、扩展练习
在该例子中,我们完成的是数据的保存和读取操作。那么,请你想一想,如何完成对数据库里面的数据进行删除操作呢?是否可以对数据库里面的文件进行排序操作呢?
期待你的完美答案!
基本数据持久性(二) 使用sqlite保存和读取数据