基于脚手架微服务的视频点播系统-脚手架开发部分Fast-dfs,redis++,odb的简单应用与二次封装
一.FastDfs
FastDFS是⼀款开源的分布式⽂件系统,功能主要包括:⽂件存储、⽂件同步、⽂件访问(⽂件上传、⽂件下载)等,解决了⽂件⼤容量存储和⾼性能访问的问题。FastDFS特别适合以⽂件为载体的在线服务,如图⽚、视频、⽂档等等服务。
FastDFS作为⼀款轻量级分布式⽂件系统,版本V6.01代码量6.3万⾏。FastDFS⽤C语⾔实现,⽀持Linux、FreeBSD、MacOS等类UNIX系统。FastDFS类似google FS,属于应⽤级⽂件系统,不是通⽤的⽂件系统,只能通过专有API访问,⽬前提供了C客⼾端和Java SDK,以及PHP扩展SDK。
FastDFS为互联⽹应⽤量⾝定做,解决⼤容量⽂件存储问题,实现⾼性能和⾼扩展性。FastDFS可以看做是基于⽂件的key value存储系统,key为⽂件ID,value为⽂件本⾝,因此称作分布式⽂件存储服务更为合适。
1.1SDK安装
git clone https://github.com/happyfish100/libfastcommon.git --depth 1
cd libfastcommon/ && ./make.sh && sudo ./make.sh install
cd ..
git clone https://github.com/happyfish100/libserverframe.git --depth 1
cd libserverframe/ && ./make.sh && sudo ./make.sh install
cd ..
git clone https://github.com/happyfish100/fastdfs.git --depth 1
cd fastdfs/ && ./make.sh && sudo ./make.sh install #编译安装#配置⽂件准备
sudo cp conf/http.conf /etc/fdfs/ #供nginx访问使⽤
sudo cp conf/mime.types /etc/fdfs/
1.2接口
1.2.1头文件
#include
#include
1.2.2链接库,Log,Error
-lfdfsclient -lfastcommon
//fastdfs有⾃⼰的⽇志输出模块(⽆法替换成为咱们封装的spdlog⽇志输出,除⾮你去修改⼈家fastdfs
//的源代码,将所有的⽇志输出操作进⾏替换),这⾥介绍fastdfs的⽇志相关操作,主要是为了让
//fastdfs只输出错误⽇志,避免因为⼤量的第三⽅库⽇志输出影响我们的视线。
typedef struct log_context {
//log level value please see: sys/syslog.h
//default value is LOG_INFO
int log_level;
int log_fd;
//.....
}
extern LogContext g_log_context;
/*
初始化全局⽇志上下⽂,return: 0 for success, != 0 fail
*/
int log_init();
/*
忽略SIGPIPE信号,return: error no , 0 success, != 0 fail
*/
int ignore_signal_pipe();
//ERRor:⽤于获取fastdfs接⼝调⽤失败的错误原因接⼝。
#define STRERROR(no) (strerror(no) != NULL ? strerror(no) : "Unkown error")
1.2.3Init
初始化客⼾端全局配置,默认可以通过配置⽂件进⾏初始化,其实也可以将配置信息整到内存中,通过内存数据进⾏客⼾端全局配置初始化。
connect_timeout = 5
network_timeout = 60
tracker_server = 192.168.0.196:22122
use_connection_pool = false
connection_pool_max_idle_time = 3600
#define fdfs_client_init(filename) \
fdfs_client_init_ex((&g_tracker_group), filename)
//从配置⽂件初始化配置,
//return: 0 success, !=0 fail, return the error code
int fdfs_client_init_ex(TrackerServerGroup *pTrackerGroup, const char*conf_filename);
//根据给定内存数据初始化客⼾端配置
//return: 0 success, !=0 fail, return the error code
#define fdfs_client_init_from_buffer(buffer) \
fdfs_client_init_from_buffer_ex((&g_tracker_group), buffer)
int fdfs_client_init_from_buffer_ex(TrackerServerGroup *pTrackerGroup, \
const char *buffer);
#define fdfs_client_destroy() \
fdfs_client_destroy_ex((&g_tracker_group))
void fdfs_client_destroy_ex(TrackerServerGroup *pTrackerGroup);
1.2.4Tracker
获取Tracker句柄,⽤于访问tracker服务器。
typedef struct {
int sock;
uint16_t port;
short af; //address family, AF_INET, AF_INET6 or AF_UNSPEC for auto dedect
FCCommunicationType comm_type;
bool validate_flag; //for connection pool
char ip_addr[IP_ADDRESS_SIZE];
void *arg1; //for RDMA
char args[0]; //for extra data
} ConnectionInfo;
#define tracker_get_connection() \
tracker_get_connection_ex((&g_tracker_group))
ConnectionInfo *tracker_get_connection_ex(TrackerServerGroup *pTrackerGroup);
#define tracker_close_connection(pTrackerServer) \
tracker_close_connection_ex(pTrackerServer, false)
//关闭所有Tracker连接,bForceClose表⽰是否强制关闭
void tracker_close_connection_ex(ConnectionInfo *conn, const bool bForceClose);
1.2.5Storage
从Tracker服务器上获取Storage信息,并返回storage节点操作句柄。
#define FDFS_GROUP_NAME_MAX_LEN 16
#define tracker_query_storage_store(pTrackerServer, pStorageServer, \
group_name, store_path_index) \
tracker_query_storage_store_without_group(pTrackerServer, \
pStorageServer, group_name, store_path_index)
//return: 0 success, !=0 fail, return the error code
int tracker_query_storage_store_without_group(ConnectionInfo *pTrackerServer,
ConnectionInfo *pStorageServer, char *group_name,
int *store_path_index);
1.2.6文件上传接口Upload
#define storage_upload_by_filename1(pTrackerServer, pStorageServer, \
store_path_index, local_filename, file_ext_name, \
meta_list, meta_count, group_name, file_id) \
storage_upload_by_filename1_ex(pTrackerServer, pStorageServer, \
store_path_index, STORAGE_PROTO_CMD_UPLOAD_FILE, \
local_filename, file_ext_name, meta_list, meta_count, \
group_name, file_id)
/*
上传⽂件:
pTrackerServer: tracker server连接句柄
pStorageServer: storage server连接句柄,可以为NULL
store_path_index: 获取storage节点时顺便获取的索引, 若没有主动获取节点则可以
为0
local_filename: 本地要上传的⽂件路径名
file_ext_name: ⽂件扩展名,可以为null
meta_list: ⽂件元信息数组
meta_count: 元信息元素数量,若没有传递元信息数组则设置为0
group_name: 若没有主动获取storage节点,则可以设置为null
file_id: 输出参数--是⽂件上传保存成功后分配的标识ID
return: 0 success, !=0 fail, return the error code
*/
int storage_upload_by_filename1_ex(ConnectionInfo *pTrackerServer, \
ConnectionInfo *pStorageServer, const int store_path_index, \
const char cmd, const char *local_filename, \
const char *file_ext_name, const FDFSMetaData *meta_list, \
const int meta_count, const char *group_name, char *file_id);
#define storage_upload_by_filebuff1(pTrackerServer, pStorageServer, \
store_path_index, file_buff, file_size, file_ext_name, \
meta_list, meta_count, group_name, file_id) \
storage_do_upload_file1(pTrackerServer, pStorageServer, \
store_path_index, STORAGE_PROTO_CMD_UPLOAD_FILE, \
FDFS_UPLOAD_BY_BUFF, file_buff, NULL, \
file_size, file_ext_name, meta_list, meta_count, \
group_name, file_id)
int storage_do_upload_file1(ConnectionInfo *pTrackerServer, \
ConnectionInfo *pStorageServer, const int store_path_index, \
const char cmd, const int upload_type, \
const char *file_buff, void *arg, const int64_t file_size, \
const char *file_ext_name, const FDFSMetaData *meta_list, \
const int meta_count, const char *group_name, char *file_id);
//⽂件元信息配置结构
typedef struct
{
char name[FDFS_MAX_META_NAME_LEN + 1]; //key
char value[FDFS_MAX_META_VALUE_LEN + 1]; //value
} FDFSMetaData;
//获取⽂件元信息
int storage_get_metadata1(ConnectionInfo *pTrackerServer, \
ConnectionInfo *pStorageServer, \
const char *file_id, \
FDFSMetaData **meta_list, int *meta_count);
1.2.7文件下载接口Download
#define storage_download_file1(pTrackerServer, pStorageServer, file_id, \
file_buff, file_size) \
storage_do_download_file1_ex(pTrackerServer, pStorageServer, \
FDFS_DOWNLOAD_TO_BUFF, file_id, 0, 0, \
file_buff, NULL, file_size)
#define storage_download_file_to_buff1(pTrackerServer, pStorageServer, \
file_id, file_buff, file_size) \
storage_do_download_file1_ex(pTrackerServer, pStorageServer, \
FDFS_DOWNLOAD_TO_BUFF, file_id, 0, 0, \
file_buff, NULL, file_size)
/**
* 从Storage下载⽂件
* params:
* pTrackerServer: tracker server
* pStorageServer: storage server
download_type: FDFS_DOWNLOAD_TO_BUFF or FDFS_DOWNLOAD_TO_FILE
* file_id: the file id (including group name and filename)
* file_offset: the start offset to download
* download_bytes: download bytes, 0 means from start offset to the file
end
* file_buff: return file content/buff, must be freed.or local filename
* file_size: return file size (bytes)
* return: 0 success, !=0 fail, return the error code
**/
int storage_do_download_file1_ex(ConnectionInfo *pTrackerServer, \
ConnectionInfo *pStorageServer, \
const int download_type, const char *file_id, \
const int64_t file_offset, const int64_t download_bytes, \
char **file_buff, void *arg, int64_t *file_size);
/**
* download file from storage server
* params:
* pTrackerServer: tracker server连接句柄
* pStorageServer: storage server连接句柄,可以为NULL
* file_id: ⽂件上传时返回的ID
* local_filename: 要另存为的⽂件路径名
* file_size: 输出参数,⽤于返回⽂件⼤⼩(必须获取)
* return: 0 success, !=0 fail, return the error code
**/
int storage_download_file_to_file1(ConnectionInfo *pTrackerServer, \
ConnectionInfo *pStorageServer, \
const char *file_id, \
const char *local_filename, int64_t *file_size);
1.2.8文件删除接口Delete
//return: 0 success, !=0 fail, return the error code
int storage_delete_file1(ConnectionInfo *pTrackerServer, \
ConnectionInfo *pStorageServer, \
const char *file_id);
1.3二次封装
因为FastDfs使用较为简单,所以我们直接进行封装然后看封装后的使用样例就能看明白如何上传/下载/删除一个文件。我们来阐述下封装的思路,首先是成员变量:
- tracker服务器地址
- ⽹络超时时间
- 连接超时时间
- 是否使⽤连接池(默认为是)
- 连接池连接空闲超时时间
其实,这几个个配置中真正关键也就是tracker服务器地址这⼀个字段。
而客户端类主要封装的就如下几个方法:
- 从文件上传文件
- 从缓冲区上传文件
- 下载文件到文件
- 下载文件到缓冲区
- 删除文件
封装实现如下:
//limefds.h
#pragma once
#include
#include
#include
extern "C" {
#include
#include
}
namespace limefds {
//fastdfs客户端相关配置项
struct fdfs_config {
//根据官方文档,设置fastdfs客户端的默认值为官方推荐值
int connect_timeout = 30; //连接超时时间
int network_timeout = 30; //网络超时时间
std::vector tracker_servers; //tracker服务器地址列表
bool use_connection_pool = true; //是否使用连接池-多线程状态最好开启
int connection_pool_max_idle_time = 3600; //连接池最大空闲时间
};
class FdfsClient{
//因为fastdfs相关配置选项都是全局的,所以我们的成员方法均给为静态
public:
static void init(const fdfs_config &config);
//从文件或内存中上传文件,返回文件id
static std::optional uploadFromLFile(const std::string &local_filename);
static std::optional uploadFromBuffer(const std::string &buffer);
//从fastdfs中下载文件,返回文件内容到本地文件中或内存中
static bool downloadToFile(const std::string &file_id,const std::string &local_filename);
static bool downloadToBuffer(const std::string &file_id, std::string &buffer);
//删除fastdfs中的文件
static bool deleteFile(const std::string &file_id);
//销毁全局配置
static void destroy();
};
}//limefds
//limefds.cc
#include "limelog.h"
#include
#include "limefds.h"
namespace limefds {
void FdfsClient::init(const fdfs_config &config)
{
//初始化fds日志模块
g_log_context.log_level = LOG_ERR;
log_init();
//格式化配置文件
std::stringstream ss;
for (auto &server : config.tracker_servers) {
ss << "tracker_server = " < FdfsClient::uploadFromLFile(const std::string &local_filename)
{
auto tracker_server = tracker_get_connection();
if (tracker_server == nullptr) {
ERR("获取tracker服务器连接句柄失败");
return std::optional();
}
char file_id[256];
int ret = storage_upload_by_filename1(tracker_server, nullptr, 0, local_filename.c_str(), nullptr, nullptr, 0, nullptr, file_id);
if (ret!= 0) {
ERR("文件上传失败: {}", STRERROR(ret));
return std::optional();
}
tracker_close_connection(tracker_server);
return std::string(file_id);
}
std::optional FdfsClient::uploadFromBuffer(const std::string &buffer)
{
auto tracker_server = tracker_get_connection();
if (tracker_server == nullptr) {
ERR("获取tracker服务器连接句柄失败");
return std::optional();
}
char file_id[256];
int ret = storage_upload_by_filebuff1(tracker_server, nullptr, 0, buffer.c_str(), buffer.size(), nullptr, nullptr, 0, nullptr, file_id);
if (ret!= 0) {
ERR("文件上传失败: {}", STRERROR(ret));
return std::optional();
}
tracker_close_connection(tracker_server);
return std::string(file_id);
}
//从fastdfs中下载文件,返回文件内容到本地文件中或内存中
bool FdfsClient::downloadToFile(const std::string &file_id,const std::string &local_filename)
{
auto tracker_server = tracker_get_connection();
if (tracker_server == nullptr) {
ERR("获取tracker服务器连接句柄失败");
return false;
}
int64_t file_size;//必须获取,不能传nullptr否则会报错
int ret = storage_download_file_to_file1(tracker_server,nullptr,file_id.c_str(),local_filename.c_str(),&file_size);
if (ret != 0) {
ERR("文件下载失败: {}", STRERROR(ret));
return false;
}
tracker_close_connection(tracker_server);
return true;
}
bool FdfsClient::downloadToBuffer(const std::string &file_id, std::string &buffer)
{
auto tracker_server = tracker_get_connection();
if (tracker_server == nullptr) {
ERR("获取tracker服务器连接句柄失败");
return false;
}
int64_t file_size;
char* download_buffer = nullptr;
int ret = storage_download_file_to_buff1(tracker_server,nullptr,file_id.c_str(),&download_buffer,&file_size);
if(ret != 0)
{
ERR("文件下载失败: {}", STRERROR(ret));
return false;
}
buffer.assign(download_buffer, file_size);
free(download_buffer);//记得释放缓冲区字符串
tracker_close_connection(tracker_server);
return true;
}
//删除fastdfs中的文件
bool FdfsClient::deleteFile(const std::string &file_id)
{
auto tracker_server = tracker_get_connection();
if (tracker_server == nullptr) {
ERR("获取tracker服务器连接句柄失败");
return false;
}
int ret = storage_delete_file1(tracker_server, nullptr, file_id.c_str());
if (ret != 0) {
ERR("文件删除失败: {}", STRERROR(ret));
return false;
}
tracker_close_connection(tracker_server);
return true;
}
//销毁全局配置
void FdfsClient::destroy()
{
fdfs_client_destroy();
}
} // namespace limefds
使用样例
从文件上传/下载
#include "../../source/limelog.h"
#include "../../source/limefds.h"//一定要最后被包含,内部有些操作与其他C++头文件中高级特性冲突了
int main() {
limelog::limelog_init();
limefds::fdfs_config config;
config.tracker_servers = {"192.168.30.128:22122"};
limefds::FdfsClient::init(config);
std::string file_id = limefds::FdfsClient::uploadFromLFile("./makefile").value();
if(file_id.empty()) {
ERR("上传文件失败");
return -1;
}
INF("上传文件成功,文件ID: {}", file_id);
std::string download_filename = "./makefile.bak";
bool ret = limefds::FdfsClient::downloadToFile(file_id, download_filename);
if(!ret) {
ERR("下载文件失败");
return -1;
}
limefds::FdfsClient::destroy();
return 0;
}
从缓冲区上传/下载
#include "../../source/limelog.h"
#include "../../source/limefds.h"//一定要最后被包含,内部有些操作与其他C++头文件中高级特性冲突了
int main() {
limelog::limelog_init();
limefds::fdfs_config config;
config.tracker_servers = {"192.168.30.128:22122"};
limefds::FdfsClient::init(config);
std::string buffer = "hello world";
std::string file_id = limefds::FdfsClient::uploadFromBuffer(buffer).value();
if(file_id.empty()) {
ERR("上传文件失败");
return -1;
}
INF("上传文件成功,文件ID: {}", file_id);
//两种方式下载文件
//1.下载到内存
buffer.clear();
bool ret = limefds::FdfsClient::downloadToBuffer(file_id, buffer);
if(!ret) {
ERR("下载内容到内存失败");
return -1;
}
INF("下载内容到内存成功,内容: {}", buffer);
//2.下载到文件
std::string download_filename = "./test.txt";
ret = limefds::FdfsClient::downloadToFile(file_id, download_filename);
if(!ret) {
ERR("下载内容到文件失败");
return -1;
}
limefds::FdfsClient::destroy();
return 0;
}
删除文件
#include "../../source/limelog.h"
#include "../../source/limefds.h"//一定要最后被包含,内部有些操作与其他C++头文件中高级特性冲突了
int main(int argv,char* argc[])
{
if(argv!= 2)
{
std::cout << "Usage: " << argc[0] << " " << std::endl;
return -1;
}
limelog::limelog_init();
limefds::fdfs_config config;
config.tracker_servers = {"192.168.30.128:22122"};
limefds::FdfsClient::init(config);
bool ret = limefds::FdfsClient::deleteFile(argc[1]);
if(!ret) {
ERR("删除文件失败");
return -1;
}
INF("删除文件成功,文件ID: {}", argc[1]);
limefds::FdfsClient::destroy();
return 0;
}
二.redis++
Redis(Remote Dictionary Server)是⼀个开源的⾼性能键值对(key-value)数据库。它通常⽤作数据结构服务器,因为除了基本的键值存储功能外,Redis 还⽀持多种类型的数据结构,如字符串(strings)、哈希(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)以及范围查询、位图、超⽇志和地理空间索引等。 主要特性啥的咱这里就不多介绍了,因为后面还会深入进行学习,后面的cmake,odb也是一样。我们主要了解它的c/c++客户端的使用方法并进行二次封装:
C++ 操作 redis 的库有很多. 咱们此处使⽤ redis-plus-plus. 这个库的功能强⼤, 使⽤简单. Github 地址: https://github.com/sewenew/redis-plus-plus
2.1安装 Redis++
redis-plus-plus 是基于 hiredis 实现的.
hiredis 是⼀个 C 语⾔实现的 redis 客⼾端.
因此需要先安装 hiredis. 直接使⽤包管理器安装即可
apt install libhiredis-dev
下载redis++ 源码git clone https://github.com/sewenew/redis-plus-plus.git
编译/安装 redis++
cd redis-plus-plus
mkdir build && cd build
cmake ..
make && sudo make install # 这⼀步操作需要管理员权限. 如果是⾮ root ⽤⼾, 使⽤sudo make install 执⾏.
2.2接口
2.2.1头文件与链接库
#include
-lhiredis -lredis++ -lpthread
2.2.2异常
namespace sw {
namespace redis {
enum ReplyErrorType {
ERR,
MOVED,
ASK
};
class Error : public std::exception {
public:
explicit Error(const std::string &msg) : _msg(msg) {}
Error(const Error &) = default;
Error& operator=(const Error &) = default;
Error(Error &&) = default;
Error& operator=(Error &&) = default;
virtual ~Error() override = default;
virtual const char* what() const noexcept override {
return _msg.data();
}
private:
std::string _msg;
};
// .... 还有很多其他的异常类,不过这⾥没有解除到因此不做过多介绍
class WatchError : public Error {
public:
explicit WatchError() : Error("Watched key has been modified") {}
WatchError(const WatchError &) = default;
WatchError& operator=(const WatchError &) = default;
WatchError(WatchError &&) = default;
WatchError& operator=(WatchError &&) = default;
virtual ~WatchError() override = default;
};
} }
2.2.3初始化操作
namespace sw {
namespace redis {
struct ConnectionOptions {
std::string host;
int port = 6379;
std::string path;
std::string user = "default";
std::string password;
int db = 0; // 默认0号库
bool keep_alive = false;
}
struct ConnectionPoolOptions {
//最⼤连接数量,包含使⽤中和空闲连接
std::size_t size = 1;
// Max time to wait for a connection. 0ms means client waits forever.
std::chrono::milliseconds wait_timeout{0};
// Max lifetime of a connection. 0ms means we never expire the connection.
std::chrono::milliseconds connection_lifetime{0};
// Max idle time of a connection. 0ms means we never expire the
connection.
std::chrono::milliseconds connection_idle_time{0};
}
class Redis {
// tcp://[[username:]password@]host[:port][/db]
explicit Redis(const std::string &uri)
explicit Redis(const ConnectionOptions &connection_opts,
const ConnectionPoolOptions &pool_opts = {})
};
} }
2.2.4常规操作
class Redis {
// Transaction commands.
void watch(const StringView &key);
template
void watch(Input first, Input last);
//关于transaction和pipeline,默认会创建新的connection,作者推荐尽量重⽤返回的对象
//但是需要注意的是,这两个对象的操作都是⾮线程安全的。
//创建⼀个事务对象⽤于事务中的批量操作的效率以及原⼦性
// 若new_connection为false,则会从连接池中获取连接创建事务对象,
// 连接在返回的事务/pipe对象析构时返回连接池
Transaction transaction(bool piped = false, bool new_connection = true);
//创建⼀个pipeline,⽤于提⾼批量操作性能
Pipeline pipeline(bool new_connection = true);
//删除所有库中的数据
void flushall(bool async = false);
//删除当前库中所有数据
void flushdb(bool async = false);
//删除指定键值对
long long del(const StringView &key);
template
long long del(Input first, Input last);
//判断指定键值对是否存在
long long exists(const StringView &key);
template
long long exists(Input first, Input last);
//为key设置⼀个过期时间
bool expire(const StringView &key, const std::chrono::seconds &timeout);
//移除key的超时过期
bool persist(const StringView &key);
//对指定数字字段进⾏数值增加
long long incrby(const StringView &key, long long increment);
//对指定数字字段进⾏数值减少
long long decrby(const StringView &key, long long decrement);
};
2.2.5String操作
class Redis {
//获取⼀个string键值对
OptionalString get(const StringView &key);
//存放⼀个string键值对,且设置过期时间-毫秒,0表⽰不设置超时
// 标志位:EXIST/NOT_EXIST/ALWAYS/,表⽰什么情况新增
bool set(const StringView &key,
const StringView &val,
const std::chrono::milliseconds &ttl = std::chrono::milliseconds(0),
UpdateType type = UpdateType::ALWAYS);
//返回旧值,并设置新值
OptionalString getset(const StringView &key, const StringView &val);
//批量获取数据-
//redis.mget(keys.begin(), keys.end(), std::back_inserter(vals));
template
void mget(Input first, Input last, Output output);
//批量新增数据
template
void mset(Input first, Input last);
};
使用样例
#include
#include
#include
//进行简单的string类型操作
int main()
{
//异常捕获
try{
//初始化redis客户端
sw::redis::ConnectionOptions options = {
.host = "192.168.30.128",
.port = 6379,
.password = "123456"
};
sw::redis::ConnectionPoolOptions pool_options = {
.size = 10,
.connection_idle_time = std::chrono::seconds(3600)
};
sw::redis::Redis redis(options, pool_options);
//进行string类型操作
redis.set("xiaoming","boy");//设置一个string类型键值对
std::string value = redis.get("xiaoming").value();//获取一个string类型键值对,返回的其实是一个std::optional类型
std::cout << "小明的性别是:" << value << std::endl;
std::string old_value = *redis.getset("xiaoming","girl");//将小明的性别重新设置为女性
std::cout << "小明的性别从" << old_value << "变成了" << redis.get("xiaoming").value() << std::endl;
//批量新增数据
std::vector> kvs = {{"xiaozhang","girl"},{"xiaoqian","boy"}};
redis.mset(kvs.begin(),kvs.end());
//批量获取数据
std::vector keys = {"xiaoming","xiaozhang","xiaoqian"};
std::vector values;
redis.mget(keys.begin(),keys.end(),std::back_inserter(values));
for(auto& value : values)
{
if(value)
{
std::cout << value.value() << std::endl;
}
else
{
std::cout << "key not exist" << std::endl;
}
}
//批量删除数据
redis.del(keys.begin(),keys.end());
}
catch(const sw::redis::Error& e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
2.2.6list操作
class Redis {
long long rpush(const StringView &key, const StringView &val);
long long lpush(const StringView &key, const StringView &val);
long long llen(const StringView &key); // 获取元素数量
template
long long rpush(const StringView &key, Input first, Input last);
template
long long lpush(const StringView &key, Input first, Input last);
//索引从0开始,-1表⽰末尾元素
OptionalString lindex(const StringView &key, long long index);
OptionalString lpop(const StringView &key);
OptionalString rpop(const StringView &key);
//0,-1表⽰全部
template
void lrange(const StringView &key, long long start, long long stop,
Output output);
};
使用样例
#include
#include
#include
void print_list(sw::redis::Redis& redis, const std::string& key)
{
//进行list键值的打印
std::vector elements;
redis.lrange(key, 0, -1, std::back_inserter(elements));
for(const auto& element : elements)
{
std::cout << element << " ";
}
std::cout << std::endl;
}
int main()
{
//异常捕获
try{
//初始化redis客户端
sw::redis::ConnectionOptions options = {
.host = "192.168.30.128",
.port = 6379,
.password = "123456"
};
sw::redis::ConnectionPoolOptions pool_options = {
.size = 10,
.connection_idle_time = std::chrono::seconds(3600)
};
sw::redis::Redis redis(options, pool_options);
//进行简单的list操作-班级学生列表
redis.rpush("class_students", "xiaoming");//添加学生到班级列表从右侧插入
redis.rpush("class_students", "xiaohong");//添加学生到班级列表从右侧插入
redis.lpush("class_students", "xiaoli");//添加学生到班级列表从左侧插入
std::cout << "班级学生列表长度:" << redis.llen("class_students") << std::endl;//获取班级学生列表长度
print_list(redis, "class_students");//打印班级学生列表
//批量从右端插入
std::vector students = {"xiaozhang", "xiaodong"};
redis.rpush("class_students", students.begin(), students.end());
print_list(redis, "class_students");//打印班级学生列表
//批量从左端插入
std::vector students2 = {"xiaozhao", "xiaowei"};
redis.lpush("class_students", students2.begin(), students2.end());
print_list(redis, "class_students");//打印班级学生列表
//查找对应位置的元素查找第0个位置应为xiaowei
std::string element = *redis.lindex("class_students", 0);
std::cout << "第0个位置的元素:" << element << std::endl;
//左弹出一个元素-删除只有左弹出右弹出这两个方法
std::string left_pop_element = *redis.lpop("class_students");
std::cout << "左弹出元素:" << left_pop_element << std::endl;
print_list(redis, "class_students");//打印班级学生列表
//右弹出一个元素
std::string right_pop_element = *redis.rpop("class_students");
std::cout << "右弹出元素:" << right_pop_element << std::endl;
print_list(redis, "class_students");//打印班级学生列表
//删除指定键值
redis.del("class_students");
std::cout << "班级学生列表已删除" << std::endl;
}
catch(const sw::redis::Error& e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
2.2.7hash操作
class Redis {
bool hexists(const StringView &key, const StringView &field);
//从key哈希中删除filed字段
long long hdel(const StringView &key, const StringView &field);
//批量字段删除
template
long long hdel(const StringView &key, Input first, Input last);
//获取key的所有字段到std::unordered_map
template
void hgetall(const StringView &key, Output output);
//
OptionalString hget(const StringView &key, const StringView &field);
template
void hmget(const StringView &key, Input first, Input last, Output output);
//std::unordered_map m
//redis.hmset("hash", m.begin(), m.end());
template
void hmset(const StringView &key, Input first, Input last);
long long hset(const StringView &key, const StringView &field, const
StringView &val);
//对指定字段进⾏数值增加操作,⽀持负数
long long hincrby(const StringView &key, const StringView &field, long
long increment);
}
使用样例
#include
#include
#include
void hash_print(sw::redis::Redis& redis, const std::string& key)
{
//进行键值对应的hash表的打印
std::unordered_map hash_map;
redis.hgetall(key,std::inserter(hash_map,hash_map.begin()));
for(auto it = hash_map.begin(); it!= hash_map.end(); ++it)
{
std::cout << it->first << ":" << it->second << " ";
}
std::cout << std::endl;
}
int main()
{
//异常捕获
try{
//初始化redis客户端
sw::redis::ConnectionOptions options = {
.host = "192.168.30.128",
.port = 6379,
.password = "123456"
};
sw::redis::ConnectionPoolOptions pool_options = {
.size = 10,
.connection_idle_time = std::chrono::seconds(3600)
};
sw::redis::Redis redis(options, pool_options);
//进行简单的hash操作-成绩表为例
//单个设置
redis.hset("math", "zhangsan", "90");
redis.hset("math", "lisi", "80");
redis.hset("math", "wangwu", "70");
hash_print(redis, "math");
//批量设置
std::unordered_map hash_map = {
{"zhaoliu", "99"},
{"tianqi", "88"},
{"xiaoming", "77"}
};
redis.hmset("math", hash_map.begin(), hash_map.end());
hash_print(redis, "math");
//获取单个值
std::cout << "zhangsan的数学成绩为:" << *redis.hget("math", "zhangsan") << std::endl;
//批量获取值
std::vector keys = {"zhaoliu", "tianqi", "xiaoming"};
std::vector out_put;
redis.hmget("math",keys.begin(),keys.end(), std::back_inserter(out_put));
for(auto it = out_put.begin(); it!= out_put.end(); ++it)
{
std::cout << "批量获取的值为:";
std::cout << *it << " ";
}
std::cout << std::endl;
//删除键值
redis.hdel("math", "zhaoliu");
hash_print(redis, "math");
//判断键是否存在
if(redis.hexists("math", "wangwu"))
{
std::cout << "wangwu在hash表中存在" << std::endl;
}
else
{
std::cout << "wangwu不在hash表中" << std::endl;
}
//批量删除键值-还是使用hdel,与批量添加一样不再展示
//对指定字段进行增加或减少操作
redis.hincrby("math", "wangwu", 10);
std::cout << "wangwu的数学成绩增加10分:" << *redis.hget("math", "wangwu") << std::endl;
redis.hincrby("math", "wangwu", -5);
std::cout << "wangwu的数学成绩减少5分:" << *redis.hget("math", "wangwu") << std::endl;
//获取hash表的长度
std::cout << "math的hash表长度为:" << redis.hlen("math") << std::endl;
hash_print(redis, "math");
//删除对应键值的hash表
redis.del("math");
std::cout << "math键已删除" << std::endl;
}
catch(const sw::redis::Error& e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
2.2.8set操作
long long sadd(const StringView &key, const StringView &member);
template long long sadd(const StringView &key, Input first,
Input last);
long long scard(const StringView &key); //获取⽆序集合元素数量
bool sismember(const StringView &key, const StringView &member);
//std::unordered_set members1;
/// redis.smembers("set", std::inserter(members1, members1.begin()));
//std::vector members2
/// redis.smembers("set", std::back_inserter(members2));
template void smembers(const StringView &key, Output output);
使用样例
#include
#include
#include
#include
// 1. 向集合中添加一个元素
// 2. 批量向集合中添加多个元素
// 3. 获取集合中元素的个数
// 4. 判断集合中是否包含某个元素
// 5. 获取集合中所有元素
int main()
{
//异常捕获
try{
//初始化redis客户端
sw::redis::ConnectionOptions options = {
.host = "192.168.30.128",
.port = 6379,
.password = "123456"
};
sw::redis::ConnectionPoolOptions pool_options = {
.size = 10,
.connection_idle_time = std::chrono::seconds(3600)
};
sw::redis::Redis redis(options, pool_options);
//进行set相关的操作演示
// 1. 向集合中添加一个元素
redis.sadd("skills", "C++");
// 2. 批量向集合中添加多个元素
std::unordered_set skills = {"Python", "Java", "Go"};
redis.sadd("skills", skills.begin(), skills.end());
// 3. 获取集合中元素的个数
auto count = redis.scard("skills");
std::cout << "skills size: " << count << std::endl;
// 4. 判断集合中是否包含某个元素
bool ret = redis.sismember("skills", "C++");
std::cout << "skills contains C++: " << ret << std::endl;
ret = redis.sismember("skills", "C");
std::cout << "skills contains C: " << ret << std::endl;
// 5. 获取集合中所有元素
std::unordered_set skills2;
redis.smembers("skills", std::inserter(skills2, skills2.begin()));
for (auto &val : skills2) {
std::cout << val << " ";
}
std::cout << std::endl;
redis.del("skills");
}
catch(const sw::redis::Error& e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
2.2.9zset操作
long long zadd(const StringView &key,
const StringView &member,
double score,
UpdateType type = UpdateType::ALWAYS,
bool changed = false);
// return:已添加成员数量
// 当前结构不⽀持增加分数,若想要增加可以使⽤通⽤接⼝进⾏
//样例:auto score = redis.command("ZADD", "key", "XX", "INCR",
10, "mem");
template
long long zadd(const StringView &key,
Input first,
Input last,
UpdateType type = UpdateType::ALWAYS,
bool changed = false);
// std::unordered_map m = {{"m1", 1.2}, {"m2", 2.3}};
// redis.zadd("zset", m.begin(), m.end());
long long zcard(const StringView &key);
// 获取有序集合成员数量
double zincrby(const StringView &key, double increment, const StringView
&member);
// 增加/减少给定成员的分数
template
void zrange(const StringView &key, long long start, long long stop, Output
output);
// 获取以得分排名的区间成员,从低到⾼排序
template
void zrangebyscore(const StringView &key, const Interval &interval, Output
output);
template
void zrangebyscore(const StringView &key, const Interval &interval,
const LimitOptions &opts, Output output);
// 获取得分的区间成员,,从低到⾼排序
OptionalLongLong zrank(const StringView &key, const StringView &member);
// 获取成员排名 -- 从低到⾼, 从0开始
template
void zrangebylex(const StringView &key, const Interval &interval, Output
output);
template
void zrangebylex(const StringView &key, const Interval &interval,
const LimitOptions &opts, Output output);
//获取字典序区间元素
template
void zrevrange(const StringView &key, long long start, long long stop, Output
output);
// 获取排名区间内的元素--- 从⾼到低排序
template
void zrevrangebyscore(const StringView &key, const Interval &interval, Output
output);
template
void zrevrangebyscore(const StringView &key, const Interval &interval,
const LimitOptions &opts, Output output);
// 获取分数区间内的元素--- 从⾼到底排序
OptionalLongLong zrevrank(const StringView &key, const StringView &member);
// 获取指定元素排名 -- 从⾼到低
template
void zrevrangebylex(const StringView &key, const Interval &interval, Output
output);
template
void zrevrangebylex(const StringView &key, const Interval &interval,
const LimitOptions &opts, Output output);
// 获取字典序区间元素
OptionalDouble zscore(const StringView &key, const StringView &member);
// 获取指定元素的权重得分
template
Cursor zscan(const StringView &key,
Cursor cursor,
const StringView &pattern,
long long count,
Output output);
// 按照指定匹配模式迭代浏览有序集合元素
long long zrem(const StringView &key, const StringView &member);
// 移除给定成员
Optional> zpopmax(const StringView &key);
// 弹出集合中得分最⾼的成员
Optional> zpopmin(const StringView &key);
// 弹出集合中得分最低的成员
template
long long zremrangebyscore(const StringView &key, const Interval &interval);
// 删除分数区间元素
long long zremrangebyrank(const StringView &key, long long start, long long
stop);
// 删除排名区间元素
template
long long zremrangebylex(const StringView &key, const Interval &interval);
// 删除集合中字典序区间元素
使用样例
#include
#include
#include
#include
//BoundType中的几种选项类似于我们高中学过的集合,它可以和BoundedInterval或LeftBoundedInterval或RightBoundedInterval组合使用,来指定区间的左右边界。
//使用BoundType时
//LEFT_OPEN等价于(2.3,5]
//RIGHT_OPEN等价于[2.3,5)
//OPEN等价于(2.3,5)
//CLOSED等价于[2.3,5]
//但如果使用LeftBoundedInterval时只能设置OPEN或RIGHT_OPEN,如果使用RightBoundedInterval时只能设置OPEN或LEFT_OPEN。
//分别对应这四种区间类型: [2.3, +inf) , (2.3, +inf) , (-inf, 5] , (-inf, 5)
//还有一种特殊的BoundedInterval为UnboundedInterval使用例子如下:
//redis.zcount("zset", UnboundedInterval{});其所指定的区间为(-inf, +inf)
// 6. 迭代浏览集合中的数据
void print(sw::redis::Redis &redis, const std::string &key) {
sw::redis::Cursor cursor = 0; //定义光标,默认从第0个元素开始进行匹配迭代
std::vector> members; //定义输出集合
while (true) {
cursor = redis.zscan(key, cursor, "*", 10, std::back_inserter(members));
if (cursor == 0) { break; }
}
for (auto &member : members) {
std::cout << member.first << " : " << member.second << std::endl;
}
}
int main()
{
try {
//初始化redis客户端
sw::redis::ConnectionOptions options = {
.host = "192.168.30.128",
.port = 6379,
.password = "123456"
};
sw::redis::ConnectionPoolOptions pool_opts = {
.size = 10,
.connection_idle_time = std::chrono::milliseconds(3600)
};
sw::redis::Redis redis(options, pool_opts);
// 对有序集合zset的操作
// 1. 向有序集合添加数据 - 单个添加/批量添加
redis.zadd("score", "zhangsan", 34);
std::unordered_map m = {
{"lisi", 95},
{"wangwu", 56},
{"zhaoliu", 78},
{"tianqi", 89},
{"xiaohong", 99},
{"xiaoming", 88},
{"xiaolan", 77},
{"xiaowang", 66}
};
redis.zadd("score", m.begin(), m.end());
print(redis, "score");
std::cout << "---------------------新增数据完毕---------------\n";
// 2. 获取集合中元素数量
std::cout << "元素数量: " << redis.zcard("score") << std::endl;
// 3. 获取指定元素权重得分(以是否能够得到得分判断元素是否存在)
std::cout << "小红得分:" << *redis.zscore("score", "xiaohong") << std::endl;
// 4. 获取指定元素权重排名(以是否能够得到排名判断元素是否存在)
std::cout << "小红从低到高排名:" << *redis.zrank("score", "xiaohong") << std::endl;
std::cout << "小红从高到低排名:" << *redis.zrevrank("score", "xiaohong") << std::endl;
// 5. 获取集合数据 - 以得分排名获取区间元素/以得分获取区间元素(从低到高/从高到低)
std::vector result;
redis.zrange("score", 0, 2, std::back_inserter(result));
std::cout << "从低到高排名前3名: ";
for (auto &member : result) {
std::cout << member << " ";
}
std::cout << std::endl;
result.clear();
redis.zrevrange("score", 0, 2, std::back_inserter(result));
std::cout << "从高到低排名前3名: ";
for (auto &member : result) {
std::cout << member << " ";
}
std::cout << std::endl;
result.clear();
//得分获取区间元素
auto interval = sw::redis::BoundedInterval(0, 60, sw::redis::BoundType::RIGHT_OPEN);
redis.zrangebyscore("score", interval, std::back_inserter(result));
std::cout << "60分以下的学生(从低到高): ";
for (auto &member : result) {
std::cout << member << " ";
}
std::cout << std::endl;
result.clear();
redis.zrevrangebyscore("score", interval, std::back_inserter(result));
std::cout << "60分以下的学生(从高到低): ";
for (auto &member : result) {
std::cout << member << " ";
}
std::cout << std::endl;
// 7. 删除指定元素
std::cout << "---------------------删除小红------------------" << std::endl;
redis.zrem("score", "xiaohong");
print(redis, "score");
std::cout << "---------------------删除数据完毕---------------\n";
// 8. 弹出集合中最高/最低分元素
auto max = redis.zpopmax("score");
if (max) { std::cout << "弹出最高分: " << max->first << " : " << max->second << std::endl; }
auto min = redis.zpopmin("score");
if (min) { std::cout << "弹出最低分: " << min->first << " : " << min->second << std::endl; }
print(redis, "score");
// 9. 以权重得分删除指定区间元素
std::cout << "---------------------删除70~80的学生------------------" << std::endl;
auto interval1 = sw::redis::BoundedInterval(70, 80, sw::redis::BoundType::OPEN);
redis.zremrangebyscore("score", interval1);
print(redis, "score");
std::cout << "---------------------删除数据完毕---------------\n";
// 10. 以权重排名删除指定区间元素
redis.zremrangebyrank("score", 0, 2);
print(redis, "score");
//11. 修改元素权重
redis.zincrby("score", 10, "tianqi");
print(redis, "score");
redis.zincrby("score", -20, "tianqi");
print(redis, "score");
//12.删除集合
std::cout << "---------------------删除集合-------------------" << std::endl;
redis.del("score");
} catch (const sw::redis::Error &e) {
std::cout << "redis error: " << e.what() << std::endl;
}
return 0;
}
2.2.10transcation&&pipline操作
namespace redis {
class Redis {
//关于transaction和pipeline,默认会创建新的connection,作者建议尽量重⽤返回的对象
// 但是需要注意的是,这两个对象的操作都是⾮线程安全的。
// 若new_connection为false,则会从连接池中获取连接创建事务对象,这样成本会低很多
// 连接在返回的transaction和pipeline对象析构时返回连接池
Transaction transaction(bool piped = false, bool new_connection = true);
//创建⼀个pipeline,⽤于提⾼批量操作性能
Pipeline pipeline(bool new_connection = true);
}
using Transaction = QueuedRedis;
using Pipeline = QueuedRedis;
template
class QueuedRedis {
// 创建⼀个redis对象,与该对象共享连接
// 作者建议:尽量限定返回的redis作⽤范围,并尽快销毁它(否则很容易死锁)
Redis redis();
QueuedReplies exec(); //⽤于执⾏并获取返回结果
void discard();//过程中取消操作
QueuedRedis& flushall(bool async = false)
QueuedRedis& del(const StringView &key)
QueuedRedis& exists(const StringView &key)
QueuedRedis& expire(const StringView &key, const std::chrono::seconds
&timeout)
QueuedRedis& get(const StringView &key)
QueuedRedis& getrange(const StringView &key, long long start, long long
end)
QueuedRedis& getset(const StringView &key, const StringView &val)
QueuedRedis& mget(Input first, Input last)
//......
QueuedRedis& hexists(const StringView &key, const StringView &field)
QueuedRedis& hget(const StringView &key, const StringView &field)
QueuedRedis& hgetall(const StringView &key)
//。。。。。
QueuedRedis& lrange(const StringView &key, long long start, long long stop)
};
// QueuedRedis 中的所有常规数据操作都⽆法直接获取结果,⽽是在执⾏exec(),获取保存了结果的对象
QueuedRedis.exists().get().mget().sget()....exec()
class QueuedReplies {
std::size_t size();
template
auto get(std::size_t idx)
-> typename std::enable_if::value,Result>::type
template
void get(std::size_t idx, Output output);
redisReply& get(std::size_t idx);
};
}
使用样例
//pipline
#include
#include
#include
#include
int main()
{
//异常捕获
try{
//初始化redis客户端
sw::redis::ConnectionOptions options = {
.host = "192.168.30.128",
.port = 6379,
.password = "123456"
};
sw::redis::ConnectionPoolOptions pool_options = {
.size = 10,
.connection_idle_time = std::chrono::seconds(3600)
};
sw::redis::Redis redis(options, pool_options);
//pipeline操作示例
//默认使用链接池中的链接去构造pipeline对象,需要注意其作用域与声明周期
{
auto pipe = redis.pipeline(false);//从链接池中获取链接构造pipeline对象
//pipeline操作
pipe.set("key1", "value1");
pipe.set("key2", "value2");
pipe.get("key1");
pipe.get("key2");
//执行pipeline操作
auto replies = pipe.exec();
//上面的操作也可以像下面这样写去执行
//pipe.set("key1", "value1").set("key2","value2").get("key1").get("key2").exec();
//打印结果数量
std::cout << "replies size: " << replies.size() << std::endl;//应该为4
//打印结果-因为前两个结果为bool类型,后两个结果为sw::redis::OptionalString类型,所以没办法for循环去打印结果
std::cout << "replies[0]: " << replies.get(0) << std::endl;
std::cout << "replies[1]: " << replies.get(1) << std::endl;
std::cout << "replies[2]: " << replies.get(2).value() << std::endl;
std::cout << "replies[3]: " << *replies.get(3) << std::endl;
//清除所有键值对
pipe.del("key1").del("key2").exec();
}
{
auto pipe = redis.pipeline(false);
//pipeline操作
pipe.hset("key1","field1","value1");
pipe.hset("key1","field2","value2");
pipe.hgetall("key1");
auto replies = pipe.exec();
//打印结果数量
std::cout << "replies size: " << replies.size() << std::endl;//应该为3
//打印结果
std::cout << "replies[0]: " << replies.get(0) << std::endl;
std::cout << "replies[1]: " << replies.get(1) << std::endl;
std::unordered_map map;
replies.get(2,std::inserter(map,map.begin()));
std::cout << "replies[2]: ";
for(auto it = map.begin();it!= map.end();++it)
{
std::cout << it->first << ":" << it->second << " ";
}
std::cout << std::endl;
//清除所有键值对
pipe.del("key1").exec();
}
}
catch(const sw::redis::Error& e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
//transaction
#include
#include
#include
#include
int main()
{
//异常捕获
try{
//初始化redis客户端
sw::redis::ConnectionOptions options = {
.host = "192.168.30.128",
.port = 6379,
.password = "123456"
};
sw::redis::ConnectionPoolOptions pool_options = {
.size = 10,
.connection_idle_time = std::chrono::seconds(3600)
};
sw::redis::Redis redis(options, pool_options);
//transaction操作示例
//默认使用链接池中的链接去构造transaction对象,需要注意其作用域与声明周期
{
auto pipe = redis.transaction(false,false);//从链接池中获取链接构造transaction对象
//transaction操作
pipe.set("key1", "value1");
pipe.set("key2", "value2");
pipe.get("key1");
pipe.get("key2");
//执行transaction操作
auto replies = pipe.exec();
//上面的操作也可以像下面这样写去执行
//pipe.set("key1", "value1").set("key2","value2").get("key1").get("key2").exec();
//打印结果数量
std::cout << "replies size: " << replies.size() << std::endl;//应该为4
//打印结果-因为前两个结果为bool类型,后两个结果为sw::redis::OptionalString类型,所以没办法for循环去打印结果
std::cout << "replies[0]: " << replies.get(0) << std::endl;
std::cout << "replies[1]: " << replies.get(1) << std::endl;
std::cout << "replies[2]: " << replies.get(2).value() << std::endl;
std::cout << "replies[3]: " << *replies.get(3) << std::endl;
//清除所有键值对
pipe.del("key1").del("key2").exec();
}
{
auto pipe = redis.transaction(false,false);
//transaction操作
pipe.hset("key1","field1","value1");
pipe.hset("key1","field2","value2");
pipe.hgetall("key1");
auto replies = pipe.exec();
//打印结果数量
std::cout << "replies size: " << replies.size() << std::endl;//应该为3
//打印结果
std::cout << "replies[0]: " << replies.get(0) << std::endl;
std::cout << "replies[1]: " << replies.get(1) << std::endl;
std::unordered_map map;
replies.get(2,std::inserter(map,map.begin()));
std::cout << "replies[2]: ";
for(auto it = map.begin();it!= map.end();++it)
{
std::cout << it->first << ":" << it->second << " ";
}
std::cout << std::endl;
//清除所有键值对
pipe.del("key1").exec();
}
}
catch(const sw::redis::Error& e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
2.2.11使用样例编译构建
all:string list hash set complement zset pipeline transaction watch
string:string.cc
g++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
list:list.cc
g++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
hash:hash.cc
g++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
set:set.cc
g++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
complement:complement.cc
g++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
zset:zset.cc
g++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
pipeline:pipeline.cc
g++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
transaction:transaction.cc
g++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
watch:watch.cc
g++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
clean:
rm -f string list hash set complement zset pipeline transaction watch
2.3二次封装
因为人家原本的客户端操作已经很完善了,所以我们这里只是对redis客户端的创建做一个简单封装即可:
//limeredis.h
#pragma once
#include
#include
#include
#include
#include
namespace limeredis {
struct redis_settings{
int db = 0;//默认使用0号数据库
int port = 6379;//默认使用6379端口
std::string host;
std::string password;
std::string user = "default";//默认使用default用户
size_t connection_pool_size = 3;//连接池链接数,默认3个连接
};
class RedisFactory{
public:
static std::shared_ptr create(const redis_settings& settings);
};
}// namespace limeredis
//limeredis.cc
#include "limeredis.h"
namespace limeredis {
std::shared_ptr RedisFactory::create(const redis_settings& settings)
{
sw::redis::ConnectionOptions options = {
.host = settings.host,
.port = settings.port,
.user = settings.user,
.password = settings.password,
.db = settings.db
};
sw::redis::ConnectionPoolOptions pool_options = {
.size = settings.connection_pool_size,
.connection_idle_time = std::chrono::seconds(3600)
};
return std::make_shared(options, pool_options);
}
}// namespace limeredis
因为就是对客户端创建的一个简单封装,所以如果想要对封装进行测试直接套用上面给的使用样例随便选一个进行测试即可,这里不再给出。
三.odb-Mysql
ODB(Object-Relational Bridge)是⼀个开源的C++对象关系映射(ORM)框架,旨在简化数据库操作,允许开发者以⾯向对象的⽅式直接操作数据库,⽽⽆需⼿动编写SQL语句。它通过⾃动映射C++类与数据库表,实现对象持久化,⽀持多种数据库后端(如MySQL、PostgreSQL、SQLite等)
核⼼⽬标:
类型安全 :所有数据库操作均通过C++对象完成,避免SQL注⼊和类型转换错误。
开发效率 :⾃动⽣成SQL代码,减少重复劳动,开发者可专注于业务逻辑
因为我们的脚手架环境中已经安装过odb了,详细的安装过程不再演示,如需单独安装有兴趣的读者可以通过网络搜索相关资料进行学习。
3.1odb常见操作
3.1.1odb类型映射
| C++ Type | MySQL Type | Default NULL Semantics |
|---|---|---|
| bool | TINYINT(1) | NOT NULL |
| char | CHAR(1) | NOT NULL |
| signed char | TINYINT | NOT NULL |
| unsigned char | TINYINT UNSIGNED | NOT NULL |
| short | SMALLINT | NOT NULL |
| unsigned short | SMALLINT UNSIGNED | NOT NULL |
| int | INT | NOT NULL |
| unsigned int | INT UNSIGNED | NOT NULL |
| long | BIGINT | NOT NULL |
| unsigned long | BIGINT UNSIGNED | NOT NULL |
| long long | BIGINT | NOT NULL |
| unsigned long long | BIGINT UNSIGNED | NOT NULL |
| float | FLOAT | NOT NULL |
| double | DOUBLE | NOT NULL |
| std::string | TEXT/VARCHAR(255) | NOT NULL |
| char[N] | VARCHAR(N-1) | NOT NULL |
| Boost date_time Type | MySQL Type | Default NULL Semantics |
|---|---|---|
| gregorian::date | DATE | NULL |
| posix_time::ptime | DATETIME | NULL |
| posix_time::time_duration | TIME | NULL |
3.1.2odb编程
ODB(Open Database)在数据元结构定义时,使⽤预处理器指令( #pragma )来提供元数据,这些元数据指⽰如何将C++类型映射到数据库模式。这些 #pragma 指令是在C++代码中使⽤的,它们不是C++语⾔的⼀部分,⽽是特定于ODB编译器的扩展。以下是ODB中常⽤的⼀些 #pragma 指令:
| #pragma db object: | ⽤于声明⼀个类是数据库对象,即这个类将映射到数据库中的⼀个表。 |
|---|---|
| #pragma db table(“table_name”): | 指定类映射到数据库中的表名。如果不指定,则默认使⽤类名。 |
| #pragma db id: | 标记类中的⼀个成员变量作为数据库表的主键。 |
| #pragma db column(“column_name”): | 指定类成员映射到数据库表中的列名。如果不指定,则默认使⽤成员变量的名字。 |
| #pragma db view: | ⽤于声明⼀个类是⼀个数据库视图,⽽不是⼀个表。 |
| #pragma db session: | ⽤于声明⼀个全局或成员变量是数据库会话。 |
| #pragma db query(“query”): | ⽤于定义⾃定义的查询函数。 |
| #pragma db index(“index_name”): | 指定成员变量应该被索引。 |
| #pragma db default(“default_value”): | 指定成员变量的默认值。 |
| #pragma db unique: | 指定成员变量或⼀组变量应该具有唯⼀性约束。 |
| #pragma db not_null: | 指定成员变量不允许为空。 |
| #pragma db auto: | 指定成员变量的值在插⼊时⾃动⽣成(例如,⾃动递增的主键)。 |
| #pragma db transient: | 指定成员变量不应该被持久化到数据库中。 |
| #pragma db type(“type_name”): | 指定成员变量的数据库类型。 |
| #pragma db convert(“converter”): | 指定⽤于成员变量的⾃定义类型转换器。 |
| #pragma db pool(“pool_name”): | 指定⽤于数据库连接的连接池。 |
| #pragma db trigger(“trigger_name”): | 指定在插⼊、更新或删除操作时触发的触发器。 |
3.2数据操作相关类与接口
3.2.1代码生成指令
odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time $^
3.2.2odb数据对象头文件
#pragma once
#include
#include // std::size_t
#include
#include
#include
3.2.3odb操作对象头文件
#include
#include
#include
#include "xxx.h"
#include "xxx-odb.h"
3.2.4链接库
-lodb-mysql -lodb -lodb-boost -lpthread
3.2.5database与transaction
需要注意,odb规定,所有对数据库的操作都必须在事务中进行,所以我们的样例演示中也都会去接收事务操作所抛出的异常
namespace odb{
namespace mysql {
//mysql连接池对象类
class LIBODB_MYSQL_EXPORT connection_pool_factory
: public connection_factory {
connection_pool_factory (std::size_t max_connections = 0,
std::size_t min_connections = 0,
bool ping = true)
}
}
//操作句柄类
class LIBODB_EXPORT database {
database (const std::string& user,
const std::string& passwd,
const std::string& db,
const std::string& host = "",
unsigned int port = 0,
const std::string* socket = 0,
const std::string& charset = "",
unsigned long client_flags = 0,
details::transfer_ptr =
details::transfer_ptr ());
//新增数据
persist (T& object);
//更新数据
void update (T& object);
void erase (T& object);
unsigned long long erase_query (const std::string&);
unsigned long long erase_query (const odb::query&);
result query (const std::string&);
result query (const odb::query&, bool cache = true);
typename result::pointer_type query_one(const odb::query&);
//odb中所有的数据操作都必须在事务内部完成
// 该接⼝⽤于创建/获取⼀个事务对象
virtual transaction_impl* begin () = 0;
};
//事务操作类
class LIBODB_EXPORT transaction {
transaction (transaction_impl*, bool make_current = true);
database_type& database(); // 当前线程内部的输操作对象
void commit ();
void rollback ();
void tracer (tracer_type& t)
};
}
3.2.6query与result
//针对查询结果所封装的容器类
template
class result: result_base::kind> {
result ()
result (const result& r)
iterator begin ()
iterator end ()
size_type size ()
bool empty ()
};
//针对查询封装的条件类
class LIBODB_EXPORT query_base {
explicit query_base (const std::string& native)
const clause_type& clause ()
};
namespace mysql
{
template
class query: public query_base,
public query_selector::columns_type {
query (const std::string& q)
query (const query_base& q)
query (bool v)
}
}
template
class query: public mysql::query {
query (bool v)
query (const std::string& q)
query (const mysql::query_base& q)
}
3.2.7nullable
//针对可能为空的字段封装的类似于智能指针的类型
template
class nullable {
typedef T value_type;
T& get ();
const T& get () const;
T* operator-> ();
const T* operator-> () const;
T& operator* ();
const T& operator* () const;
}
3.2.8sql追踪
namespace odb
{
namespace mysql
{
class LIBODB_MYSQL_EXPORT tracer: private odb::tracer
{
public:
virtual ~tracer ();
virtual void prepare (connection&, const statement&);
virtual void execute (connection&, const statement&);
virtual void execute (connection&, const char* statement) = 0;
virtual void deallocate (connection&, const statement&);
}
class LIBODB_MYSQL_EXPORT statement: public odb::statement
{
public:
virtual const char* text () const;
}
}
extern LIBODB_EXPORT tracer& stderr_tracer;
extern LIBODB_EXPORT tracer& stderr_full_tracer;
};
3.2.9异常
namespace odb
{
struct LIBODB_EXPORT exception: std::exception, details::shared_base
{
virtual const char* what () const ODB_NOTHROW_NOEXCEPT = 0;
virtual exception* clone () const = 0;
};
namespace common
{
using odb::exception;
}
}
3.2.10使用样例
curd
//student.h-数据库表定义头文件
#pragma once
#include
#include // std::size_t
#include
#include
#include
#pragma db object table("students")
class Students{
public:
Students() {};
Students(const std::string& name):_name(name){};
std::size_t id() const { return _id; }
void set_id(std::size_t id) { _id = id; }
const std::string& name() const { return _name; }
void set_name(const std::string& name) { _name = name; }
odb::nullable age() const { return _age; }
void set_age(odb::nullable age) { _age = age; }
odb::nullable birthday() const { return _birthday; }
void set_birthday(odb::nullable birthday) { _birthday = birthday; }
odb::nullable gender() const { return _gender; }
void set_gender(odb::nullable gender) { _gender = gender; }
private:
friend class odb::access;
#pragma db id auto column("id")
std::size_t _id;
#pragma db column("name") not_null
std::string _name;
#pragma db column("age")
odb::nullable _age;
#pragma db column("birthday")
odb::nullable _birthday;
#pragma db column("gender")
odb::nullable _gender;
};
#include
#include
#include
#include "student.h"
#include "student-odb.hxx"
const std::string USER = "root";
const std::string PASSWORD = "123456";
const std::string DATABASE = "mytest";
const std::string HOST = "192.168.30.128";
const unsigned int PORT = 3306;
const std::string CHRSET = "utf8";
void insert(const std::unique_ptr& handler)
{
//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃
try{
odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象
//4.获取数据库操作句柄
auto& database = t.database();
//5.数据操作
Students s("张三");
//s.set_id(1001);//自动主键会忽略这步设定在odb中
s.set_age(18);
s.set_gender(std::string("男"));
s.set_birthday(boost::posix_time::time_from_string("2001-02-12 11:45:14"));
database.persist(s);
//6.提交事务-odb规定所有对数据库的数据操作都必须在事务中完成
t.commit();
}catch(const std::exception& e){
std::cerr << "odb error:" << e.what() << std::endl;
}
}
void select(const std::unique_ptr& handler)
{
//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃
try{
odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象
//4.获取数据库操作句柄
auto& database = t.database();
//5.数据操作
auto stu = database.query_one(odb::query::id == 2);
if(!stu)
{
std::cout << "not found" << std::endl;
return;
}
//打印数据
std::cout << "id:" << stu->id() << std::endl;
std::cout << "name:" << stu->name() << std::endl;
if(stu->age()) std::cout << "age:" << *stu->age() << std::endl;
if(stu->gender()) std::cout << "gender:" << *stu->gender() << std::endl;
if(stu->birthday()) std::cout << "birthday:" << stu->birthday()->date() << std::endl;//date打印格式为2001-Feb-12
//如果想要原模原样的打印出2001-02-12 11:45:14,可以使用:boost::posix_time::to_simple_string(*stu->birthday())
//6.提交事务-odb规定所有对数据库的数据操作都必须在事务中完成
t.commit();
}catch(const std::exception& e){
std::cerr << "odb error:" << e.what() << std::endl;
}
}
void select_all(const std::unique_ptr& handler)
{
//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃
try{
odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象
//4.获取数据库操作句柄
auto& database = t.database();
//5.数据操作
auto stus = database.query();
for(auto& stu : stus)
{
//打印数据
std::cout << "id:" << stu.id() << std::endl;
std::cout << "name:" << stu.name() << std::endl;
if(stu.age()) std::cout << "age:" << *(stu.age()) << std::endl;
if(stu.gender()) std::cout << "gender:" << *(stu.gender()) << std::endl;
if(stu.birthday()) std::cout << "birthday:" << stu.birthday()->date() << std::endl;
}
//6.提交事务-odb规定所有对数据库的数据操作都必须在事务中完成
t.commit();
}catch(const std::exception& e){
std::cerr << "odb error:" << e.what() << std::endl;
}
}
void update(const std::unique_ptr& handler)
{
//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃
try{
odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象
//4.获取数据库操作句柄
auto& database = t.database();
//5.数据操作-先查询才能进行更新
auto stu = database.query_one(odb::query::id == 2);
if(!stu)
{
std::cout << "not found" << std::endl;
return;
}
//更新数据
stu->set_age(19);
stu->set_gender(std::string("女"));
stu->set_birthday(boost::posix_time::time_from_string("2001-02-12 11:45:14"));
//提交更新
database.update(*stu);
//6.提交事务-odb规定所有对数据库的数据操作都必须在事务中完成
t.commit();
}catch(const std::exception& e){
std::cerr << "odb error:" << e.what() << std::endl;
}
}
void erase(const std::unique_ptr& handler)
{
//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃
try{
odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象
//4.获取数据库操作句柄
auto& database = t.database();
//5.数据操作-erase接口需要先查询后删除,但是erase_query不需要,一般常用的也是后者
auto stu = database.erase_query(odb::query::id == 3);
if(stu == 0)
{
std::cout << "删除数据成功" << std::endl;
}
//6.提交事务-odb规定所有对数据库的数据操作都必须在事务中完成
t.commit();
}catch(const std::exception& e){
std::cerr << "odb error:" << e.what() << std::endl;
}
}
int main()
{
//1.创建数据库连接池对象
std::unique_ptr pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5
//2.创建数据库操作句柄
std::unique_ptr handler = std::make_unique(USER,PASSWORD,DATABASE,HOST,PORT,nullptr,CHRSET,0,std::move(pool));//将链接池对象的管理权移交给数据库对象
//7.测试操作
insert(handler);
select(handler);
std::cout << std::endl;
select_all(handler);
std::cout << std::endl;
update(handler);
std::cout << std::endl;
select(handler);
erase(handler);
return 0;
}
编译构建
curd:curd.cc student-odb.cxx
g++ $^ -o $@ -std=c++17 -lodb-mysql -lodb -lodb-boost -lgflags -lpthread
student-odb.cxx:student.h
odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time $^
clean:
rm -f curd student-odb.cxx student-odb.hxx
多表查询
#pragma once
#include
#include // std::size_t
#include
#include
#include
#pragma db object table("tbl_students")
class Students{
public:
Students() {};
Students(const std::string& name):_name(name){};
std::size_t id() const { return _id; }
void set_id(std::size_t id) { _id = id; }
std::size_t class_id() const { return _class_id; }
void set_class_id(std::size_t class_id) { _class_id = class_id; }
const std::string& name() const { return _name; }
void set_name(const std::string& name) { _name = name; }
odb::nullable age() const { return _age; }
void set_age(odb::nullable age) { _age = age; }
odb::nullable birthday() const { return _birthday; }
void set_birthday(odb::nullable birthday) { _birthday = birthday; }
odb::nullable gender() const { return _gender; }
void set_gender(odb::nullable gender) { _gender = gender; }
private:
friend class odb::access;
#pragma db id auto column("id")
std::size_t _id;
#pragma db column("class_id")
std::size_t _class_id;
#pragma db column("name") not_null
std::string _name;
#pragma db column("age")
odb::nullable _age;
#pragma db column("birthday")
odb::nullable _birthday;
#pragma db column("gender")
odb::nullable _gender;
};
#pragma db object table("tbl_classes")
class Classes{
public:
Classes() {};
Classes(const std::string& name):_name(name){};
std::size_t id() const { return _id; }
void set_id(std::size_t id) { _id = id; }
const std::string& name() const { return _name; }
void set_name(const std::string& name) { _name = name; }
private:
friend class odb::access;
#pragma db id auto column("id")
std::size_t _id;
#pragma db column("name") not_null unique type("VARCHAR(32)")
std::string _name;
};
//查询条件:class_id == 1
#pragma db view object(Classes)\
object(Students : Students::_class_id == Classes::_id) \
query((?))
struct ClassStudents{
std::shared_ptr class_ptr;
std::shared_ptr student_ptr;
};
//查询条件:name == "张三"
#pragma db view object(Students) \
object(Classes : Students::_class_id == Classes::_id) \
query((?))
struct StudentClass{
std::shared_ptr student_ptr;
std::shared_ptr class_ptr;
};
#include
#include
#include
#include "student.h"
#include "student-odb.hxx"
const std::string USER = "root";
const std::string PASSWORD = "123456";
const std::string DATABASE = "mytest";
const std::string HOST = "192.168.30.128";
const unsigned int PORT = 3306;
const std::string CHRSET = "utf8";
void showClassStudents(const std::unique_ptr& handler)
{
//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃
try{
odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象
//4.获取数据库操作句柄
auto& database = t.database();
//5.数据操作查询1号班级的所有学生
typedef odb::query Query;
typedef odb::result Result;
Result r = database.query(Query::Classes::name == "一年级一班");
//遍历结果
for(auto& item:r){
if(item.student_ptr) std::cout << item.student_ptr->name() << "\t";
std::cout << item.class_ptr->name() << std::endl;
}
//6.提交事务-odb规定所有对数据库的数据操作都必须在事务中完成
t.commit();
}catch(const std::exception& e){
std::cerr << "odb error:" << e.what() << std::endl;
}
}
void showStudentDetail(const std::unique_ptr& handler)
{
//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃
try{
odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象
//4.获取数据库操作句柄
auto& database = t.database();
//5.数据操作查询1号班级的所有学生
typedef odb::query Query;
typedef odb::result Result;
Result r = database.query(Query::Students::name == "张三");
for(auto& item:r){
if(item.student_ptr) {
std::cout << item.student_ptr->name() << "\t";
std::cout << *(item.student_ptr->age()) << "\t";
std::cout << *(item.student_ptr->gender()) << "\t";
}
if(item.class_ptr) std::cout << item.class_ptr->name() << std::endl;
}
//6.提交事务-odb规定所有对数据库的数据操作都必须在事务中完成
t.commit();
}catch(const std::exception& e){
std::cerr << "odb error:" << e.what() << std::endl;
}
}
int main()
{
//1.创建数据库连接池对象
std::unique_ptr pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5
//2.创建数据库操作句柄
std::unique_ptr handler = std::make_unique(USER,PASSWORD,DATABASE,HOST,PORT,nullptr,CHRSET,0,std::move(pool));//将链接池对象的管理权移交给数据库对象
//7.测试操作
showClassStudents(handler);
showStudentDetail(handler);
return 0;
}
编译构建:
view:view.cc student-odb.cxx
g++ $^ -o $@ -std=c++17 -lodb-mysql -lodb -lodb-boost -lgflags -lpthread
student-odb.cxx:student.h
odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time $^
clean:
rm -f view *-odb.* *.sql
分组查询
//student.h
#pragma once
#include
#include // std::size_t
#include
#include
#include
#pragma db object table("tbl_students")
class Students{
public:
Students() {};
Students(const std::string& name):_name(name){};
std::size_t id() const { return _id; }
void set_id(std::size_t id) { _id = id; }
std::size_t class_id() const { return _class_id; }
void set_class_id(std::size_t class_id) { _class_id = class_id; }
const std::string& name() const { return _name; }
void set_name(const std::string& name) { _name = name; }
odb::nullable age() const { return _age; }
void set_age(odb::nullable age) { _age = age; }
odb::nullable birthday() const { return _birthday; }
void set_birthday(odb::nullable birthday) { _birthday = birthday; }
odb::nullable gender() const { return _gender; }
void set_gender(odb::nullable gender) { _gender = gender; }
private:
friend class odb::access;
#pragma db id auto column("id")
std::size_t _id;
#pragma db column("class_id")
std::size_t _class_id;
#pragma db column("name") not_null
std::string _name;
#pragma db column("age")
odb::nullable _age;
#pragma db column("birthday")
odb::nullable _birthday;
#pragma db column("gender")
odb::nullable _gender;
};
#pragma db view object(Students) \
query("group by" + Students::_class_id + "having" + (?))
struct ClassGroup{
#pragma db column(Students::_class_id)
std::size_t class_id;
#pragma db column("count(" + Students::_id + ") as class_total_count")
std::size_t class_total_count;
#pragma db column("avg(" + Students::_age + ") as class_avg_age")
double class_avg_age;
};
#include
#include
#include
#include "student.h"
#include "student-odb.hxx"
const std::string USER = "root";
const std::string PASSWORD = "123456";
const std::string DATABASE = "mytest";
const std::string HOST = "192.168.30.128";
const unsigned int PORT = 3306;
const std::string CHRSET = "utf8";
void showStudentsGroupByClass(const std::unique_ptr& handler)
{
//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃
try{
odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象
//追踪sql语句
t.tracer(odb::stderr_full_tracer);
//4.获取数据库操作句柄
auto& database = t.database();
//5.打印出平均年龄高于19岁的学生信息
typedef odb::query Query;
typedef odb::result Result;
Result r = database.query("class_avg_age >= 19");
for(auto& row : r){
std::cout << "班级:" << row.class_id << "\t";
std::cout << "学生总数:" << row.class_total_count << "\t";
std::cout << "平均年龄:" << row.class_avg_age << std::endl;
}
//6.提交事务-odb规定所有对数据库的数据操作都必须在事务中完成
t.commit();
}catch(const std::exception& e){
std::cerr << "odb error:" << e.what() << std::endl;
}
}
int main()
{
//1.创建数据库连接池对象
std::unique_ptr pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5
//2.创建数据库操作句柄
std::unique_ptr handler = std::make_unique(USER,PASSWORD,DATABASE,HOST,PORT,nullptr,CHRSET,0,std::move(pool));//将链接池对象的管理权移交给数据库对象
//7.测试操作
showStudentsGroupByClass(handler);
return 0;
}
编译构建
group:group.cc student-odb.cxx
g++ $^ -o $@ -std=c++17 -lodb-mysql -lodb -lodb-boost -lgflags -lpthread
student-odb.cxx:student.h
odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time $^
clean:
rm -f group *-odb.* *.sql
分页查询
#pragma once
#include
#include // std::size_t
#include
#include
#include
#pragma db object table("tbl_students")
class Students{
public:
Students() {};
Students(const std::string& name):_name(name){};
std::size_t id() const { return _id; }
void set_id(std::size_t id) { _id = id; }
std::size_t class_id() const { return _class_id; }
void set_class_id(std::size_t class_id) { _class_id = class_id; }
const std::string& name() const { return _name; }
void set_name(const std::string& name) { _name = name; }
odb::nullable age() const { return _age; }
void set_age(odb::nullable age) { _age = age; }
odb::nullable birthday() const { return _birthday; }
void set_birthday(odb::nullable birthday) { _birthday = birthday; }
odb::nullable gender() const { return _gender; }
void set_gender(odb::nullable gender) { _gender = gender; }
private:
friend class odb::access;
#pragma db id auto column("id")
std::size_t _id;
#pragma db column("class_id")
std::size_t _class_id;
#pragma db column("name") not_null
std::string _name;
#pragma db column("age")
odb::nullable _age;
#pragma db column("birthday")
odb::nullable _birthday;
#pragma db column("gender")
odb::nullable _gender;
};
#pragma db object table("tbl_classes")
class Classes{
public:
Classes() {};
Classes(const std::string& name):_name(name){};
std::size_t id() const { return _id; }
void set_id(std::size_t id) { _id = id; }
const std::string& name() const { return _name; }
void set_name(const std::string& name) { _name = name; }
private:
friend class odb::access;
#pragma db id auto column("id")
std::size_t _id;
#pragma db column("name") not_null unique type("VARCHAR(32)")
std::string _name;
};
#pragma db view object(Students) \
query((?) + "order by" + Students::_age + "desc limit 3 offset 0")
struct StudentsAgeRanking{
std::size_t id;
std::size_t class_id;
std::string name;
odb::nullable age;
};
#include
#include
#include
#include "student.h"
#include "student-odb.hxx"
const std::string USER = "root";
const std::string PASSWORD = "123456";
const std::string DATABASE = "mytest";
const std::string HOST = "192.168.30.128";
const unsigned int PORT = 3306;
const std::string CHRSET = "utf8";
void showStudentsRankingByAge(const std::unique_ptr& handler)
{
//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃
try{
odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象
//追踪sql语句
t.tracer(odb::stderr_full_tracer);
//4.获取数据库操作句柄
auto& database = t.database();
//5.打印出年龄大于18并且位于前三的所有学生
typedef odb::query Query;
typedef odb::result Result;
Result r = database.query(Query::age + ">= 18");
for(auto& row : r){
std::cout << "学号:" << row.id << "\t";
std::cout << "班级:" << row.class_id << "\t";
std::cout << "姓名:" << row.name << "\t";
std::cout << "年龄:" << row.age.get() << std::endl;
}
//6.提交事务-odb规定所有对数据库的数据操作都必须在事务中完成
t.commit();
}catch(const std::exception& e){
std::cerr << "odb error:" << e.what() << std::endl;
}
}
int main()
{
//1.创建数据库连接池对象
std::unique_ptr pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5
//2.创建数据库操作句柄
std::unique_ptr handler = std::make_unique(USER,PASSWORD,DATABASE,HOST,PORT,nullptr,CHRSET,0,std::move(pool));//将链接池对象的管理权移交给数据库对象
//7.测试操作
showStudentsRankingByAge(handler);
return 0;
}
limit:limit.cc student-odb.cxx
g++ $^ -o $@ -std=c++17 -lodb-mysql -lodb -lodb-boost -lgflags -lpthread
student-odb.cxx:student.h
odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time $^
clean:
rm -f limit *-odb.* *.sql
原生语句查询
#pragma once
#include
#include // std::size_t
#include
#include
#include
#pragma db object table("tbl_students")
class Students{
public:
Students() {};
Students(const std::string& name):_name(name){};
std::size_t id() const { return _id; }
void set_id(std::size_t id) { _id = id; }
std::size_t class_id() const { return _class_id; }
void set_class_id(std::size_t class_id) { _class_id = class_id; }
const std::string& name() const { return _name; }
void set_name(const std::string& name) { _name = name; }
odb::nullable age() const { return _age; }
void set_age(odb::nullable age) { _age = age; }
odb::nullable birthday() const { return _birthday; }
void set_birthday(odb::nullable birthday) { _birthday = birthday; }
odb::nullable gender() const { return _gender; }
void set_gender(odb::nullable gender) { _gender = gender; }
private:
friend class odb::access;
#pragma db id auto column("id")
std::size_t _id;
#pragma db column("class_id")
std::size_t _class_id;
#pragma db column("name") not_null
std::string _name;
#pragma db column("age")
odb::nullable _age;
#pragma db column("birthday")
odb::nullable _birthday;
#pragma db column("gender")
odb::nullable _gender;
};
#pragma db object table("tbl_classes")
class Classes{
public:
Classes() {};
Classes(const std::string& name):_name(name){};
std::size_t id() const { return _id; }
void set_id(std::size_t id) { _id = id; }
const std::string& name() const { return _name; }
void set_name(const std::string& name) { _name = name; }
private:
friend class odb::access;
#pragma db id auto column("id")
std::size_t _id;
#pragma db column("name") not_null unique type("VARCHAR(32)")
std::string _name;
};
//使用原生语句进行年龄大于18岁并位于前4的所有学生排名
#pragma db view query("select tbl_students.id, tbl_students.class_id, tbl_students.name, tbl_students.age from tbl_students where tbl_students.age >= 18 order by tbl_students.age desc limit 4 offset 0")
struct StudentsAgeRanking{
std::size_t id;
std::size_t class_id;
std::string name;
odb::nullable age;
};
#include
#include
#include
#include "student.h"
#include "student-odb.hxx"
const std::string USER = "root";
const std::string PASSWORD = "123456";
const std::string DATABASE = "mytest";
const std::string HOST = "192.168.30.128";
const unsigned int PORT = 3306;
const std::string CHRSET = "utf8";
void showStudentsRankingByAge(const std::unique_ptr& handler)
{
//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃
try{
odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象
//追踪sql语句
t.tracer(odb::stderr_full_tracer);
//4.获取数据库操作句柄
auto& database = t.database();
//5.打印出年龄大于18并且位于前三的所有学生
typedef odb::query Query;
typedef odb::result Result;
Result r = database.query();
for(auto& row : r){
std::cout << "学号:" << row.id << "\t";
std::cout << "班级:" << row.class_id << "\t";
std::cout << "姓名:" << row.name << "\t";
std::cout << "年龄:" << row.age.get() << std::endl;
}
//6.提交事务-odb规定所有对数据库的数据操作都必须在事务中完成
t.commit();
}catch(const std::exception& e){
std::cerr << "odb error:" << e.what() << std::endl;
}
}
int main()
{
//1.创建数据库连接池对象
std::unique_ptr pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5
//2.创建数据库操作句柄
std::unique_ptr handler = std::make_unique(USER,PASSWORD,DATABASE,HOST,PORT,nullptr,CHRSET,0,std::move(pool));//将链接池对象的管理权移交给数据库对象
//7.测试操作
showStudentsRankingByAge(handler);
return 0;
}
编译构建
native:native.cc student-odb.cxx
g++ $^ -o $@ -std=c++17 -lodb-mysql -lodb -lodb-boost -lgflags -lpthread
student-odb.cxx:student.h
odb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time $^
clean:
rm -f native *-odb.* *.sql
3.3二次封装
这里跟前面的redis++一样,因为人家原来提供的客户端封装的已经很完善了,所以我们这里也只是对odb数据库操作对象创建进行一个简单封装:
#pragma once
#include
#include
#include
namespace limeodb {
struct mysql_settings{
std::string host;//主机地址
std::string user;//用户名
std::string password;//密码
std::string database;//数据库名
unsigned int port = 3306;//端口号
std::string charset = "utf8";//字符集
};
class DbFactory {
public:
static std::shared_ptr create_mysqldb(const mysql_settings& settings);
};
}// namespace limeodb
#include "limeodb.h"
namespace limeodb {
std::shared_ptr DbFactory::create_mysqldb(const mysql_settings& settings)
{
//1.创建数据库连接池对象
std::unique_ptr pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5
//2.创建数据库操作句柄
std::unique_ptr handler \
= std::make_unique(settings.user,settings.password,settings.database, \
settings.host,settings.port,nullptr,settings.charset,0,std::move(pool));//将链接池对象的管理权移交给数据库对象
return handler;
}
}//namespace limeodb
想要测试封装成果的与redis++哪里一样的方法即可,这里不再给出测试封装样例。
浙公网安备 33010602011771号