iOS开发系列--详细介绍数据存取


本文整理自网络,侵删。

概览

在iOS开发中数据存储的方式可以归纳为两类:一类是存储为文件,另一类是存储到数据库。例如前面IOS开发系列—Objective-C之Foundation框架的文章中提到归档、plist文件存储,包括偏好设置其本质都是存储为文件,只是说归档或者plist文件存储可以选择保存到沙盒中,而偏好设置系统已经规定只能保存到沙盒的Library/Preferences目录。当然,文件存储并不作为本文的重点内容。本文重点还是说数据库存储,做过数据库开发的朋友应该知道,可以通过SQL直接访问数据库,也可以通过ORM进行对象关系映射访问数据库。这两种方式恰恰对应iOS中SQLite和Core Data的内容,在此将重点进行分析:

  • SQLite
  • Core Data
  • FMDB

SQLite

SQLite是目前主流的嵌入式关系型数据库,其最主要的特点就是轻量级、跨平台,当前很多嵌入式操作系统都将其作为数据库首选。虽然SQLite是一款轻型数据库,但是其功能也绝不亚于很多大型关系数据库。学习数据库就要学习其相关的定义、操作、查询语言,也就是大家日常说得SQL语句。和其他数据库相比,SQLite中的SQL语法并没有太大的差别,因此这里对于SQL语句的内容不会过多赘述,大家可以参考SQLite中其他SQL相关的内容,这里还是重点讲解iOS中如何使用SQLite构建应用程序。先看一下SQLite数据库的几个特点:

  • 基于C语言开发的轻型数据库
  • 在iOS中需要使用C语言语法进行数据库操作、访问(无法使用ObjC直接访问,因为libsqlite3框架基于C语言编写)
  • SQLite中采用的是动态数据类型,即使创建时定义了一种类型,在实际操作时也可以存储其他类型,但是推荐建库时使用合适的类型(特别是应用需要考虑跨平台的情况时)
  • 建立连接后通常不需要关闭连接(尽管可以手动关闭)

要使用SQLite很简单,如果在Mac OSX上使用可以考虑到SQLite网站下载命令行工具,也可以使用类似于SQLiteManager、Navicat for SQLite等工具。为了方便大家开发调试,建议在开发环境中安装上述工具。

在iOS中操作SQLite数据库可以分为以下几步(注意先在项目中导入libsqlite3框架):

  1. 打开数据库,利用sqlite3_open()打开数据库会指定一个数据库文件保存路径,如果文件存在则直接打开,否则创建并打开。打开数据库会得到一个sqlite3类型的对象,后面需要借助这个对象进行其他操作。
  2. 执行SQL语句,执行SQL语句又包括有返回值的语句和无返回值语句。
  3. 对于无返回值的语句(如增加、删除、修改等)直接通过sqlite3_exec()函数执行;
  4. 对于有返回值的语句则首先通过sqlite3_prepare_v2()进行sql语句评估(语法检测),然后通过sqlite3_step()依次取出查询结果的每一行数据,对于每行数据都可以通过对应的sqlite3_column_类型()方法获得对应列的数据,如此反复循环直到遍历完成。当然,最后需要释放句柄。

在整个操作过程中无需管理数据库连接,对于嵌入式SQLite操作是持久连接(尽管可以通过sqlite3_close()关闭),不需要开发人员自己释放连接。纵观整个操作过程,其实与其他平台的开发没有明显的区别,较为麻烦的就是数据读取,在iOS平台中使用C进行数据读取采用了游标的形式,每次只能读取一行数据,较为麻烦。因此实际开发中不妨对这些操作进行封装:

KCDbManager.h

//
// DbManager.h
// DataAccess
//
// Created by Kenshin Cui on 14-3-29.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <sqlite3.h>
#import "KCSingleton.h"

@interface KCDbManager : NSObject

singleton_interface(KCDbManager);

#pragma mark - 属性
#pragma mark 数据库引用,使用它进行数据库操作
@property (nonatomic) sqlite3 *database;


