赶在2019年春节放假前一天,发一最后一波文章,虽然有点凌乱,但绝对使用
大力我上一家公司是做即时聊天应用的,聊天功能不是走第三方的平台,如环信、网易云等,毕竟是应用的核心功能,所以还是掌握在自己手上比较靠谱(结果各种坑),当然还没到自定义协议那一高层次,我们使用的WebSocket,所以比如socket发消息的保证、socket重连、UI搭建等等都是需要自己从零开始做起,其中少不了重要的一环:聊天记录和用户数据的保存。
铺垫这么久了,是时候回归我们今天要讨论的话题:数据库
项目数据库管理是使用常用的第三方框架:FMDB,今天只讲一些常用的sql语法和功能,具体的代码说明已经在代码注释写了...请看代码
@interface ViewController ()
@property (nonatomic, copy, readwrite) NSString *fileName;
@property (nonatomic, strong, readwrite) FMDatabase *db;
@property (nonatomic, strong, readwrite) FMDatabaseQueue *queue;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 数据库路径
NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *fileName = [doc stringByAppendingPathComponent:@"student.db"];
self.fileName = fileName;
NSLog(@"====%@",fileName);
}
#pragma mark - touch
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// [self insert];
// [self update];
// [self delete];
// [self query];
[self dataBaseQueueAction];
}
@end
1、数据库基本使用:增删改查
#pragma mark - FMDB 增删改查
- (void)baseUseDemo {
/*
三大类
(1)FMDatabase
一个FMDatabase对象就代表一个单独的SQLite数据库
用来执行SQL语句
(2)FMResultSet
使用FMDatabase执行查询后的结果集
(3)FMDatabaseQueue
用于在多线程中执行多个查询或更新,它是线程安全的
*/
// 获得数据库
/*
有三种情况
(1)具体文件路径
如果不存在会自动创建
(2)空字符串@""
会在临时目录创建一个空的数据库
当FMDatabase连接关闭时,数据库文件也被删除
(3)nil
会创建一个内存中临时数据库,当FMDatabase连接关闭时,数据库会被销毁
*/
FMDatabase *db = [FMDatabase databaseWithPath:self.fileName];
// 打开数据库
if ([db open]) {
NSString *sqlString = @"CREATE TABLE IF NOT EXISTS t_student(id integer PRIMARY KEY AUTOINCREMENT,name text NOT NULL , age integer NOT NULL);";
BOOL result = [db executeUpdate:sqlString];
if (result) {
NSLog(@"创表成功");
}
else{
NSLog(@"创表失败");
}
}
self.db = db;
}
#pragma mark - 数据库sql语句
// w3school sql 语句学习:http://www.w3school.com.cn/sql/sql_distinct.asp
/**
插入数据
*/
- (void)insert {
for (int i=0; i<10; i++) {
NSString *name = [NSString stringWithFormat:@"liven-%d",arc4random_uniform(100)];
[self.db executeUpdate:@"INSERT INTO t_student (name,age) VALUES (?,?)",name,@(arc4random_uniform(40))];
}
}
/**
删除
*/
- (void)delete {
// 清空数据库表的数据(这只是删除所有行的数据,并不会对表的结构、属性和索性有影响)
//(即使数据库没有这张表,删除也不会崩溃,只会报错)
[self.db executeUpdate:@"DELETE FROM t_student"];
// 删除某一行:DELETE FROM 表名称 WHERE 列名称 = 值
}
/**
更新
*/
- (void)update {
// 公式:UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值
[self.db executeUpdate:@"UPDATE t_student SET age=? WHERE name='大力'",@(18)];
}
/**
查询
*/
- (void)query {
FMResultSet *resultSet = [self.db executeQuery:@"SELECT * FROM t_student"];
while ([resultSet next]) {
int ID = [resultSet intForColumn:@"id"];
NSString *name = [resultSet stringForColumn:@"name"];
int age = [resultSet intForColumn:@"age"];
NSLog(@"%d %@ %d",ID,name,age);
}
}
2、批处理
#pragma mark - 批处理
- (void)lotOperation {
NSString *creatSqlString = @"CREATE TABLE IF NOT EXISTS t_person (id integer PRINARY KEY AUTOINCREMENT,name text NOT NULL, age integer NOT NULL, age integer NOT NULL);"
@"CREATE TABLE IF NOT EXISTS t_student (id integer PRINARY KEY AUTOINCREMENT,name text NOT NULL, age integer NOT NULL, age integer NOT NULL);";
[self.queue inDatabase:^(FMDatabase * _Nonnull db) {
[db executeStatements:creatSqlString];
}];
}
3、线程安全databaseQueue
#pragma mark - dataBaseQueue线程安全
- (void)dataBaseQueueAction {
/*
备注:
1、FMDatabaseQueue初始化时已将数据库打开和关闭封装好了,所以操作时不需要单独调用FMDatabase的open和close方法
2、FMDatabaseQueue是基于同步串行队列保证数据库访问的安全性,所以不能叠加使用,避免死锁
*/
// 获取数据库队列
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:self.fileName];
[queue inDatabase:^(FMDatabase * _Nonnull db) {
BOOL resutl = [db executeUpdate:@"CREATE TABLE IF NOT EXISTS t_person (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);"];
if (resutl) {
NSLog(@"创表成功");
}
else{
NSLog(@"创表失败");
}
}];
self.queue = queue;
// 插入数据
[self.queue inDatabase:^(FMDatabase * _Nonnull db) {
[db executeUpdate:@"INSERT INTO t_person (name,age) VALUES (?,?);",@"wendingding",@(22)];
}];
// 查询
[self.queue inDatabase:^(FMDatabase * _Nonnull db) {
FMResultSet *resultSet = [db executeQuery:@"SELECT * FROM t_person"];
while ([resultSet next]) {
int ID = [resultSet intForColumn:@"id"];
NSString *name = [resultSet stringForColumn:@"name"];
int age = [resultSet intForColumn:@"age"];
NSLog(@"%d %@ %d",ID,name,age);
}
}];
/*
备注:
https://stackoverflow.com/questions/15720272/when-to-close-sqlite-database-using-fmdb
数据库不需频繁的切换open和close两种状态,否则会造成不小的性能损耗(如cpu使用率),FMDB的作者也提到
只有在需要更改数据库模式的时候才需要切换状态,即使是退到后台线程也是不需要关闭的,维持数据打开的状态就可以
*/
}
4、事务
// 事务:所有任务执行完成后才再将结果一次性提交到数据库
// 特点:要么全部成功,要么全部失败(如果中途出现问题,则会回滚)
// 注意:开启事务比不开事务的耗时更少
[self.queue inDatabase:^(FMDatabase * _Nonnull db) {
// 开启事务
[db beginTransaction];
BOOL first = [db executeUpdate:@"INSERT INTO t_person (name,age) VALUES (?,?);",@"lucky",@(22)];
BOOL second = [db executeUpdate:@"INSERT INTO t_person (name,age) VALUES (?,?);",@"james",@(22)];
// 如果其中一个失败,则结束,数据回滚
if (!first || !second) {
[db rollback];
return ;
}
// 提交事务
[db commit];
}];
// 另外一种实现方式
[self.queue inTransaction:^(FMDatabase * _Nonnull db, BOOL * _Nonnull rollback) {
BOOL first = [db executeUpdate:@"INSERT INTO t_person (name,age) VALUES (?,?);",@"wendi",@(22)];
BOOL second = [db executeUpdate:@"INSERT INTO t_person (name,age) VALUES (?,?);",@"davi",@(22)];
if (!first || !second) {
*rollback = YES;
return ;
}
}];
5、判断某张表是否存在
#pragma mark - 判断某张表是否存在
- (void)chectDatabaseTableExists {
/*
备注:
sqlite中有一个内k建表sqlite_master,这个表中存储这个所有自建表的表名称等信息
可以通过:select * from sqlite_master 查看这个内建表的所有记录
引申:判断指定的表是否存在,可以用如下语句
select count(*) from sqlite_master wheretype='table' and name='要查询的表名'
如果查询结果大于0,表示该表存在于数据库中,否则不存在
详情:
https://blog.csdn.net/aflyeaglenku/article/details/50884837
*/
}
6、检测字段是否存在(并且添加字段)
#pragma mark - 检测字段是否存在(并且添加字段)
- (void)checkDatabaseColumn {
/*
sqlite中并没有直接删除列和重命名列名称的sql语句,但是可以使用下面的思路实现同样的效果
1、新建一个新表(新表的字段除了要删除的字段外,其余的都要)
2、将旧表的值copy到新表
3、删除旧表
4、重命名表表名
详情:
https://blog.csdn.net/aflyeaglenku/article/details/50884837
*/
[self.queue inDatabase:^(FMDatabase * _Nonnull db) {
if ([db columnExists:@"phone" inTableWithName:@"t_person"]) {
// 如果t_person表中存在phone这个字段,不需要操作
}else{
// 如果t_person表中不存在phone这个字段,插入列
[db executeUpdate:@"ALTER TABLE t_person ADD phone text"];
}
}];
}
7、表迁移
#pragma mark - 表迁移
- (void)updateToNewTable {
/*
这功能主要是表设计结构变更的时候需要使用
思路:
重新创建一个表
将旧表数据导入到新的表中
删除旧表
*/
}
8、多表联查(重点:此处先介绍左连、右连、内连的概念)
#pragma mark - 多表连查 join
- (void)mulTable {
/*
左连接:左边有的,右边没有的为null
右连接:左边没有的,右边有的为null
内连接:显示左边右边共有的
详情介绍:
https://blog.csdn.net/wang0112233/article/details/78418698
https://blog.csdn.net/plg17/article/details/78758593
*/
// 实例:比如有两个表A和B,联查条件A.a字段与B.b字段相同
NSLog(@"select A.*,B.* from A left join B on(A.a = B.b)");
}
9、创建索引
#pragma mark - 创建索引(有索引后查找速度更快速)
- (void)createIndex {
/*
备注:
1、在不读取整个表的情况下,索引使数据库应用程序可以更快地查找数据。
2、更新一个包含索引的表需要比更新一个没有索引的表更多的时间,这是由于索引本身也需要更新。
因此,理想的做法是仅仅在常常被搜索的列(以及表)上面创建索引
公式:CREATE INDEX index_name ON table_name (column_name)
详情介绍:
http://www.w3school.com.cn/sql/sql_create_index.asp
*/
// 实例:创建一个简单的索引,名为 "PersonIndex",在 Person 表的 LastName 列:
NSLog(@"CREATE INDEX PersonIndex ON Person(LastName)");
}
10、数据库的删除(清空)
#pragma mark - 数据库的删除(清空)
- (void)clearTable {
/*
1、删除表(表的结构、属性以及索引也会被删除)
DROP TABLE表名称
2、删除数据库
DROP DATABASE数据库名称
3、清空表内的数据,但并不删除表本身
TRUNCATE TABLE表名称
*/
}
11、统计表的数据
#pragma mark - 统计表的数据
- (void)tableCount {
// 统计某表(如Person)的数据量
NSLog(@"SELECT COUNT(*) FROM Person");
// 统计某表某列的数据量(如:Person中name的数量,备注:NULL不计入)
NSLog(@"SELECT COUNT(name) FROM Person");
}
12、分页读取数据
#pragma mark - 分页读取数据
- (void)pageData {
// 实例:分页获取表Person中数据,每页20条
// 从第0行开始获取 一次获取20条
NSLog(@"SELECT * FROM Person LIMIT 0,20");
// 从第21行开始获取 一次获取20条
NSLog(@"SELECT * FROM Person LIMIT 20,20");
}
13、数据写入和读出处理(如写入的内容包括一些特殊的转义字符,这些直接写入会导致失败,所以需要特殊处理)
#pragma mark - 数据写入和读出处理(如写入的内容包括一些特殊的转义字符,这些直接写入会导致失败,所以需要特殊处理)
- (NSString *)inDBReplaceStr:(NSString *)inStr {
NSString *tempStr = inStr;
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"/" withString:@"//"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"'" withString:@"''"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"[" withString:@"/["];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"]" withString:@"/]"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"%" withString:@"/%"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"&" withString:@"/&"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"_" withString:@"/_"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"(" withString:@"/("];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@")" withString:@"/)"];
return tempStr;
}
- (NSString *)outDBReplaceStr:(NSString *)outStr {
NSString *tempStr = outStr;
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"//" withString:@"/"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"''" withString:@"'"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"/[" withString:@"["];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"/]" withString:@"]"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"/%" withString:@"%"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"/&" withString:@"&"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"/_" withString:@"_"];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"/(" withString:@"("];
tempStr = [tempStr stringByReplacingOccurrencesOfString:@"/)" withString:@")"];
return tempStr;
}
网友评论