基于脚手架微服务的视频点播系统-脚手架开发部分Fast-dfs,redis++,odb的简单使用与二次封装
文章目录
- 一.FastDfs
- 1.1SDK安装
- 1.2接口
- 1.2.1头文件
- 1.2.2链接库,Log,Error
- 1.2.3Init
- 1.2.4Tracker
- 1.2.5Storage
- 1.2.6文件上传接口Upload
- 1.2.7文件下载接口Download
- 1.2.8文件删除接口Delete
- 1.3二次封装
- 使用样例
- 从文件上传/下载
- 从缓冲区上传/下载
- 删除文件
- 二.redis++
- 2.1安装 Redis++
- 2.2接口
- 2.2.1头文件与链接库
- 2.2.2异常
- 2.2.3初始化操作
- 2.2.4常规操作
- 2.2.5String操作
- 使用样例
- 2.2.6list操作
- 使用样例
- 2.2.7hash操作
- 使用样例
- 2.2.8set操作
- 使用样例
- 2.2.9zset操作
- 使用样例
- 2.2.10transcation&&pipline操作
- 使用样例
- 2.2.11使用样例编译构建
- 2.3二次封装
- 三.odb-Mysql
- 3.1odb常见操作
- 3.1.1odb类型映射
- 3.1.2odb编程
- 3.2数据操作相关类与接口
- 3.2.1代码生成指令
- 3.2.2odb数据对象头文件
- 3.2.3odb操作对象头文件
- 3.2.4链接库
- 3.2.5database与transaction
- 3.2.6query与result
- 3.2.7nullable
- 3.2.8sql追踪
- 3.2.9异常
- 3.2.10使用样例
- curd
- 多表查询
- 分组查询
- 分页查询
- 原生语句查询
- 3.3二次封装
一.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 <fastcommon/logger.h>
#include <fastdfs/fdfs_client.h>
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_INFOint 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 dedectFCCommunicationType comm_type;bool validate_flag; //for connection poolchar ip_addr[IP_ADDRESS_SIZE];void *arg1; //for RDMAchar 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]; //keychar 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 <iostream>
#include <vector>
#include <optional>
extern "C" {#include <fastcommon/logger.h>#include <fastdfs/fdfs_client.h>
}namespace limefds {//fastdfs客户端相关配置项struct fdfs_config {//根据官方文档,设置fastdfs客户端的默认值为官方推荐值int connect_timeout = 30; //连接超时时间int network_timeout = 30; //网络超时时间std::vector<std::string> 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);//从文件或内存中上传文件,返回文件idstatic std::optional<std::string> uploadFromLFile(const std::string &local_filename);static std::optional<std::string> 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 <sstream>
#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 = " <<server << "\n";}ss << "connect_timeout = " << config.connect_timeout << "\n";ss << "network_timeout = " << config.network_timeout << "\n";ss << "use_connection_pool = " << config.use_connection_pool << "\n";ss << "connection_pool_max_idle_time = " << config.connection_pool_max_idle_time << "\n";int ret = fdfs_client_init_from_buffer(ss.str().c_str());if(ret != 0){ERR("初始化fastdfs客户端失败,错误原因:{}",STRERROR(ret));exit(-1);//初始化都失败了就直接终止程序运行了}}//从文件或内存中上传文件,返回文件idstd::optional<std::string> FdfsClient::uploadFromLFile(const std::string &local_filename){auto tracker_server = tracker_get_connection();if (tracker_server == nullptr) {ERR("获取tracker服务器连接句柄失败");return std::optional<std::string>();}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<std::string>();}tracker_close_connection(tracker_server);return std::string(file_id);}std::optional<std::string> FdfsClient::uploadFromBuffer(const std::string &buffer){auto tracker_server = tracker_get_connection();if (tracker_server == nullptr) {ERR("获取tracker服务器连接句柄失败");return std::optional<std::string>();}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<std::string>();}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] << " <file_id>" << 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 <sw/redis++/redis.h>
-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 theconnection.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 <typename Input>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 <typename Input>long long del(Input first, Input last);//判断指定键值对是否存在long long exists(const StringView &key);template <typename Input>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 <typename Input, typename Output>void mget(Input first, Input last, Output output);//批量新增数据template <typename Input>void mset(Input first, Input last);
};
使用样例
#include <sw/redis++/redis.h>
#include <iostream>
#include <chrono>//进行简单的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<std::pair<std::string,std::string>> kvs = {{"xiaozhang","girl"},{"xiaoqian","boy"}};redis.mset(kvs.begin(),kvs.end());//批量获取数据std::vector<std::string> keys = {"xiaoming","xiaozhang","xiaoqian"};std::vector<sw::redis::OptionalString> 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 <typename Input>long long rpush(const StringView &key, Input first, Input last);template <typename Input>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 <typename Output>void lrange(const StringView &key, long long start, long long stop,Output output);
};
使用样例
#include <sw/redis++/redis.h>
#include <iostream>
#include <chrono>void print_list(sw::redis::Redis& redis, const std::string& key)
{//进行list键值的打印std::vector<std::string> 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<std::string> students = {"xiaozhang", "xiaodong"};redis.rpush("class_students", students.begin(), students.end());print_list(redis, "class_students");//打印班级学生列表//批量从左端插入std::vector<std::string> students2 = {"xiaozhao", "xiaowei"};redis.lpush("class_students", students2.begin(), students2.end());print_list(redis, "class_students");//打印班级学生列表//查找对应位置的元素查找第0个位置应为xiaoweistd::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 <typename Input>long long hdel(const StringView &key, Input first, Input last);//获取key的所有字段到std::unordered_maptemplate <typename Output>void hgetall(const StringView &key, Output output);//OptionalString hget(const StringView &key, const StringView &field);template <typename Input, typename Output>void hmget(const StringView &key, Input first, Input last, Output output);//std::unordered_map<std::string, std::string> m//redis.hmset("hash", m.begin(), m.end());template <typename Input>void hmset(const StringView &key, Input first, Input last);long long hset(const StringView &key, const StringView &field, constStringView &val);//对指定字段进⾏数值增加操作,⽀持负数long long hincrby(const StringView &key, const StringView &field, longlong increment);
}
使用样例
#include <sw/redis++/redis.h>
#include <iostream>
#include <chrono>void hash_print(sw::redis::Redis& redis, const std::string& key)
{//进行键值对应的hash表的打印std::unordered_map<std::string, std::string> 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<std::string, std::string> 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<std::string> keys = {"zhaoliu", "tianqi", "xiaoming"};std::vector<std::string> 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 <typename Input> 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<std::string> members1;
/// redis.smembers("set", std::inserter(members1, members1.begin()));
//std::vector<std::string> members2
/// redis.smembers("set", std::back_inserter(members2));
template <typename Output> void smembers(const StringView &key, Output output);
使用样例
#include <sw/redis++/redis.h>
#include <iostream>
#include <chrono>
#include <unordered_set>// 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<std::string> 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<std::string> 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<OptionalDouble>("ZADD", "key", "XX", "INCR",
10, "mem");
template <typename Input>
long long zadd(const StringView &key,Input first,Input last,UpdateType type = UpdateType::ALWAYS,bool changed = false);
// std::unordered_map<std::string, double> 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 <typename Output>
void zrange(const StringView &key, long long start, long long stop, Outputoutput);
// 获取以得分排名的区间成员,从低到⾼排序
template <typename Interval, typename Output>
void zrangebyscore(const StringView &key, const Interval &interval, Outputoutput);
template <typename Interval, typename Output>
void zrangebyscore(const StringView &key, const Interval &interval,const LimitOptions &opts, Output output);
// 获取得分的区间成员,,从低到⾼排序
OptionalLongLong zrank(const StringView &key, const StringView &member);
// 获取成员排名 -- 从低到⾼, 从0开始
template <typename Interval, typename Output>
void zrangebylex(const StringView &key, const Interval &interval, Outputoutput);
template <typename Interval, typename Output>
void zrangebylex(const StringView &key, const Interval &interval,const LimitOptions &opts, Output output);
//获取字典序区间元素
template <typename Output>
void zrevrange(const StringView &key, long long start, long long stop, Outputoutput);
// 获取排名区间内的元素--- 从⾼到低排序
template <typename Interval, typename Output>
void zrevrangebyscore(const StringView &key, const Interval &interval, Outputoutput);
template <typename Interval, typename Output>
void zrevrangebyscore(const StringView &key, const Interval &interval,const LimitOptions &opts, Output output);
// 获取分数区间内的元素--- 从⾼到底排序
OptionalLongLong zrevrank(const StringView &key, const StringView &member);
// 获取指定元素排名 -- 从⾼到低
template <typename Interval, typename Output>
void zrevrangebylex(const StringView &key, const Interval &interval, Outputoutput);
template <typename Interval, typename Output>
void zrevrangebylex(const StringView &key, const Interval &interval,const LimitOptions &opts, Output output);
// 获取字典序区间元素
OptionalDouble zscore(const StringView &key, const StringView &member);
// 获取指定元素的权重得分
template <typename Output>
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<std::pair<std::string, double>> zpopmax(const StringView &key);
// 弹出集合中得分最⾼的成员
Optional<std::pair<std::string, double>> zpopmin(const StringView &key);
// 弹出集合中得分最低的成员
template <typename Interval>
long long zremrangebyscore(const StringView &key, const Interval &interval);
// 删除分数区间元素
long long zremrangebyrank(const StringView &key, long long start, long longstop);
// 删除排名区间元素
template <typename Interval>
long long zremrangebylex(const StringView &key, const Interval &interval);
// 删除集合中字典序区间元素
使用样例
#include <sw/redis++/redis.h>
#include <iostream>
#include <chrono>
#include <unordered_set>//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<double>{});其所指定的区间为(-inf, +inf)// 6. 迭代浏览集合中的数据
void print(sw::redis::Redis &redis, const std::string &key) {sw::redis::Cursor cursor = 0; //定义光标,默认从第0个元素开始进行匹配迭代std::vector<std::pair<std::string, double>> 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<std::string, double> 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<std::string> 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<double>(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<double>(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<TransactionImpl>;using Pipeline = QueuedRedis<PipelineImpl>;template <typename Impl>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 longend)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 <typename Result>auto get(std::size_t idx)-> typename std::enable_if<!std::is_same<Result, bool>::value,Result>::typetemplate <typename Output>void get(std::size_t idx, Output output);redisReply& get(std::size_t idx);};
}
使用样例
//pipline
#include <sw/redis++/redis.h>
#include <sw/redis++/queued_redis.h>
#include <iostream>
#include <chrono>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<bool>(0) << std::endl;std::cout << "replies[1]: " << replies.get<bool>(1) << std::endl;std::cout << "replies[2]: " << replies.get<sw::redis::OptionalString>(2).value() << std::endl;std::cout << "replies[3]: " << *replies.get<sw::redis::OptionalString>(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<bool>(0) << std::endl;std::cout << "replies[1]: " << replies.get<bool>(1) << std::endl;std::unordered_map<std::string,std::string> 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 <sw/redis++/redis.h>
#include <sw/redis++/queued_redis.h>
#include <iostream>
#include <chrono>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<bool>(0) << std::endl;std::cout << "replies[1]: " << replies.get<bool>(1) << std::endl;std::cout << "replies[2]: " << replies.get<sw::redis::OptionalString>(2).value() << std::endl;std::cout << "replies[3]: " << *replies.get<sw::redis::OptionalString>(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<bool>(0) << std::endl;std::cout << "replies[1]: " << replies.get<bool>(1) << std::endl;std::unordered_map<std::string,std::string> 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.ccg++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
list:list.ccg++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
hash:hash.ccg++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
set:set.ccg++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
complement:complement.ccg++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
zset:zset.ccg++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
pipeline:pipeline.ccg++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
transaction:transaction.ccg++ -std=c++17 $^ -o $@ -lpthread -lredis++ -lhiredis
watch:watch.ccg++ -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 <sw/redis++/redis.h>
#include <sw/redis++/queued_redis.h>
#include <iostream>
#include <chrono>
#include <memory>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<sw::redis::Redis> create(const redis_settings& settings);};
}// namespace limeredis
//limeredis.cc
#include "limeredis.h"namespace limeredis {std::shared_ptr<sw::redis::Redis> 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<sw::redis::Redis>(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 <string>
#include <cstddef> // std::size_t
#include <boost/date_time/posix_time/posix_time.hpp>
#include <odb/nullable.hxx>
#include <odb/core.hxx>
3.2.3odb操作对象头文件
#include <odb/database.hxx>
#include <odb/mysql/transaction.hxx>
#include <odb/mysql/database.hxx>
#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<connection_factory> =details::transfer_ptr<connection_factory> ());//新增数据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<T>&);result<T> query (const std::string&);result<T> query (const odb::query<T>&, bool cache = true);typename result<T>::pointer_type query_one(const odb::query<T>&);//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 <typename T>
class result: result_base<T, class_traits<T>::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 <typename T>class query: public query_base,public query_selector<T, id_mysql>::columns_type {query (const std::string& q)query (const query_base& q)query (bool v)}}
template <typename T>
class query<T, mysql::query_base>: public mysql::query<T> {query (bool v)query (const std::string& q)query (const mysql::query_base& q)
}
3.2.7nullable
//针对可能为空的字段封装的类似于智能指针的类型
template <typename T>
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 <string>
#include <cstddef> // std::size_t
#include <boost/date_time/posix_time/posix_time.hpp>
#include <odb/nullable.hxx>
#include <odb/core.hxx>#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<std::size_t> age() const { return _age; }void set_age(odb::nullable<std::size_t> age) { _age = age; }odb::nullable<boost::posix_time::ptime> birthday() const { return _birthday; }void set_birthday(odb::nullable<boost::posix_time::ptime> birthday) { _birthday = birthday; }odb::nullable<std::string> gender() const { return _gender; }void set_gender(odb::nullable<std::string> gender) { _gender = gender; }private:friend class odb::access;#pragma db id auto column("id")std::size_t _id;#pragma db column("name") not_nullstd::string _name;#pragma db column("age")odb::nullable<std::size_t> _age;#pragma db column("birthday")odb::nullable<boost::posix_time::ptime> _birthday;#pragma db column("gender")odb::nullable<std::string> _gender;
};
#include <odb/database.hxx>
#include <odb/mysql/transaction.hxx>
#include <odb/mysql/database.hxx>#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<odb::mysql::database>& 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<odb::mysql::database>& handler)
{//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃try{odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象//4.获取数据库操作句柄auto& database = t.database();//5.数据操作auto stu = database.query_one<Students>(odb::query<Students>::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<odb::mysql::database>& handler)
{//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃try{odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象//4.获取数据库操作句柄auto& database = t.database();//5.数据操作auto stus = database.query<Students>();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<odb::mysql::database>& handler)
{//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃try{odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象//4.获取数据库操作句柄auto& database = t.database();//5.数据操作-先查询才能进行更新auto stu = database.query_one<Students>(odb::query<Students>::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<odb::mysql::database>& 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<Students>(odb::query<Students>::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<odb::mysql::connection_factory> pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5//2.创建数据库操作句柄std::unique_ptr<odb::mysql::database> handler = std::make_unique<odb::mysql::database>(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.cxxg++ $^ -o $@ -std=c++17 -lodb-mysql -lodb -lodb-boost -lgflags -lpthread
student-odb.cxx:student.hodb -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 <string>
#include <cstddef> // std::size_t
#include <boost/date_time/posix_time/posix_time.hpp>
#include <odb/nullable.hxx>
#include <odb/core.hxx>#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<std::size_t> age() const { return _age; }void set_age(odb::nullable<std::size_t> age) { _age = age; }odb::nullable<boost::posix_time::ptime> birthday() const { return _birthday; }void set_birthday(odb::nullable<boost::posix_time::ptime> birthday) { _birthday = birthday; }odb::nullable<std::string> gender() const { return _gender; }void set_gender(odb::nullable<std::string> 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_nullstd::string _name;#pragma db column("age")odb::nullable<std::size_t> _age;#pragma db column("birthday")odb::nullable<boost::posix_time::ptime> _birthday;#pragma db column("gender")odb::nullable<std::string> _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<Classes> class_ptr;std::shared_ptr<Students> student_ptr;
};//查询条件:name == "张三"
#pragma db view object(Students) \object(Classes : Students::_class_id == Classes::_id) \query((?))
struct StudentClass{std::shared_ptr<Students> student_ptr;std::shared_ptr<Classes> class_ptr;
};
#include <odb/database.hxx>
#include <odb/mysql/transaction.hxx>
#include <odb/mysql/database.hxx>#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<odb::mysql::database>& handler)
{//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃try{odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象//4.获取数据库操作句柄auto& database = t.database();//5.数据操作查询1号班级的所有学生typedef odb::query<ClassStudents> Query;typedef odb::result<ClassStudents> Result;Result r = database.query<ClassStudents>(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<odb::mysql::database>& handler)
{//3.创建事务对象然后由事务对象获取数据库对象-为了确保线程安全-需要进行异常捕获否则会引发程序崩溃try{odb::mysql::transaction t(handler->begin(),true);//后面的bool参数表示是否是在当前线程中创建事务对象//4.获取数据库操作句柄auto& database = t.database();//5.数据操作查询1号班级的所有学生typedef odb::query<StudentClass> Query;typedef odb::result<StudentClass> Result;Result r = database.query<StudentClass>(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<odb::mysql::connection_factory> pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5//2.创建数据库操作句柄std::unique_ptr<odb::mysql::database> handler = std::make_unique<odb::mysql::database>(USER,PASSWORD,DATABASE,HOST,PORT,nullptr,CHRSET,0,std::move(pool));//将链接池对象的管理权移交给数据库对象//7.测试操作showClassStudents(handler);showStudentDetail(handler);return 0;
}
编译构建:
view:view.cc student-odb.cxxg++ $^ -o $@ -std=c++17 -lodb-mysql -lodb -lodb-boost -lgflags -lpthread
student-odb.cxx:student.hodb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time $^
clean:rm -f view *-odb.* *.sql
分组查询
//student.h
#pragma once
#include <string>
#include <cstddef> // std::size_t
#include <boost/date_time/posix_time/posix_time.hpp>
#include <odb/nullable.hxx>
#include <odb/core.hxx>#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<std::size_t> age() const { return _age; }void set_age(odb::nullable<std::size_t> age) { _age = age; }odb::nullable<boost::posix_time::ptime> birthday() const { return _birthday; }void set_birthday(odb::nullable<boost::posix_time::ptime> birthday) { _birthday = birthday; }odb::nullable<std::string> gender() const { return _gender; }void set_gender(odb::nullable<std::string> 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_nullstd::string _name;#pragma db column("age")odb::nullable<std::size_t> _age;#pragma db column("birthday")odb::nullable<boost::posix_time::ptime> _birthday;#pragma db column("gender")odb::nullable<std::string> _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 <odb/database.hxx>
#include <odb/mysql/transaction.hxx>
#include <odb/mysql/database.hxx>#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<odb::mysql::database>& 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<ClassGroup> Query;typedef odb::result<ClassGroup> Result;Result r = database.query<ClassGroup>("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<odb::mysql::connection_factory> pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5//2.创建数据库操作句柄std::unique_ptr<odb::mysql::database> handler = std::make_unique<odb::mysql::database>(USER,PASSWORD,DATABASE,HOST,PORT,nullptr,CHRSET,0,std::move(pool));//将链接池对象的管理权移交给数据库对象//7.测试操作showStudentsGroupByClass(handler);return 0;
}
编译构建
group:group.cc student-odb.cxxg++ $^ -o $@ -std=c++17 -lodb-mysql -lodb -lodb-boost -lgflags -lpthread
student-odb.cxx:student.hodb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time $^
clean:rm -f group *-odb.* *.sql
分页查询
#pragma once
#include <string>
#include <cstddef> // std::size_t
#include <boost/date_time/posix_time/posix_time.hpp>
#include <odb/nullable.hxx>
#include <odb/core.hxx>#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<std::size_t> age() const { return _age; }void set_age(odb::nullable<std::size_t> age) { _age = age; }odb::nullable<boost::posix_time::ptime> birthday() const { return _birthday; }void set_birthday(odb::nullable<boost::posix_time::ptime> birthday) { _birthday = birthday; }odb::nullable<std::string> gender() const { return _gender; }void set_gender(odb::nullable<std::string> 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_nullstd::string _name;#pragma db column("age")odb::nullable<std::size_t> _age;#pragma db column("birthday")odb::nullable<boost::posix_time::ptime> _birthday;#pragma db column("gender")odb::nullable<std::string> _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<std::size_t> age;
};
#include <odb/database.hxx>
#include <odb/mysql/transaction.hxx>
#include <odb/mysql/database.hxx>#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<odb::mysql::database>& 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<StudentsAgeRanking> Query;typedef odb::result<StudentsAgeRanking> Result;Result r = database.query<StudentsAgeRanking>(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<odb::mysql::connection_factory> pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5//2.创建数据库操作句柄std::unique_ptr<odb::mysql::database> handler = std::make_unique<odb::mysql::database>(USER,PASSWORD,DATABASE,HOST,PORT,nullptr,CHRSET,0,std::move(pool));//将链接池对象的管理权移交给数据库对象//7.测试操作showStudentsRankingByAge(handler);return 0;
}
limit:limit.cc student-odb.cxxg++ $^ -o $@ -std=c++17 -lodb-mysql -lodb -lodb-boost -lgflags -lpthread
student-odb.cxx:student.hodb -d mysql --std c++11 --generate-query --generate-schema --profile boost/date-time $^
clean:rm -f limit *-odb.* *.sql
原生语句查询
#pragma once
#include <string>
#include <cstddef> // std::size_t
#include <boost/date_time/posix_time/posix_time.hpp>
#include <odb/nullable.hxx>
#include <odb/core.hxx>#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<std::size_t> age() const { return _age; }void set_age(odb::nullable<std::size_t> age) { _age = age; }odb::nullable<boost::posix_time::ptime> birthday() const { return _birthday; }void set_birthday(odb::nullable<boost::posix_time::ptime> birthday) { _birthday = birthday; }odb::nullable<std::string> gender() const { return _gender; }void set_gender(odb::nullable<std::string> 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_nullstd::string _name;#pragma db column("age")odb::nullable<std::size_t> _age;#pragma db column("birthday")odb::nullable<boost::posix_time::ptime> _birthday;#pragma db column("gender")odb::nullable<std::string> _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<std::size_t> age;
};
#include <odb/database.hxx>
#include <odb/mysql/transaction.hxx>
#include <odb/mysql/database.hxx>#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<odb::mysql::database>& 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<StudentsAgeRanking> Query;typedef odb::result<StudentsAgeRanking> Result;Result r = database.query<StudentsAgeRanking>();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<odb::mysql::connection_factory> pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5//2.创建数据库操作句柄std::unique_ptr<odb::mysql::database> handler = std::make_unique<odb::mysql::database>(USER,PASSWORD,DATABASE,HOST,PORT,nullptr,CHRSET,0,std::move(pool));//将链接池对象的管理权移交给数据库对象//7.测试操作showStudentsRankingByAge(handler);return 0;
}
编译构建
native:native.cc student-odb.cxxg++ $^ -o $@ -std=c++17 -lodb-mysql -lodb -lodb-boost -lgflags -lpthread
student-odb.cxx:student.hodb -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 <odb/database.hxx>
#include <odb/mysql/transaction.hxx>
#include <odb/mysql/database.hxx>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<odb::mysql::database> create_mysqldb(const mysql_settings& settings);};
}// namespace limeodb
#include "limeodb.h"namespace limeodb {std::shared_ptr<odb::mysql::database> DbFactory::create_mysqldb(const mysql_settings& settings){//1.创建数据库连接池对象std::unique_ptr<odb::mysql::connection_factory> pool(new odb::mysql::connection_pool_factory(5));//最大连接数为5//2.创建数据库操作句柄std::unique_ptr<odb::mysql::database> handler \= std::make_unique<odb::mysql::database>(settings.user,settings.password,settings.database, \settings.host,settings.port,nullptr,settings.charset,0,std::move(pool));//将链接池对象的管理权移交给数据库对象return handler;}
}//namespace limeodb
想要测试封装成果的与redis++哪里一样的方法即可,这里不再给出测试封装样例。