#pragma mark - 共有方法
/**
 * 打开数据库
 *
 * @param dbname 数据库名称
 */
-(void)openDb:(NSString *)dbname;

/**
 * 执行无返回值的sql
 *
 * @param sql sql语句
 */
-(void)executeNonQuery:(NSString *)sql;

/**
 * 执行有返回值的sql
 *
 * @param sql sql语句
 *
 * @return 查询结果
 */
-(NSArray *)executeQuery:(NSString *)sql;
@end

KCDbManager.m

//
// DbManager.m
// DataAccess
//
// Created by Kenshin Cui on 14-3-29.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCDbManager.h"
#import <sqlite3.h>
#import "KCSingleton.h"
#import "KCAppConfig.h"

#ifndef kDatabaseName

#define kDatabaseName @"myDatabase.db"

#endif

@interface KCDbManager()
@end

@implementation KCDbManager

singleton_implementation(KCDbManager)

#pragma mark 重写初始化方法
-(instancetype)init{
  KCDbManager *manager;
  if((manager=[super init]))
  {
    [manager openDb:kDatabaseName];
  }
  return manager;
}

-(void)openDb:(NSString *)dbname{
  //取得数据库保存路径,通常保存沙盒Documents目录
  NSString *directory=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  NSLog(@"%@",directory);
  NSString *filePath=[directory stringByAppendingPathComponent:dbname];
  //如果有数据库则直接打开,否则创建并打开(注意filePath是ObjC中的字符串,需要转化为C语言字符串类型)
  if (SQLITE_OK ==sqlite3_open(filePath.UTF8String, &_database)) {
    NSLog(@"数据库打开成功!");
  }else{
    NSLog(@"数据库打开失败!");
  }
}

-(void)executeNonQuery:(NSString *)sql{
  char *error;
  //单步执行sql语句,用于插入、修改、删除
  if (SQLITE_OK!=sqlite3_exec(_database, sql.UTF8String, NULL, NULL,&error)) {
    NSLog(@"执行SQL语句过程中发生错误!错误信息:%s",error);
  }
}

-(NSArray *)executeQuery:(NSString *)sql{
  NSMutableArray *rows=[NSMutableArray array];//数据行
  
  //评估语法正确性
  sqlite3_stmt *stmt;
  //检查语法正确性
  if (SQLITE_OK==sqlite3_prepare_v2(_database, sql.UTF8String, -1, &stmt, NULL)) {
    //单步执行sql语句
    while (SQLITE_ROW==sqlite3_step(stmt)) {
      int columnCount= sqlite3_column_count(stmt);
      NSMutableDictionary *dic=[NSMutableDictionary dictionary];
      for (int i=0; i<columnCount; i++) {
        const char *name= sqlite3_column_name(stmt, i);//取得列名
        const unsigned char *value= sqlite3_column_text(stmt, i);//取得某列的值
        dic[[NSString stringWithUTF8String:name]]=[NSString stringWithUTF8String:(const char *)value];
      }
      [rows addObject:dic];
    }
  }
  
  //释放句柄
  sqlite3_finalize(stmt);
  
  return rows;
}
@end

在上面的类中对于数据库操作进行了封装,封装之后数据操作更加方便,同时所有的语法都由C转换成了ObjC。

下面仍然以微博查看为例进行SQLite演示。当然实际开发中微博数据是从网络读取的,但是考虑到缓存问题,通常会选择将微博数据保存到本地,下面的Demo演示了将数据存放到本地数据库以及数据读取的过程。当然,实际开发中并不会在视图控制器中直接调用数据库操作方法,在这里通常会引入两个概念Model和Service。Model自不必多说,就是MVC中的模型。而Service指的是操作数据库的服务层,它封装了对于Model的基本操作方法,实现具体的业务逻辑。为了解耦,在控制器中是不会直接接触数据库的,控制器中只和模型(模型是领域的抽象)、服务对象有关系,借助服务层对模型进行各类操作,模型的操作反应到数据库中就是对表中数据的操作。具体关系如下:
要完成上述功能,首先定义一个应用程序全局对象进行数据库、表的创建。为了避免每次都创建数据库和表出错,这里利用了偏好设置进行保存当前创建状态(其实这也是数据存储的一部分),如果创建过了数据库则不再创建,否则创建数据库和表。

