前言
FMDB库虽然进一步对封装了系统本身的SQLite操作逻辑,但是使用过的朋友都知道,你还是需要写一堆的SQL操作语句才能进行操作而且并不支持实体操作。为了解决这些问题就需要对FMDB进行二次封装,支持实体操作以及简化其操作逻辑。
大体的思路;
(1)通过runtime获取model实体的属性变量名充当数据表的字段名,属性变量类型充当数据表字段类型,实体的类名充当数据表的名称;
(2)封装基本的数据库操作逻辑(增删改查),通过拼接SQL字符串,然后采用FMDB第三方库操作SQL语句。
整体设计架构主要的两个部分,数据库创建管理和数据表操作管理;
(1)统一管理的服务类DBService,拥有数据库管理对象,提供操作数据库的外部接口,类似外观模式。
(2)数据库管理,通过JSDatabase基类实现数据库创建以及注册数据表的生成,一般使用方法是通过子类继承该类配置数据库存储路径,并拥有管理所有表对象的权限;
(3)数据表操作管理,通过JSContentTable基类实现所有的逻辑,每一个子类继承该类对应一个操作数据表的对象,这些表管理对象由数据库管理对象管理(也就是继承JSDatabase的子类);
设计思路参照.Net的EF框架的设计思路,表对象对应数据表,而DBService就相当于EF的DataContext数据库上下文管理对象,通过DBService可以完成整个数据库所有表的CURD操作。
数据库管理基类,负责创建数据库以及创建所有数据表和创建版本控制表进行数据库版本升级,并且通过子类设置数据库存放路径创建数据库,并执行createAllTable生成数据表。
#import "JSDatabase.h"
#import "FMDatabase.h"
#import "FMDatabaseQueue.h"
@interface JSDatabase ()
@property (nonatomic, copy) NSMutableArray *tables;
@property (nonatomic, strong) FMDatabaseQueue *dbQueue;
@property (nonatomic, copy) NSString *dbPath;
@end
@implementation JSDatabase
- (void)dealloc {
[self.dbQueue close];
}
// 设置数据库版本
- (NSInteger)getDBVersion {
return 1;
}
// 创建数据库并生成所有数据表;
- (void)createPath:(NSString *)dbPath mode:(JsDatabaseMode)mode registerTable:(void (^)())registerTable
{
self.dbPath = dbPath;
if (mode == JsDatabaseModeRead) {
self.dbQueue = [FMDatabaseQueue databaseQueueWithPath:dbPath flags:SQLITE_OPEN_READONLY];
}
else if (mode == JsDatabaseModeWrite) {
// FMDatabaseQueue通过gcd创建数据库;
self.dbQueue = [[FMDatabaseQueue alloc] initWithPath:dbPath];
}
if (registerTable) {
registerTable();
}
// 生成所有数据表;
[self.dbQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
NSString *version = [self getVersion:db];
if (!version) {
[self createDBVersionTable:db];
[self createAllTable:db];
[self updateDBVersion:db];
}
else if ([version integerValue] != [self getDBVersion]) {
[self updateDB:db];
[self updateDBVersion:db];
}
}];
}
// 创建数据表;
- (void)createAllTable:(FMDatabase *)aDB
{
[self.tables enumerateObjectsUsingBlock:^(JSContentTable * _Nonnull objTable, NSUInteger idx, BOOL * _Nonnull stop) {
[objTable createTable:aDB];
}];
}
/* 数据库版本控制,通过jsdb_version管理,当数据表结构发生变化时,通过更新getDBVersion中的版本号值,执行updateDB方法进行数据表的数据迁移工作,迁移完成后再执行updateDBVersion更新jsdb_version表中的version字段的值,表示数据库迁移成功。
*/
// 取得jsdb_version数据表中最新的版本
- (NSString *)getVersion:(FMDatabase *)aDB
{
NSString *version = nil;
FMResultSet *resultSet = [aDB executeQuery:@"select version from jsdb_version where name = 'version'"];
while ([resultSet next]) {
version = [resultSet stringForColumnIndex:0];
}
[resultSet close];
return version;
}
// 更新数据库版本为当前最新版本
- (void)updateDBVersion:(FMDatabase *)aDB {
NSString *version = [self getVersion:aDB];
if (version) {
[aDB executeUpdate:@"update jsdb_version set version = ? where name = 'version'", @([self getDBVersion])];
}
else {
[aDB executeUpdate:@"insert into jsdb_version (name, version) values (?,?)", @"version", @([self getDBVersion])];
}
}
// 刚生成数据库时,创建数据库版本控制表jsdb_version
- (void)createDBVersionTable:(FMDatabase *)aDB {
[aDB executeUpdate:@"create table if not exists jsdb_version (version vachar (20) ,name varchar (10))"];
}
// 数据库版本发生更新时,更新所有表的结构并迁移旧数据
- (void)updateDB:(FMDatabase *)aDB {
[self.tables enumerateObjectsUsingBlock:^(JSContentTable * _Nonnull objTable, NSUInteger idx, BOOL * _Nonnull stop) {
[objTable updateDB:aDB];
}];
}
// 注册数据表;
- (JSContentTable *)registerTableClass:(Class)tableClass
{
JSContentTable *table = [[tableClass alloc] init];
[table configTableName];
table.dbQueue = self.dbQueue;
[self.tables addObject:table];
return table;
}
// 保存数据表对象
- (NSMutableArray *)tables
{
if (!_tables) {
_tables = [[NSMutableArray alloc] init];
}
return _tables;
}
@end
数据库子类,继承JSDatabase,负责配置数据库存放的路径以及管理所有的数据表;
#import "JSDatabase.h"
#import "GradeTable.h"
@interface DataClient : JSDatabase
@property (nonatomic, strong, readonly) GradeTable *gradeTable;
+ (instancetype)database;
- (void)construct;
@end
#import "DataClient.h"
#import "JSDatabaseConfig.h"
@interface DataClient ()
@property (nonatomic, strong) NSString *dbPath;
@property (nonatomic, strong) GradeTable *gradeTable;
@end
@implementation DataClient
+ (instancetype)database
{
DataClient *dataClient = [[DataClient alloc] init];
[dataClient construct];
return dataClient;
}
- (void)construct {
[self createPath:self.dbPath mode:JsDatabaseModeWrite registerTable:^{
self.gradeTable = (GradeTable *)[self registerTableClass:[GradeTable class]];
}];
}
数据库版本升级,重写getDBVersion替换版本号
//- (NSInteger)getDBVersion {
//
// return 2;
//}
- (NSString *)dbPath
{
if (!_dbPath) {
NSString *dbPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:[JSDatabaseConfig config].dataBaseName];
_dbPath = dbPath;
}
return _dbPath;
}
@end
对于DBService以及数据表类,代码略;
(1)DBService主要是管理数据库,并且提供所有操作数据表的外部接口,方便使用;
(2)数据表类主要是管理数据表,比如增删改查,设置索引以及增加字段和删除字段等操作。