美文网首页数据库
数据库基本使用和封装教程(基础版看我的就可以了)

数据库基本使用和封装教程(基础版看我的就可以了)

作者: MR_詹 | 来源:发表于2019-01-28 15:52 被阅读0次

    赶在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;
    }
    

    数据库的封装

    image.png WeChat76c9c04b8403928e83515db8e716cc3c.png

    完整DEMO

    相关文章

      网友评论

        本文标题:数据库基本使用和封装教程(基础版看我的就可以了)

        本文链接:https://www.haomeiwen.com/subject/qqmajqtx.html