KCDatabaseCreator.m

//
// KCDatabaseCreator.m
// DataAccess
//
// Created by Kenshin Cui on 14-3-29.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCDatabaseCreator.h"
#import "KCDbManager.h"

@implementation KCDatabaseCreator

+(void)initDatabase{
  NSString *key=@"IsCreatedDb";
  NSUserDefaults *defaults=[[NSUserDefaults alloc]init];
  if ([[defaults valueForKey:key] intValue]!=1) {
    [self createUserTable];
    [self createStatusTable];
    [defaults setValue:@1 forKey:key];
  }
}

+(void)createUserTable{
  NSString *sql=@"CREATE TABLE User (Id integer PRIMARY KEY AUTOINCREMENT,name text,screenName text, profileImageUrl text,mbtype text,city text)";
  [[KCDbManager sharedKCDbManager] executeNonQuery:sql];
}

+(void)createStatusTable{
  NSString *sql=@"CREATE TABLE Status (Id integer PRIMARY KEY AUTOINCREMENT,source text,createdAt date,\"text\" text,user integer REFERENCES User (Id))";
  [[KCDbManager sharedKCDbManager] executeNonQuery:sql];
}
@end

其次,定义数据模型,这里定义用户User和微博Status两个数据模型类。注意模型应该尽量保持其单纯性,仅仅是简单的POCO,不要引入视图、控制器等相关内容。

KCUser.h

//
// KCUser.h
// UrlConnection
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface KCUser : NSObject

#pragma mark 编号
@property (nonatomic,strong) NSNumber *Id;

#pragma mark 用户名
@property (nonatomic,copy) NSString *name;

#pragma mark 用户昵称
@property (nonatomic,copy) NSString *screenName;

#pragma mark 头像
@property (nonatomic,copy) NSString *profileImageUrl;

#pragma mark 会员类型
@property (nonatomic,copy) NSString *mbtype;

#pragma mark 城市
@property (nonatomic,copy) NSString *city;

#pragma mark - 动态方法

/**
 * 初始化用户
 *
 * @param name 用户名
 * @param city 所在城市
 *
 * @return 用户对象
 */
-(KCUser *)initWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city;

/**
 * 使用字典初始化用户对象
 *
 * @param dic 用户数据
 *
 * @return 用户对象
 */
-(KCUser *)initWithDictionary:(NSDictionary *)dic;

#pragma mark - 静态方法
+(KCUser *)userWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city;
@end
KCUser.m

//
// KCUser.m
// UrlConnection
//
// Created by Kenshin Cui on 14-3-22.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCUser.h"

@implementation KCUser

-(KCUser *)initWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city{
  if (self=[super init]) {
    self.name=name;
    self.screenName=screenName;
    self.profileImageUrl=profileImageUrl;
    self.mbtype=mbtype;
    self.city=city;
  }
  return self;
}


-(KCUser *)initWithDictionary:(NSDictionary *)dic{
  if (self=[super init]) {
    [self setValuesForKeysWithDictionary:dic];
  }
  return self;
}

+(KCUser *)userWithName:(NSString *)name screenName:(NSString *)screenName profileImageUrl:(NSString *)profileImageUrl mbtype:(NSString *)mbtype city:(NSString *)city{
  KCUser *user=[[KCUser alloc]initWithName:name screenName:screenName profileImageUrl:profileImageUrl mbtype:mbtype city:city];
  return user;
}

@end

KCStatus.h

//
// KCStatus.h
// UITableView
//
// Created by Kenshin Cui on 14-3-1.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "KCUser.h"

