[iOS學習筆記]·FMDB:第三方資料庫處理框架·用法示例篇(附原始碼Demo)
關於學習使用FMDB,除了筆者整理的官方文件翻譯版,更重要的是實踐和程式碼練習。筆者曾查閱過FMDB示例用法的一些文獻,一部分有的只講到FMDatabase類,另一部分只講專為多執行緒操作的FMDatabaseQueue類,很少有同時介紹兩者用法區別的文章。這裡,筆者做一個嘗試。
這裡提供我整理的原始碼GigHub下載連結, 覺得有需要的朋友可以直接下載Demo,而不用看本篇文章介紹。歡迎作出進一步優化與分享,筆者這個嘗試也是得益於兩個大神的文章。
關於FMDatabase,官方文件說:它表示一個單獨的SQLite資料庫,用來執行SQLite的命令。而關於FMDatabaseQueue,官方文件說:如果你想在多執行緒中執行多個查詢或更新,你應該使用該類。這是執行緒安全的。
然而,你看完還是不知道FMDatabaseQueue是什麼。在字面Queue意義上,它只是個佇列?那還需要自己另外建一個DataBase嗎?其實FMDatabaseQueue這個類的物件已經把一個資料庫和有關事務操作的方法封裝在一起了。一個FMDatabaseQueue例項物件裡面包含一個FMDatabase資料庫物件。
Demo演示效果圖:
1. 單執行緒生物:FMDatabase類
1.1 對FMDB的FMDataBase類進行一層封裝
1.1.1 原始碼
- DataBase.h
//
// DataBase.h
// FMDBDemo
#import <Foundation/Foundation.h>
@class Person;
@class Car;
@interface DataBase : NSObject
@property(nonatomic,strong) Person *person;
+ (instancetype)sharedDataBase;
#pragma mark - Person
/**
* 新增person
*
*/
- (void)addPerson:(Person *)person;
/**
* 刪除person
*
*/
- (void)deletePerson:(Person *)person;
/**
* 更新person
*
*/
- (void)updatePerson:(Person *)person;
/**
* 獲取所有資料
*
*/
- (NSMutableArray *)getAllPerson;
#pragma mark - Car
/**
* 給person新增車輛
*
*/
- (void)addCar:(Car *)car toPerson:(Person *)person;
/**
* 給person刪除車輛
*
*/
- (void)deleteCar:(Car *)car fromPerson:(Person *)person;
/**
* 獲取person的所有車輛
*
*/
- (NSMutableArray *)getAllCarsFromPerson:(Person *)person;
/**
* 刪除person的所有車輛
*
*/
- (void)deleteAllCarsFromPerson:(Person *)person;
@end
- DataBase.m
//
// DataBase.m
// FMDBDemo
#import "DataBase.h"
#import <FMDB.h>
#import "Person.h"
#import "Car.h"
static DataBase *_DBCtl = nil;
@interface DataBase()<NSCopying,NSMutableCopying>{
FMDatabase *_db;
}
@end
@implementation DataBase
#pragma mark - 單例操作
+(instancetype)sharedDataBase{
@synchronized(self) {
if (_DBCtl == nil) {
_DBCtl = [[DataBase alloc] init];
[_DBCtl initDataBase];
}
}
return _DBCtl;
}
+(instancetype)allocWithZone:(struct _NSZone *)zone{
@synchronized(self) {
if (_DBCtl == nil) {
_DBCtl = [super allocWithZone:zone];
}
}
return _DBCtl;
}
-(id)copy{
return self;
}
-(id)mutableCopy{
return self;
}
-(id)copyWithZone:(NSZone *)zone{
return self;
}
-(id)mutableCopyWithZone:(NSZone *)zone{
return self;
}
-(void)initDataBase{
// 獲得Documents目錄路徑
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 檔案路徑
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"DB1.sqlite"];
// 例項化FMDataBase物件
_db = [FMDatabase databaseWithPath:filePath];
[_db open];
// 初始化資料表
NSString *personSql = @"CREATE TABLE 'person' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ,'person_id' VARCHAR(255),'person_name' VARCHAR(255),'person_age' VARCHAR(255),'person_number'VARCHAR(255)) ";
NSString *carSql = @"CREATE TABLE 'car' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ,'own_id' VARCHAR(255),'car_id' VARCHAR(255),'car_brand' VARCHAR(255),'car_price'VARCHAR(255)) ";
[_db executeUpdate:personSql];
[_db executeUpdate:carSql];
[_db close];
}
#pragma mark - 介面
#pragma mark - 對人操作
//新增人
- (void)addPerson:(Person *)person{
[_db open];
NSNumber *maxID = @(0);
FMResultSet *res = [_db executeQuery:@"SELECT * FROM person "];
//獲取資料庫中最大的ID
while ([res next]) {
if ([maxID integerValue] < [[res stringForColumn:@"person_id"] integerValue]) {
maxID = @([[res stringForColumn:@"person_id"] integerValue] ) ;
}
}
maxID = @([maxID integerValue] + 1);
[_db executeUpdate:@"INSERT INTO person(person_id,person_name,person_age,person_number)VALUES(?,?,?,?)",maxID,person.name,@(person.age),@(person.number)];
[_db close];
}
//刪除人
- (void)deletePerson:(Person *)person{
[_db open];
[_db executeUpdate:@"DELETE FROM person WHERE person_id = ?",person.ID];
[_db close];
}
//更新人
- (void)updatePerson:(Person *)person{
[_db open];
[_db executeUpdate:@"UPDATE 'person' SET person_name = ? WHERE person_id = ? ",person.name,person.ID];
[_db executeUpdate:@"UPDATE 'person' SET person_age = ? WHERE person_id = ? ",@(person.age),person.ID];
[_db executeUpdate:@"UPDATE 'person' SET person_number = ? WHERE person_id = ? ",@(person.number + 1),person.ID];
[_db close];
}
//獲取所有人
- (NSMutableArray *)getAllPerson{
NSMutableArray *dataArray = [[NSMutableArray alloc] init];
if ([_db open]) {
FMResultSet *res = [_db executeQuery:@"SELECT * FROM person"];
while ([res next]) {
Person *person = [[Person alloc] init];
person.ID = @([[res stringForColumn:@"person_id"] integerValue]);
person.name = [res stringForColumn:@"person_name"];
person.age = [[res stringForColumn:@"person_age"] integerValue];
person.number = [[res stringForColumn:@"person_number"] integerValue];
[dataArray addObject:person];
}
};
[_db close];
return dataArray;
}
#pragma mark - 對人的車操作
/**
* 給person新增車輛
*
*/
- (void)addCar:(Car *)car toPerson:(Person *)person{
[_db open];
//根據person是否擁有car來新增car_id
NSNumber *maxID = @(0);
FMResultSet *res = [_db executeQuery:[NSString stringWithFormat:@"SELECT * FROM car where own_id = %@ ",person.ID]];
while ([res next]) {
if ([maxID integerValue] < [[res stringForColumn:@"car_id"] integerValue]) {
maxID = @([[res stringForColumn:@"car_id"] integerValue]);
}
}
maxID = @([maxID integerValue] + 1);
[_db executeUpdate:@"INSERT INTO car(own_id,car_id,car_brand,car_price)VALUES(?,?,?,?)",person.ID,maxID,car.brand,@(car.price)];
[_db close];
}
/**
* 給person刪除車輛
*
*/
- (void)deleteCar:(Car *)car fromPerson:(Person *)person{
[_db open];
[_db executeUpdate:@"DELETE FROM car WHERE own_id = ? and car_id = ? ",person.ID,car.car_id];
[_db close];
}
/**
* 獲取person的所有車輛
*
*/
- (NSMutableArray *)getAllCarsFromPerson:(Person *)person{
[_db open];
NSMutableArray *carArray = [[NSMutableArray alloc] init];
FMResultSet *res = [_db executeQuery:[NSString stringWithFormat:@"SELECT * FROM car where own_id = %@",person.ID]];
while ([res next]) {
Car *car = [[Car alloc] init];
car.own_id = person.ID;
car.car_id = @([[res stringForColumn:@"car_id"] integerValue]);
car.brand = [res stringForColumn:@"car_brand"];
car.price = [[res stringForColumn:@"car_price"] integerValue];
[carArray addObject:car];
}
[_db close];
return carArray;
}
- (void)deleteAllCarsFromPerson:(Person *)person{
[_db open];
[_db executeUpdate:@"DELETE FROM car WHERE own_id = ?",person.ID];
[_db close];
}
@end
1.1.2 知識點總結
- 靜態變數(這裡是靜態例項)
- 單例類
- SQL語言
1.1.3 說明
FMDatabase的例項物件_db在執行SQL語句的時候採取類似下面的程式碼。其中,有兩種風格,一種在executeUpdate方法後面直接寫上字串的字面量語法。另一種在執行方法的後面基於SQL字串初始化一個NSString物件。
//刪除
[_db executeUpdate:@"DELETE FROM person WHERE person_id = ?",person.ID];
[_db executeUpdate:[NSString stringWithFormat:@"delete from %@ where name = '%@'",KTable_UserName, dto.name]];
上面注意?
和'%@'
的區別!但對於也僅僅對於SELECT查詢操作,可以省掉'%@'
兩邊的''
噢。DEMO程式碼中也是這樣做的。
//查詢
FMResultSet *res = [_db executeQuery:[NSString stringWithFormat:@"SELECT * FROM car where own_id = '%@'",person.ID]];
//去掉''
FMResultSet *res = [_db executeQuery:[NSString stringWithFormat:@"SELECT * FROM car where own_id = %@",person.ID]];
1.2 模型層
1.2.1 原始碼
- Person.h
//
// Person.h
// FMDBDemo
#import <Foundation/Foundation.h>
@interface Person : NSObject
@property(nonatomic,strong) NSNumber *ID;
@property(nonatomic,copy) NSString *name;
@property(nonatomic,assign) NSInteger age;
@property(nonatomic,assign) NSInteger number;
/**
* 一個人可以擁有多輛車
*/
@property(nonatomic,strong) NSMutableArray *carArray;
@end
- Car.h
//
// Car.h
// FMDBDemo
#import <Foundation/Foundation.h>
@interface Car : NSObject
/**
* 所有者
*/
@property(nonatomic,strong ) NSNumber *own_id;
/**
* 車的ID
*/
@property(nonatomic,strong) NSNumber *car_id;
@property(nonatomic,copy) NSString *brand;
@property(nonatomic,assign) NSInteger price;
@end
1.3 呼叫層
- 類名
- FMViewController.m
- 示例方法: 新增一個人
#pragma mark - Action
/**
* 新增資料到資料庫
*/
- (void)addData{
NSLog(@"addData");
int nameRandom = arc4random_uniform(1000);
NSInteger ageRandom = arc4random_uniform(100) + 1;
NSString *name = [NSString stringWithFormat:@"person_%d號",nameRandom];
NSInteger age = ageRandom;
Person *person = [[Person alloc] init];
person.name = name;
person.age = age;
[[DataBase sharedDataBase] addPerson:person];
self.dataArray = [[DataBase sharedDataBase] getAllPerson];
[self.tableView reloadData];
}
- 知識點
- arc4random_uniform(uint32_t)會隨機返回一個0到上界之間(不含上界)的整數。另外,arc4random(void)這個全域性函式會生成9位數的隨機整數。具體參考 http://www.jianshu.com/p/51269165c3e0 。
-
[[DataBase sharedDataBase] addPerson:person];
中的sharedDataBase
方法返回一個該類的靜態例項static DataBase *_DBCtl
。如上重寫了sharedDataBase
和allocWithZone
等方法,DataBase是一個單例類。
2. 多執行緒生物:FMDatabaseQueue類
2.1 封裝
2.1.1 單例類:負責建立FMDatabaseQueue和它的資料庫
- DataBaseManager.m
//
// DataBaseManager.m
#import "DataBaseManager.h"
@implementation DataBaseManager
//單例
+(instancetype)sharedManager{
static DataBaseManager *manager = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
manager = [[DataBaseManager alloc] init];
});
return manager;
}
- (instancetype)init{
if (self = [super init]) {
NSString *docDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [docDir stringByAppendingPathComponent:@"DB2.sqlite"];
NSLog(@"路徑 = %@",filePath);
_databaseQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];
}
return self;
}
@end
2.1.2 資料庫訪問類·基本類(DAO):負責建立表
- DAO.m
//
// DAO.m
#import "DAO.h"
@implementation DAO
- (FMDatabaseQueue *)databaseQueue{
return [DataBaseManager sharedManager].databaseQueue;;
}
+ (void)creatTableIfNeed{
//【注意】:以後做sql語句,()外面的引數用字串拼,()裡面的引數放在執行方法裡寫
NSString *sql = [NSString stringWithFormat:@"CREATE TABLE IF NOT EXISTS %@ (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER, score REAL,arr BLOB,dic BLOB,book BLOB,date,img BLOB)",KTable_UserName];
[[DataBaseManager sharedManager].databaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
if ([db executeUpdate:sql]) {
NSLog(@"建立表成功");
}
}];
}
@end
2.1.3 資料庫訪問類·使用者類(UserDAO):負責增刪改查
- UserDAO.m
//
// UserDAO.m
#import "UserDAO.h"
#import "UserDTO.h"
@implementation UserDAO
///單例
+(instancetype)sharedInstance{
static UserDAO *instance = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
//1.
instance = [[UserDAO alloc] init];
});
return instance;
}
/*!
* 插入資料
* 【注意】:???
*/
- (BOOL)insertUserDTO:(UserDTO *)dto{
__block BOOL success = NO;
NSString *sql = [NSString stringWithFormat:@"insert into %@ (name,age,score,arr,dic,book,date,img) values (?,?,?,?,?,?,?,?)",KTable_UserName];
[self.databaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
if (![db executeUpdate:sql,
dto.name,
@(dto.age),
@(dto.score),
[NSKeyedArchiver archivedDataWithRootObject:dto.arr],
[NSKeyedArchiver archivedDataWithRootObject:dto.dic],
[NSKeyedArchiver archivedDataWithRootObject:dto.book],
dto.date,
UIImagePNGRepresentation(dto.img)]) {
*rollback = YES;
return ;
}
success = YES;
}];
return success;
}
/*!
* 獲取資料
*/
- (NSMutableArray *)loadUserData{
NSString *sql = [NSString stringWithFormat:@"select * from %@",KTable_UserName];
NSMutableArray *arrM = [NSMutableArray array];
[self.databaseQueue inDatabase:^(FMDatabase *db) {
FMResultSet *re = [db executeQuery:sql];
while ([re next]) {
UserDTO *dto = [[UserDTO alloc] init];
dto.name = [re stringForColumn:@"name"];
dto.age = [re intForColumn:@"age"];
dto.score = [re doubleForColumn:@"score"];
dto.arr = [NSKeyedUnarchiver unarchiveObjectWithData:[re dataForColumn:@"arr"]];
dto.dic = [NSKeyedUnarchiver unarchiveObjectWithData:[re dataForColumn:@"dic"]];
dto.book = [NSKeyedUnarchiver unarchiveObjectWithData:[re dataForColumn:@"book"]];
dto.date = [re dateForColumn:@"date"];
dto.img = [UIImage imageWithData:[re dataForColumn:@"img"]];
[arrM addObject:dto];
NSLog(@"名字 = %@",dto.name);
NSLog(@"陣列 = %@",dto.arr);
NSLog(@"字典 = %@",dto.dic);
NSLog(@"BOOK = %@",dto.book);
NSLog(@"時間 = %@",dto.date);
}
}];
return arrM;
}
/*!
* 修改資料
* 【注意】
1.這個引數,可要又不要,最好要,然後把新的模型賦值過去
2.修改兩個值的格式是: set a = 'a' , b = 'b' 一定不要少了中間的逗號
3.是單引號,不可使用雙引號,所有的都是
*/
- (BOOL)updateUserDTO:(UserDTO *)dto{
__block BOOL success = NO;
NSString *sql = [NSString stringWithFormat:@"update %@ set name = '%@',score = '%@'",KTable_UserName,@"郭美美",@(100)];
[self.databaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
if (![db executeUpdate:sql]) {
*rollback = YES;
return ;
}
success = YES;
}];
return success;
}
/*!
* 刪除某個模型 (簡單)
*/
- (BOOL)deleteUserDTO:(UserDTO *)dto{
__block BOOL success = NO;
NSString *sql = [NSString stringWithFormat:@"delete from %@ where name = '%@'",KTable_UserName, dto.name];
[self.databaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
if (![db executeUpdate:sql]) {
*rollback = YES;
return ;
}
success = YES;
}];
return success;
}
/*!
* 刪除某個模型 (簡單)
*/
- (BOOL)deleteAllUserDTO{
__block BOOL success = NO;
NSString *sql = [NSString stringWithFormat:@"delete from %@",KTable_UserName];
[self.databaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
if (![db executeUpdate:sql]) {
*rollback = YES;
return ;
}
success = YES;
}];
return success;
}
@end
2.2 模型層
2.2.1 使用者資料操作物件UserDTO
- UserDTO.h
//
// UserDTO.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@class BookDTO;
@interface UserDTO : NSObject
/** 基本屬性 */
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, assign) float score;
/** 陣列 */
@property (nonatomic, strong) NSArray *arr;
/** 字典 */
@property (nonatomic, strong) NSDictionary *dic;
/** 自定義模型 */
@property (nonatomic, strong) BookDTO *book;
/** 時間 */
@property (nonatomic, strong) NSDate *date;
/** 圖片 */
@property (nonatomic, strong) UIImage *img;
///構造器1
+ (UserDTO *)userWithUserName:(NSString *)userName
userAge:(NSInteger)userAge
userScore:(float)userScore
arr:(NSArray *)arr
dic:(NSDictionary *)dic
book:(BookDTO *)book
date:(NSDate *)date
image:(UIImage *)img;
@end
2.2.2 書本資料操作物件BookDTO
- BookDTO.h
//
// BookDTO.h
#import <Foundation/Foundation.h>
@interface BookDTO : NSObject
@property (nonatomic, copy) NSString *bookName;
@property (nonatomic, assign) NSInteger bookId;
/** 建立book */
+ (BookDTO *)bookWithBookName:(NSString *)bookName bookId:(NSInteger)bookId;
@end
2.3 呼叫層
- 類名
- DBQViewController.m
- 示例方法:
#pragma mark - Action
/**
* 新增資料到資料庫
*/
- (void)addData{
NSLog(@"addData");
int nameRandom = arc4random_uniform(1000);
NSInteger ageRandom = arc4random_uniform(100) + 1;
NSString *name = [NSString stringWithFormat:@"person_%d號",nameRandom];
NSInteger age = ageRandom;
NSArray *arrr = @[@"t1",@"t2",@"t3"];
NSDictionary *dic = @{@"key":@"t123"};
BookDTO *bDto = [BookDTO bookWithBookName:@"NewBook" bookId:0011];
UIImage *imag = [UIImage imageNamed:@"1"];
NSLog(@"date = %@",[NSDate date]);
//2016-05-11 10:04:50 +0000
//2016-05-11 10:05:40 +0000 13
//2016-05-11 10:06:01 +0000 31
UserDTO *dto = [UserDTO userWithUserName:name userAge:age userScore:9 arr:arrr dic:dic book:bDto date:[NSDate date] image:imag];
if ([[UserDAO sharedInstance] insertUserDTO:dto]) {
NSLog(@"插入成功");
}else{
NSLog(@"插入失敗");
}
self.dataArray = [[UserDAO sharedInstance] loadUserData];
[self.tableView reloadData];
}
3. 小結
- FMDatabase建立資料庫的路徑程式碼為:
// 獲得Documents目錄路徑
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
// 檔案路徑
NSString *filePath = [documentsPath stringByAppendingPathComponent:@"DB1.sqlite"];
- FMDatabaseQueue建立資料庫的路徑程式碼為:
if (self = [super init]) {
NSString *docDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
NSString *filePath = [docDir stringByAppendingPathComponent:@"DB2.sqlite"];
NSLog(@"路徑 = %@",filePath);
_databaseQueue = [FMDatabaseQueue databaseQueueWithPath:filePath];
}
return self;
- 執行的時候,你可以打個斷點,列印出資料庫的路徑,類似如下:
/Users/ChenMan/Library/Developer/CoreSimulator/Devices/12B1701D-AFF5-4D6B-9923-86CE8AE0C387/data/Containers/Data/Application/6512C7E8-BD84-47FC-9284-00E7BBBD67D8/Documents/
- 回到電腦桌面,按住快捷鍵shift+command+G,到達如上檔案目錄,可以看到檔案目錄結構類似如下:
- 用Navicat軟體開啟.sqlite檔案可以視覺化的檢視錶的資料,類似如下:
參考文獻:
相關文章
- 【Pandas學習筆記02】-資料處理高階用法筆記
- Axios用法–學習筆記iOS筆記
- YOLOv3學習筆記之資料處理YOLO筆記
- SpringMVC 學習筆記(四) 處理模型資料SpringMVC筆記模型
- Python深度學習(處理文字資料)--學習筆記(十二)Python深度學習筆記
- iOS開發原始碼閱讀篇--FMDB原始碼分析1(FMResultSet)iOS原始碼
- iOS開發原始碼閱讀篇--FMDB原始碼分析2(FMResultSet)iOS原始碼
- 深度學習——資料預處理篇深度學習
- 資料庫學習筆記資料庫筆記
- IOS資料儲存之FMDB資料庫iOS資料庫
- 【Pandas學習筆記02】處理資料實用操作筆記
- MyBatis原始碼學習筆記(一) 初遇篇MyBatis原始碼筆記
- iOS學習筆記-動畫篇1iOS筆記動畫
- 飛機的 PHP 學習筆記之資料庫篇PHP筆記資料庫
- iOS開發原始碼閱讀篇--FMDB原始碼分析3(FMDatabaseQueue+FMDatabasePool)iOS原始碼Database
- [OCP學習筆記]043-07-處理資料庫損壞--基本知識筆記資料庫
- MySQL資料庫學習筆記MySql資料庫筆記
- Ajax學習筆記demo筆記
- React學習筆記-事件處理React筆記事件
- 雲端計算學習路線原始碼框架筆記:Mysql原始碼一原始碼框架筆記MySql
- 雲端計算學習路線原始碼框架筆記:Mysql原始碼二原始碼框架筆記MySql
- 雲端計算學習路線原始碼框架筆記:Mysql原始碼三原始碼框架筆記MySql
- 資料庫mysql學習筆記記錄資料庫MySql筆記
- 【筆記】jQuery原始碼(文件處理3)筆記jQuery原始碼
- 資料預處理 demo
- Retrofit原始碼學習筆記原始碼筆記
- Redis學習筆記(七) 資料庫Redis筆記資料庫
- 達夢資料庫學習筆記資料庫筆記
- python學習筆記:資料庫Python筆記資料庫
- 資料庫原理學習筆記——引言資料庫筆記
- 電商大資料學習筆記:理論大資料筆記
- 筆記-FMDB筆記
- [OCP學習筆記]043-07-處理資料庫損壞--模擬實驗(2)筆記資料庫
- [OCP學習筆記]043-07-處理資料庫損壞--模擬實驗(1)筆記資料庫
- Vue學習筆記之事件處理Vue筆記事件
- iOS FMDB資料庫之增刪改查使用iOS資料庫
- Qt Creator 原始碼學習筆記02,認識框架結構QT原始碼筆記框架
- 大資料學習筆記(十五)-大資料排程框架大資料筆記框架