首页 > 代码库 > 基本数据持久性(二) 使用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步骤以后的程序应该与下面的程序代码类似:

 

 

view sourceprint?
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”方法里面设置代理,程序代码如下:

 

view sourceprint?
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”方法,代码如下:

 

view sourceprint?
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的指针,以便操作数据。完成后的代码如下所示:

 

view sourceprint?
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”,详细代码如下所示:

 

view sourceprint?
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”方法的代码应如下所示:

 

view sourceprint?
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.接下来,我们就来完善“保存”按钮的方法,使得我们能够将我们输入的数据保存到数据库中以供读取。详细的代码如下:

 

view sourceprint?
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。详细代码如下所示:

 

view sourceprint?
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保存和读取数据