@interface KCStatus : NSObject

#pragma mark - 属性
@property (nonatomic,strong) NSNumber *Id;//微博id
@property (nonatomic,strong) KCUser *user;//发送用户
@property (nonatomic,copy) NSString *createdAt;//创建时间
@property (nonatomic,copy) NSString *source;//设备来源
@property (nonatomic,copy) NSString *text;//微博内容

#pragma mark - 动态方法

/**
 * 初始化微博数据
 *
 * @param createAt    创建日期
 * @param source     来源
 * @param text      微博内容
 * @param user      发送用户
 *
 * @return 微博对象
 */
-(KCStatus *)initWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text user:(KCUser *)user;

/**
 * 初始化微博数据
 *
 * @param profileImageUrl 用户头像
 * @param mbtype     会员类型
 * @param createAt    创建日期
 * @param source     来源
 * @param text      微博内容
 * @param userId     用户编号
 *
 * @return 微博对象
 */
-(KCStatus *)initWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text userId:(int)userId;
/**
 * 使用字典初始化微博对象
 *
 * @param dic 字典数据
 *
 * @return 微博对象
 */
-(KCStatus *)initWithDictionary:(NSDictionary *)dic;

#pragma mark - 静态方法
/**
 * 初始化微博数据
 *
 * @param createAt    创建日期
 * @param source     来源
 * @param text      微博内容
 * @param user      发送用户
 *
 * @return 微博对象
 */
+(KCStatus *)statusWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text user:(KCUser *)user;
/**
 * 初始化微博数据
 *
 * @param profileImageUrl 用户头像
 * @param mbtype     会员类型
 * @param createAt    创建日期
 * @param source     来源
 * @param text      微博内容
 * @param userId     用户编号
 *
 * @return 微博对象
 */
+(KCStatus *)statusWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text userId:(int)userId;

@end

KCStatus.m

//
// KCStatus.m
// UITableView
//
// Created by Kenshin Cui on 14-3-1.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCStatus.h"

@implementation KCStatus

-(KCStatus *)initWithDictionary:(NSDictionary *)dic{
  if (self=[super init]) {
    [self setValuesForKeysWithDictionary:dic];
    self.user=[[KCUser alloc]init];
    self.user.Id=dic[@"user"];
  }
  return self;
}

-(KCStatus *)initWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text user:(KCUser *)user{
  if (self=[super init]) {
    self.createdAt=createAt;
    self.source=source;
    self.text=text;
    self.user=user;
  }
  return self;
}

-(KCStatus *)initWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text userId:(int)userId{
  if (self=[super init]) {
    self.createdAt=createAt;
    self.source=source;
    self.text=text;
    KCUser *user=[[KCUser alloc]init];
    user.Id=[NSNumber numberWithInt:userId];
    self.user=user;
  }
  return self;
}

-(NSString *)source{
  return [NSString stringWithFormat:@"来自 %@",_source];
}

+(KCStatus *)statusWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text user:(KCUser *)user{
  KCStatus *status=[[KCStatus alloc]initWithCreateAt:createAt source:source text:text user:user];
  return status;
}

+(KCStatus *)statusWithCreateAt:(NSString *)createAt source:(NSString *)source text:(NSString *)text userId:(int)userId{
  KCStatus *status=[[KCStatus alloc]initWithCreateAt:createAt source:source text:text userId:userId];
  return status;
}
@end

然后,编写服务类,进行数据的增、删、改、查操作,由于服务类方法同样不需要过多的配置,因此定义为单例,保证程序中只有一个实例即可。服务类中调用前面封装的数据库方法将对数据库的操作转换为对模型的操作。

KCUserService.h

//
// KCUserService.h
// DataAccess
//
// Created by Kenshin Cui on 14-3-29.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "KCUser.h"
#import "KCSingleton.h"

@interface KCUserService : NSObject
singleton_interface(KCUserService)

/**
 * 添加用户信息
 *
 * @param user 用户对象
 */
-(void)addUser:(KCUser *)user;

/**
 * 删除用户
 *
 * @param user 用户对象
 */
-(void)removeUser:(KCUser *)user;

/**
 * 根据用户名删除用户
 *
 * @param name 用户名
 */
-(void)removeUserByName:(NSString *)name;

/**
 * 修改用户内容
 *
 * @param user 用户对象
 */
-(void)modifyUser:(KCUser *)user;

/**
 * 根据用户编号取得用户
 *
 * @param Id 用户编号
 *
 * @return 用户对象
 */
-(KCUser *)getUserById:(int)Id;

/**
 * 根据用户名取得用户
 *
 * @param name 用户名
 *
 * @return 用户对象
 */
-(KCUser *)getUserByName:(NSString *)name;

@end

KCUserService.m

//
// KCUserService.m
// DataAccess
//
// Created by Kenshin Cui on 14-3-29.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCUserService.h"
#import "KCUser.h"
#import "KCDbManager.h"

@implementation KCUserService
singleton_implementation(KCUserService)

-(void)addUser:(KCUser *)user{
  NSString *sql=[NSString stringWithFormat:@"INSERT INTO User (name,screenName, profileImageUrl,mbtype,city) VALUES('%@','%@','%@','%@','%@')",user.name,user.screenName, user.profileImageUrl,user.mbtype,user.city];
  [[KCDbManager sharedKCDbManager] executeNonQuery:sql];
}

-(void)removeUser:(KCUser *)user{
  NSString *sql=[NSString stringWithFormat:@"DELETE FROM User WHERE Id='%@'",user.Id];
  [[KCDbManager sharedKCDbManager] executeNonQuery:sql];
}

-(void)removeUserByName:(NSString *)name{
  NSString *sql=[NSString stringWithFormat:@"DELETE FROM User WHERE name='%@'",name];
  [[KCDbManager sharedKCDbManager] executeNonQuery:sql];
}

-(void)modifyUser:(KCUser *)user{
  NSString *sql=[NSString stringWithFormat:@"UPDATE User SET name='%@',screenName='%@',profileImageUrl='%@',mbtype='%@',city='%@' WHERE Id='%@'",user.name,user.screenName,user.profileImageUrl,user.mbtype,user.city,user.Id];
  [[KCDbManager sharedKCDbManager] executeNonQuery:sql];
}

-(KCUser *)getUserById:(int)Id{
  KCUser *user=[[KCUser alloc]init];
  NSString *sql=[NSString stringWithFormat:@"SELECT name,screenName,profileImageUrl,mbtype,city FROM User WHERE Id='%i'", Id];
  NSArray *rows= [[KCDbManager sharedKCDbManager] executeQuery:sql];
  if (rows&&rows.count>0) {
    [user setValuesForKeysWithDictionary:rows[0]];
  }
  return user;
}

-(KCUser *)getUserByName:(NSString *)name{
  KCUser *user=[[KCUser alloc]init];
  NSString *sql=[NSString stringWithFormat:@"SELECT Id, name,screenName,profileImageUrl,mbtype,city FROM User WHERE name='%@'", name];
  NSArray *rows= [[KCDbManager sharedKCDbManager] executeQuery:sql];
  if (rows&&rows.count>0) {
    [user setValuesForKeysWithDictionary:rows[0]];
  }
  return user;
}
@end

KCStatusService.h

//
// KCStatusService.h
// DataAccess
//
// Created by Kenshin Cui on 14-3-29.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "KCSingleton.h"
@class KCStatus;

@interface KCStatusService : NSObject
singleton_interface(KCStatusService)

/**
 * 添加微博信息
 *
 * @param status 微博对象
 */
-(void)addStatus:(KCStatus *)status;

/**
 * 删除微博
 *
 * @param status 微博对象
 */
-(void)removeStatus:(KCStatus *)status;

/**
 * 修改微博内容
 *
 * @param status 微博对象
 */
-(void)modifyStatus:(KCStatus *)status;

/**
 * 根据编号取得微博
 *
 * @param Id 微博编号
 *
 * @return 微博对象
 */
-(KCStatus *)getStatusById:(int)Id;

/**
 * 取得所有微博对象
 *
 * @return 所有微博对象
 */
-(NSArray *)getAllStatus;
@end

KCStatusService.m

//
// KCStatusService.m
// DataAccess
//
// Created by Kenshin Cui on 14-3-29.
// Copyright (c) 2014年 Kenshin Cui. All rights reserved.
//

#import "KCStatusService.h"
#import "KCDbManager.h"
#import "KCStatus.h"
#import "KCUserService.h"
#import "KCSingleton.h"

@interface KCStatusService(){
  
}

@end

@implementation KCStatusService
singleton_implementation(KCStatusService)


-(void)addStatus:(KCStatus *)status{
  NSString *sql=[NSString stringWithFormat:@"INSERT INTO Status (source,createdAt,\"text\" ,user) VALUES('%@','%@','%@','%@')",status.source,status.createdAt,status.text,status.user.Id];
  [[KCDbManager sharedKCDbManager] executeNonQuery:sql];
}

-(void)removeStatus:(KCStatus *)status{
  NSString *sql=[NSString stringWithFormat:@"DELETE FROM Status WHERE Id='%@'",status.Id];
  [[KCDbManager sharedKCDbManager] executeNonQuery:sql];
}

-(void)modifyStatus:(KCStatus *)status{
  NSString *sql=[NSString stringWithFormat:@"UPDATE Status SET source='%@',createdAt='%@',\"text\"='%@' ,user='%@' WHERE Id='%@'",status.source,status.createdAt,status.text,status.user, status.Id];
  [[KCDbManager sharedKCDbManager] executeNonQuery:sql];
}

-(KCStatus *)getStatusById:(int)Id{
  KCStatus *status=[[KCStatus alloc]init];
  NSString *sql=[NSString stringWithFormat:@"SELECT Id, source,createdAt,\"text\" ,user FROM Status WHERE Id='%i'", Id];
  NSArray *rows= [[KCDbManager sharedKCDbManager] executeQuery:sql];
  if (rows&&rows.count>0) {
    [status setValuesForKeysWithDictionary:rows[0]];
    status.user=[[KCUserService sharedKCUserService] getUserById:[(NSNumber *)rows[0][@"user"] intValue]] ;
  }
  return status;
}

-(NSArray *)getAllStatus{
  NSMutableArray *array=[NSMutableArray array];
  NSString *sql=@"SELECT Id, source,createdAt,\"text\" ,user FROM Status ORDER BY Id";
  NSArray *rows= [[KCDbManager sharedKCDbManager] executeQuery:sql];
  for (NSDictionary *dic in rows) {
    KCStatus *status=[self getStatusById:[(NSNumber *)dic[@"Id"] intValue]];
    [array addObject:status];
  }
  return array;
}
@end

最后,在视图控制器中调用相应的服务层进行各类数据操作,在下面的代码中分别演示了增、删、改、查四类操作。

阅读剩余部分

相关阅读 >>

详解android数据存储之sqlcipher数据库加密

python执行数据库的查询操作实例讲解

python数据库Sqlite3图文实例详解

sql的常用数据类型列表详解

易语言操作edb数据库的方法

Sqlite教程(五):索引和数据分析清理

android sharepreferences与数据库Sqlite存储实现方法介绍

ios开发系列--详细介绍数据存取

android通用流行框架大全【整理】

sql学习之case when then else end的用法

更多相关阅读请进入《Sqlite》频道 >>


数据库系统概念 第6版
书籍

数据库系统概念 第6版

机械工业出版社

本书主要讲述了数据模型、基于对象的数据库和XML、数据存储和查询、事务管理、体系结构等方面的内容。



打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,您说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

评论

管理员已关闭评论功能...