English | 中文
iguana版本1.0.9 https://github.com/qicosmos/iguana.git
谁在用ormpp, 也希望ormpp用户帮助编辑用户列表,也是为了让更多用户把ormpp用起来,也是对ormpp 最大的支持,用户列表的用户问题会优先处理。
ormpp最重要的目标就是让c++中的数据库编程变得简单,为用户提供统一的接口,支持多种数据库,降低用户使用数据库的难度。
ormpp是modern c++(c++11/14/17/20)开发的ORM库,目前支持了三种数据库:mysql, postgresql和sqlite,ormpp主要有以下几个特点:
- header only — 无需编译,直接包含头文件即可使用
- cross platform — 支持 Linux、macOS、Windows
- unified interface — 统一的 API,切换数据库只需改模板参数
- easy to use — 基于编译期反射,自动完成对象到数据表的映射
- easy to change database — 从 MySQL 切换到 PostgreSQL/SQLite 只需修改数据库类型
- 安全的链式调用 — 类型安全的 SQL 构建器,编译期检查字段类型
- 连接池 — 内置数据库连接池,支持自动回收和健康检查
- 异步 MySQL — 支持基于 ASIO/async_simple 的异步非阻塞查询
- AOP 切面 — 支持日志、校验等切面编程,通过
warper_connect接入 - SQLCipher 加密 — SQLite 支持 SQLCipher 加密存储
使用REGISTER_AUTO_KEY注册自增主键
struct person {
int id;
std::string name;
int age;
};
REGISTER_AUTO_KEY(person, id)使用REGISTER_CONFLICT_KEY注册冲突主键来进行update,如果未注册冲突主键则会采用自增主键
struct student {
int code;
std::string name;
char sex;
int age;
double dm;
std::string classroom;
};
REGISTER_CONFLICT_KEY(student, code)auto l = sqlite.select(all).from<test_optional>().collect();
auto l1 =
sqlite.select(col(&test_optional::id), col(&test_optional::name))
.from<test_optional>()
.collect();
auto l2 = sqlite.select(all)
.from<test_optional>()
.where(col(&test_optional::id).in(1, 2))
.order_by(col(&test_optional::id).desc(),
col(&test_optional::name).desc())
.limit(5)
.offset(0)
.collect();// 调用param() 意味着它是一个占位符'?', 调用collect(2) 意味着绑定对应的参数
auto l0 = sqlite.select(all)
.from<test_optional>()
.where(col(&test_optional::id).param())
.collect(2);
auto l = sqlite.select(all)
.from<test_optional>()
.where(col(&test_optional::name).param())
.collect(std::string("test"));
CHECK(l0.size() == 1);
CHECK(l.size() == 1);auto l = sqlite.select(count()).from<test_optional>().collect();
auto l2 = sqlite.select(count(col(&test_optional::id)))
.from<test_optional>()
.collect();
auto l3 = sqlite.select(count_distinct(col(&test_optional::id)))
.from<test_optional>()
.collect();
CHECK(l == 2);
CHECK(l2 == 2);
CHECK(l3 == 2);
auto l4 = sqlite.select(sum(col(&test_optional::id)))
.from<test_optional>()
.collect();
auto l5 = sqlite.select(avg(col(&test_optional::id)))
.from<test_optional>()
.collect();
auto l6 = sqlite.select(min(col(&test_optional::id)))
.from<test_optional>()
.collect();
auto l7 = sqlite.select(max(col(&test_optional::id)))
.from<test_optional>()
.collect();auto l =
sqlite.select(count(col(&test_optional::id)), col(&test_optional::id))
.from<test_optional>()
.group_by(col(&test_optional::id))
.collect();
auto l1 =
sqlite.select(sum(col(&test_optional::id)), col(&test_optional::id))
.from<test_optional>()
.group_by(col(&test_optional::id))
.collect();
auto l2 =
sqlite.select(sum(col(&test_optional::id)), col(&test_optional::id))
.from<test_optional>()
.group_by(col(&test_optional::id))
.collect();
auto l3 =
sqlite.select(sum(col(&test_optional::id)), col(&test_optional::id))
.from<test_optional>()
.where(col(&test_optional::id) > 0)
.group_by(col(&test_optional::id))
.collect();
auto l4 =
sqlite.select(sum(col(&test_optional::age)), col(&test_optional::id))
.from<test_optional>()
.where(col(&test_optional::id) > 0)
.group_by(col(&test_optional::id))
.having(sum(col(&test_optional::age)) > 0 && count() > 0)
.collect();auto l2 = sqlite.select(col(&test_optional::name), col(&person::name))
.from<test_optional>()
.inner_join(col(&test_optional::id), col(&person::id))
.where(col(&person::id) > 0 || col(&person::id) == 1)
.collect();
/*
std::string sql =
"select a.title, a.content, u.user_name as author_name, t.name as "
"tag_name, a.created_at, a.updated_at, a.views_count, a.comments_count "
"from articles a INNER JOIN users u ON a.author_id = u.id INNER JOIN "
"tags t ON a.tag_id = t.tag_id WHERE a.slug = ? and a.is_deleted=0;";
*/
auto results =
conn->select(col(&articles_t::title), col(&articles_t::content),
col(&users_t::user_name), col(&tags_t::name),
col(&articles_t::created_at), col(&articles_t::updated_at),
col(&articles_t::views_count),
col(&articles_t::comments_count))
.from<articles_t>()
.inner_join(col(&articles_t::author_id), col(&users_t::id))
.inner_join(col(&articles_t::tag_id), col(&tags_t::tag_id))
.where(col(&articles_t::slug) == slug &&
col(&articles_t::is_deleted) == 0).collect();这个例子展示如何使用ormpp实现数据库的增删改查之类的操作,无需写sql语句。
#include "dbng.hpp"
#include "mysql.hpp"//注意,使用什么数据库时就需要include对应的hpp文件,里面是对相关函数的反射封装
//#include "sqlite.hpp" //例如使用sqlite时,则包含sqlite.hpp
using namespace ormpp;
struct person {
int id;
std::string name;
std::optional<int> age; // 可以插入null值
static constexpr auto get_alias_field_names(person *) {
return std::array{ylt::reflection::field_alias_t{"person_id", 0},
ylt::reflection::field_alias_t{"person_name", 1},
ylt::reflection::field_alias_t{"person_age", 2}}; // 注意: 这里需与YLT_REFL的注册顺序一致
}
static constexpr std::string_view get_alias_struct_name(person *) {
return "CUSTOM_TABLE_NAME"; // 表名默认结构体名字(person), 这里可以修改表名
}
};
REGISTER_AUTO_KEY(person, id)
REGISTER_CONFLICT_KEY(person, name)
// REGISTER_CONFLICT_KEY(person, name, age) // 如果是多个
int main() {
person p = {0, "test1", 2};
person p1 = {0, "test2", 3};
person p2 = {0, "test3", 4};
std::vector<person> v{p1, p2};
dbng<mysql> mysql;
mysql.connect("127.0.0.1", "dbuser", "yourpwd", "testdb");
mysql.create_datatable<person>(ormpp_auto_key{"id"});
// 插入数据
mysql.insert(p);
mysql.insert(v);
// 查询数据(id=1)
auto one_person = mysql.query_s<person>("id=?", 1);
// 获取插入后的自增id
person p3 = {0, "test4", 4};
person p4 = {0, "test5", 5};
person p5 = {0, "test6", 6};
auto id1 = mysql.get_insert_id_after_insert(p3);
auto id2 = mysql.get_insert_id_after_insert(std::vector<person>{p4, p5});
// 更新数据
mysql.update(p);
mysql.update(v);
mysql.update(p, "id=1");
// 替换数据
mysql.replace(p);
mysql.replace(v);
// 更新指定字段
// mysql.update_some<&person::name, &person::age>(p);
// mysql.update_some<&person::name, &person::age>(v);
auto persons = mysql.query_s<person>();
for (auto &item : persons) {
std::cout << item.id << " " << item.name << " ";
if (item.age.has_value()) {
std::cout << *item.age;
}
else {
std::cout << "NULL";
}
std::cout << std::endl;
}
mysql.delete_records<person>();
// transaction
mysql.begin();
for (int i = 0; i < 10; ++i) {
person s = {0, "tom", 19};
if (!mysql.insert(s)) {
mysql.rollback();
return -1;
}
}
mysql.commit();
return 0;
}enum class Color { BLUE = 10, RED = 15 };
enum Fruit { APPLE, BANANA };
struct test_enum_t {
Color color;
Fruit fruit;
int id;
};
REGISTER_AUTO_KEY(test_enum_t, id)
int main() {
dbng<sqlite> sqlite;
sqlite.connect(db);//或者开启sqcipher后sqlite.connect(db, password);
sqlite.execute("drop table if exists test_enum_t");
sqlite.create_datatable<test_enum_t>(ormpp_auto_key{"id"});
sqlite.insert<test_enum_t>({Color::BLUE});
auto vec1 = sqlite.query<test_enum_t>();
vec1.front().color = Color::RED;
sqlite.update(vec1.front());
auto vec2 = sqlite.query<test_enum_t>();
sqlite.update<test_enum_t>({Color::BLUE, BANANA, 1}, "id=1");
auto vec3 = sqlite.query<test_enum_t>();
vec3.front().color = Color::RED;
sqlite.replace(vec3.front());
auto vec4 = sqlite.query<test_enum_t>();
sqlite.delete_records<test_enum_t>();
auto vec5 = sqlite.query<test_enum_t>();
return 0;
}新增接口:
- create_table()
- update()
- remove()
- alter_table()
例子:
struct builder_person {
std::string name;
int age;
int id;
int score;
};
sqlite.create_table<builder_person>()
.auto_increment(col(&builder_person::id))
.not_null(col(&builder_person::name), col(&builder_person::age))
.default_value(col(&builder_person::score), 0)
.execute();
CHECK(sqlite.update<builder_person>()
.set(col(&builder_person::name), "jerry")
.set(col(&builder_person::age), 20)
.where(col(&builder_person::id) == 1)
.execute() == 1);
CHECK(sqlite.update<builder_person>()
.set(col(&builder_person::score), 0)
.where(col(&builder_person::id) == 1)
.execute() == 1);
CHECK(sqlite.alter_table<builder_person>()
.add_index("idx_builder_person_name", col(&builder_person::name))
.drop_index("idx_builder_person_name")
.execute());
CHECK(sqlite.remove<builder_person>()
.where(col(&builder_person::id) == 1)
.execute() == 1);范围分区通过统一的链式 API 表达,ormpp 会根据数据库类型生成不同 SQL:
- MySQL:使用原生
PARTITION BY RANGE COLUMNS、DELETE ... PARTITION、ALTER TABLE ... TRUNCATE/DROP PARTITION - PostgreSQL:使用父表
PARTITION BY RANGE和子分区表 - SQLite:没有原生分区,使用分区字段范围条件模拟删除,并为分区字段创建索引
例子:
struct order_log {
int id;
int bucket;
std::string payload;
};
auto p202405 = ormpp::range_partition("p202405", 202405, 202406);
auto p202406 = ormpp::range_partition("p202406", 202406, 202407);
db.create_table<order_log>()
.primary_key(col(&order_log::id), col(&order_log::bucket))
.partition_by_range(col(&order_log::bucket))
.partition(p202405)
.partition(p202406)
.execute();
// 删除一个逻辑分区中的数据
db.remove<order_log>()
.partition(col(&order_log::bucket), p202405)
.execute_all();
// 清空一个逻辑分区
db.alter_table<order_log>()
.clear_partition(col(&order_log::bucket), p202406)
.execute();
// 维护分区结构。SQLite 没有原生分区,drop_partition 会失败返回 false。
auto p202407 = ormpp::range_partition("p202407", 202407, 202408);
db.alter_table<order_log>()
.add_partition(col(&order_log::bucket), p202407)
.drop_partition(p202407)
.execute();注意:分区名会按 SQL 标识符校验,只允许字母、数字和下划线,且不能以数字开头。MySQL 分区表的主键/唯一键需要满足 MySQL 自身限制,通常应包含分区字段。
支持的选项如下: 1. ENABLE_SQLITE3 2. ENABLE_MYSQL 3. ENABLE_PG
cmake -B build -DENABLE_SQLITE3=ON -DCMAKE_BUILD_TYPE=Debug cmake --build build --config Debug
mysql
set(ENABLE_MYSQL ON)
add_definitions(-DORMPP_ENABLE_MYSQL)
add_subdirectory(ormpp)或者
set(ENABLE_MYSQL ON)
add_definitions(-DORMPP_ENABLE_MYSQL)
add_library(ormpp INTERFACE)
include(ormpp/cmake/mysql.cmake)
target_link_libraries(ormpp INTERFACE ${MYSQL_LIBRARY})
target_include_directories(ormpp INTERFACE ormpp ormpp/ormpp ${MYSQL_INCLUDE_DIR})sqlite
set(ENABLE_SQLITE3 ON)
add_definitions(-DORMPP_ENABLE_SQLITE3)
add_subdirectory(ormpp)或者
set(ENABLE_SQLITE3 ON)
add_definitions(-DORMPP_ENABLE_SQLITE3)
add_subdirectory(ormpp/thirdparty)
add_library(ormpp INTERFACE)
target_link_libraries(ormpp INTERFACE sqlite3)
target_include_directories(ormpp INTERFACE ormpp ormpp/ormpp ormpp/thirdparty/sqlite3)如果需要开启sqlcipher
因为sqlcipher需要链接到OpenSSL库,需要在本地先安装OpenSSL,然后添加下述cmake语句
set(ENABLE_SQLITE3 ON)
set(ENABLE_SQLITE3_CODEC)#开启sqlcipher需要同时开启ENABLE_SQLITE3和ENABLE_SQLITE3_CODEC
add_definitions(
-DORMPP_ENABLE_SQLITE3
-DSQLITE_HAS_CODEC
)
add_subdirectory(ormpp)或者
set(ENABLE_SQLITE3 ON)
set(ENABLE_SQLITE3_CODEC)#开启sqlcipher需要同时开启ENABLE_SQLITE3和ENABLE_SQLITE3_CODEC
add_definitions(
-DORMPP_ENABLE_SQLITE3
-DSQLITE_HAS_CODEC
)
add_subdirectory(ormpp/thirdparty)
add_library(ormpp INTERFACE)
target_link_libraries(ormpp INTERFACE sqlite3)
target_include_directories(ormpp INTERFACE ormpp ormpp/ormpp ormpp/thirdparty/sqlite3)注意:最终发布的应用程序依赖于 OpenSSL 的动态库,
- Windows 下通常为
libssl-3-x64.dll和libcrypto-3-x64.dll;
请将这两个.dll拷贝到可执行文件所在目录。 - Linux下通常已安装在
/usr/lib或/usr/local/lib,不需要拷贝;
如果你使用自编译的 OpenSSL 或者希望随同可执行文件一起分发,也可以将libssl.so与libcrypto.so放在执行目录,并通过LD_LIBRARY_PATH或者-Wl,-rpath指定查找路径。 - macOS 下通常为
libssl.dylib和libcrypto.dylib; 请将这两个.dylib拷贝到可执行文件所在目录,或通过DYLD_LIBRARY_PATH、Mach-O@rpath设置查找路径。
pg
set(ENABLE_PG ON)
add_definitions(-DORMPP_ENABLE_PG)
add_subdirectory(ormpp)或者
set(ENABLE_PG ON)
add_definitions(-DORMPP_ENABLE_PG)
add_library(ormpp INTERFACE)
include(ormpp/cmake/pgsql.cmake)
target_link_libraries(ormpp INTERFACE ${PGSQL_LIBRARY})
target_include_directories(ormpp INTERFACE ormpp ormpp/ormpp ${PGSQL_INCLUDE_DIR})需要支持C++17的编译器, 要求的编译器版本:linux gcc7.2, clang4.0; windows >vs2017 update5
因为ormpp支持mysql和postgresql,所以需要安装mysql,postgresql,postgresql官方提供的libpq,安装之后,在CMakeLists.txt配置目录和库路径(默认安装不需要)。
ormpp屏蔽了不同数据库操作接口的差异,提供了统一简单的数据库操作接口,具体提供了数据库连接、断开连接、创建数据表、插入数据、更新数据、删除数据、查询数据和事务相关的接口。
//连接数据库
template <typename... Args>
bool connect(Args&&... args);
//断开数据库连接
bool disconnect();
//创建数据表
template<typename T, typename... Args>
bool create_datatable(Args&&... args);
//插入单条数据
template<typename T, typename... Args>
int insert(const T& t, Args&&... args);
//插入多条数据
template<typename T, typename... Args>
int insert(const std::vector<T>& t, Args&&... args);
//替换单条数据
template <typename T, typename... Args>
int replace(const T &t, Args &&...args);
//替换多条数据
template <typename T, typename... Args>
int replace(const std::vector<T> &v, Args &&...args);
//更新单条数据
template<typename T, typename... Args>
int update(const T& t, Args&&... args);
//更新多条数据
template<typename T, typename... Args>
int update(const std::vector<T>& t, Args&&... args);
//更新单条数据(指定字段)
template <auto... members, typename T, typename... Args>
int update_some(const T &t, Args &&...args);
//更新多条数据(指定字段)
template <auto... members, typename T, typename... Args>
int update_some(const std::vector<T> &v, Args &&...args);
//获取插入后的自增id
template <typename T, typename... Args>
uint64_t get_insert_id_after_insert(const T &t, Args &&...args);
//删除数据(带预处理)
template <typename T, typename... Args>
int delete_records_s(const std::string &str = "", Args &&...args);
//查询数据,包括单表查询和多表查询(带预处理)
template <typename T, typename... Args>
std::vector<T> query_s(const std::string &str = "", Args &&...args);
//删除数据(不带预处理)
template <typename T, typename... Args>
[[deprecated]] bool delete_records(Args &&...where_condition)
//查询数据,包括单表查询和多表查询(不带预处理)
template <typename T, typename... Args>
[[deprecated]] std::vector<T> query(Args &&...args);
//执行原生的sql语句
int execute(const std::string& sql);
//开始事务
bool begin();
//提交事务
bool commit();
//回滚
bool rollback();先在entity.hpp中定义业务实体(和数据库的表对应),接着定义数据库对象:
#include "dbng.hpp"
//#include "mysql.hpp"...等等,别忘记了
using namespace ormpp;
struct person {
int id;
std::string name;
std::optional<int> age; // 插入null值
static constexpr std::string_view get_alias_struct_name(person *) {
return "CUSTOM_TABLE_NAME";
}
};
REGISTER_AUTO_KEY(person, id)
int main(){
dbng<mysql> mysql;
dbng<sqlite> sqlite;
dbng<postgresql> postgres;
//......
}- 连接数据库
template <typename... Args>
bool connect(Args&&... args);connect exmple:
// mysql.connect(host, dbuser, pwd, dbname);
mysql.connect("127.0.0.1", "root", "12345", "testdb");
// mysql.connect(host, dbuser, pwd, dbname, timeout, port);
mysql.connect("127.0.0.1", "root", "12345", "testdb", 5, 3306);
postgres.connect("127.0.0.1", "root", "12345", "testdb");
sqlite.connect("127.0.0.1", "testdb");//或直接sqlite.connect("testdb");
//开启sqlcipher后
sqlite.connect("127.0.0.1", "root", "12345", "testdb");//或者直接sqlite.connect("testdb", "123456");返回值:bool,成功返回true,失败返回false.
- 断开数据库连接
bool disconnect();disconnect exmple:
mysql.disconnect();
postgres.disconnect();
sqlite.disconnect();注意:用户可以不用显式调用,在数据库对象析构时会自动调用disconnect接口。
返回值:bool,成功返回true,失败返回false.
- 创建数据表
template<typename T, typename... Args>
bool create_datatable(Args&&... args);create_datatable example:
//创建不含主键的表
mysql.create_datatable<student>();
postgres.create_datatable<student>();
sqlite.create_datatable<student>();
//创建含主键和not null属性的表
ormpp_key key1{"id"};
ormpp_not_null not_null{{"id", "age"}};
person p = {1, "test1", 2};
person p1 = {2, "test2", 3};
person p2 = {3, "test3", 4};
mysql.create_datatable<person>(key1, not_null);
postgres.create_datatable<person>(key1, not_null);
sqlite.create_datatable<person>(key1);注意:目前只支持了key、unique和not null属性。
mysql.create_datatable<person>(ormpp_unique{{"name"}});
当在mysql中使用由unique声明的std::string成员创建表时,
由于"BLOB/TEXT column 'NAME' used in key specification without a key length",
故在创建表时,如果是由unique声明的std::string成员对应的数据类型则为VARCHAR(512),否则则为TEXT
返回值:bool,成功返回true,失败返回false.
- 插入单条数据
template<typename T, typename... Args>
int insert(const T& t, Args&&... args);insert example:
person p = {1, "test1", 2};
TEST_CHECK(mysql.insert(p)==1);
TEST_CHECK(postgres.insert(p)==1);
TEST_CHECK(sqlite.insert(p)==1);
// age为null
person p = {1, "test1", {}};
TEST_CHECK(mysql.insert(p)==1);
TEST_CHECK(postgres.insert(p)==1);
TEST_CHECK(sqlite.insert(p)==1);返回值:int,成功返回插入数据的条数1,失败返回INT_MIN.
- 插入多条数据
template<typename T, typename... Args>
int insert(const std::vector<T>& t, Args&&... args);multiple insert example:
person p = {1, "test1", 2};
person p1 = {2, "test2", 3};
person p2 = {3, "test3", 4};
std::vector<person> v1{p, p1, p2};
TEST_CHECK(mysql.insert(v1)==3);
TEST_CHECK(postgres.insert(v1)==3);
TEST_CHECK(sqlite.insert(v1)==3);返回值:int,成功返回插入数据的条数N,失败返回INT_MIN.
- 更新单条数据
template<typename T, typename... Args>
int update(const T& t, Args&&... args);update example:
person p = {1, "test1", 2};
TEST_CHECK(mysql.update(p)==1);
TEST_CHECK(postgres.update(p)==1);
TEST_CHECK(sqlite.update(p)==1);注意:更新会根据表的key字段去更新,如果表没有key字段的时候,需要指定一个更新依据字段名,比如
TEST_CHECK(mysql.update(p, "age")==1);
TEST_CHECK(postgres.update(p, "age")==1);
TEST_CHECK(sqlite.update(p, "age")==1);返回值:int,成功返回更新数据的条数1,失败返回INT_MIN.
- 更新多条数据
template<typename T, typename... Args>
int update(const std::vector<T>& t, Args&&... args);multiple insert example:
person p = {1, "test1", 2};
person p1 = {2, "test2", 3};
person p2 = {3, "test3", 4};
std::vector<person> v1{p, p1, p2};
TEST_CHECK(mysql.update(v1)==3);
TEST_CHECK(postgres.update(v1)==3);
TEST_CHECK(sqlite.update(v1)==3);注意:更新会根据表的key字段去更新,如果表没有key字段的时候,需要指定一个更新依据字段名,用法同上。
返回值:int,成功返回更新数据的条数N,失败返回INT_MIN.
- 替换数据(INSERT OR REPLACE / UPSERT)
template <typename T, typename... Args>
int replace(const T &t, Args &&...args);
template <typename T, typename... Args>
int replace(const std::vector<T> &v, Args &&...args);replace example:
person p = {1, "test1", 2};
TEST_CHECK(mysql.replace(p)==1);
TEST_CHECK(postgres.replace(p)==1);
TEST_CHECK(sqlite.replace(p)==1);
// 批量替换
std::vector<person> v{p1, p2};
TEST_CHECK(mysql.replace(v)==2);注意:replace 的行为在不同数据库间有差异:
- MySQL / SQLite:使用
REPLACE INTO— 先删除旧记录,再插入新记录 - PostgreSQL:使用
INSERT ... ON CONFLICT ... DO UPDATE— 遇到冲突时更新原有记录,不会删除
因此,PostgreSQL 下的 replace 不会触发删除相关的触发器,自增 ID 也不会重置。建议根据业务场景选择合适的数据库。
返回值:int,成功返回替换数据的条数,失败返回INT_MIN.
- 更新指定字段
template <auto... members, typename T, typename... Args>
int update_some(const T &t, Args &&...args);
template <auto... members, typename T, typename... Args>
int update_some(const std::vector<T> &v, Args &&...args);update_some example:
person p = {1, "new_name", 25};
// 只更新 name 和 age 字段,不碰其他字段
TEST_CHECK(mysql.update_some<&person::name, &person::age>(p)==1);
TEST_CHECK(postgres.update_some<&person::name, &person::age>(p)==1);
TEST_CHECK(sqlite.update_some<&person::name, &person::age>(p)==1);注意:update_some 只会更新指定的成员字段,适合部分字段更新的场景,避免不必要的数据传输。
返回值:int,成功返回更新数据的条数,失败返回INT_MIN.
- 获取插入后的自增ID
template <typename T, typename... Args>
uint64_t get_insert_id_after_insert(const T &t, Args &&...args);get_insert_id_after_insert example:
person p = {0, "test1", 2}; // id=0 表示让数据库自增
// get_insert_id_after_insert 内部会执行 insert 并返回自增 ID
auto id = mysql.get_insert_id_after_insert(p);
std::cout << "inserted id: " << id << std::endl;注意:get_insert_id_after_insert 内部会执行 insert 操作并返回自增 ID,无需在外部先调用 insert。仅对含有自增主键的表有效。
返回值:uint64_t,成功返回自增ID,失败返回0.
- 删除数据
template<typename T, typename... Args>
int delete_records_s(const std::string &str = "", Args &&...args);delete_records_s example:
//删除所有数据
TEST_REQUIRE(mysql.delete_records_s<person>());
TEST_REQUIRE(postgres.delete_records_s<person>());
TEST_REQUIRE(sqlite.delete_records_s<person>());
//根据条件删除数据
TEST_REQUIRE(mysql.delete_records_s<person>("id=?", 1));
TEST_REQUIRE(postgres.delete_records_s<person>("id=$1", 1));
TEST_REQUIRE(sqlite.delete_records_s<person>("id=?", 1));返回值:bool,成功返回true,失败返回false.
- 查询数据
template<typename T, typename... Args>
std::vector<T> query_s(const std::string &str = "", Args &&...args);query_s example:
auto result = mysql.query_s<person>();
TEST_CHECK(result.size()==3);
auto result1 = postgres.query_s<person>();
TEST_CHECK(result1.size()==3);
auto result2 = sqlite.query_s<person>();
TEST_CHECK(result2.size()==3);
//可以根据条件查询
auto result3 = mysql.query_s<person>("id=?", 1);
TEST_CHECK(result3.size()==1);
auto result4 = postgres.query_s<person>("id=$1", 2);
TEST_CHECK(result4.size()==1);
auto result5 = sqlite.query_s<person>("id=?", 3);返回值:std::vector,成功vector不为空,失败则为空.
- 特定列查询
template<typename T, typename... Args>
std::vector<T> query_s(const std::string &str = "", Args &&...args);some fields query_s example:
auto result = mysql.query_s<std::tuple<int, std::string, int>>("select code, name, dm from person");
TEST_CHECK(result.size()==3);
auto result1 = postgres.query_s<std::tuple<int, std::string, int>>("select code, name, dm from person");
TEST_CHECK(result1.size()==3);
auto result2 = sqlite.query_s<std::tuple<int, std::string, int>>("select code, name, dm from person");
TEST_CHECK(result2.size()==3);
auto result3 = mysql.query_s<std::tuple<int>>("select count(1) from person");
TEST_CHECK(result3.size()==1);
TEST_CHECK(std::get<0>(result3[0])==3);
auto result4 = postgres.query_s<std::tuple<int>>("select count(1) from person");
TEST_CHECK(result4.size()==1);
TEST_CHECK(std::get<0>(result4[0])==3);
auto result5 = sqlite.query_s<std::tuple<int>>("select count(1) from person");
TEST_CHECK(result5.size()==1);
TEST_CHECK(std::get<0>(result5[0])==3);返回值:std::vector<std::tuple>,成功vector不为空,失败则为空.
- 执行原生sql语句
int execute(const std::string& sql);execute example:
r = mysql.execute("drop table if exists person");
TEST_REQUIRE(r);
r = postgres.execute("drop table if exists person");
TEST_REQUIRE(r);
r = sqlite.execute("drop table if exists person");
TEST_REQUIRE(r);注意:execute接口支持的原生sql语句是不带占位符的,是一条完整的sql语句。
返回值:int,成功返回更新数据的条数1,失败返回INT_MIN.
- 事务接口
开始事务,提交事务,回滚
//transaction
mysql.begin();
for (int i = 0; i < 10; ++i) {
person s = {i, "tom", 19};
if(!mysql.insert(s)){
mysql.rollback();
return -1;
}
}
mysql.commit();返回值:bool,成功返回true,失败返回false.
- 面向切面编程AOP
定义切面:
struct log{
//args...是业务逻辑函数的入参
template<typename... Args>
bool before(Args... args){
std::cout<<"log before"<<std::endl;
return true;
}
//T的类型是业务逻辑返回值,后面的参数则是业务逻辑函数的入参
template<typename T, typename... Args>
bool after(T t, Args... args){
std::cout<<"log after"<<std::endl;
return true;
}
};
struct validate{
//args...是业务逻辑函数的入参
template<typename... Args>
bool before(Args... args){
std::cout<<"validate before"<<std::endl;
return true;
}
//T的类型是业务逻辑返回值,后面的参数则是业务逻辑函数的入参
template<typename T, typename... Args>
bool after(T t, Args... args){
std::cout<<"validate after"<<std::endl;
return true;
}
};注意:切面的定义中,允许你只定义before或after,或者二者都定义。
//增加日志和校验的切面
dbng<mysql> mysql;
auto r = mysql.warper_connect<log, validate>("127.0.0.1", "root", "12345", "testdb");
TEST_REQUIRE(r);ormpp 支持 std::optional<T> 类型的字段,可优雅地处理数据库 NULL 值:
struct person {
int id;
std::string name;
std::optional<int> age; // age 可为 NULL
};
YLT_REFL(person, id, name, age);
// 插入时 age 为 null
person p1 = {1, "alice", {}}; // age = NULL
person p2 = {2, "bob", 25}; // age = 25
mysql.insert(p1);
mysql.insert(p2);
// 查询时自动还原 optional
auto result = mysql.query_s<person>();
for (auto& p : result) {
if (p.age.has_value()) {
std::cout << p.name << " age: " << *p.age << std::endl;
} else {
std::cout << p.name << " age: NULL" << std::endl;
}
}支持的可空类型:std::optional<int>, std::optional<std::string>, std::optional<double> 等。
ormpp 自动支持 C++ enum 和 enum class 与数据库整型字段的映射:
enum class Color { BLUE = 10, RED = 15 };
enum Fruit { APPLE, BANANA };
struct test_enum_t {
Color color;
Fruit fruit;
int id;
};
YLT_REFL(test_enum_t, color, fruit, id);
mysql.create_datatable<test_enum_t>(ormpp_auto_key{"id"});
mysql.insert<test_enum_t>({Color::BLUE, APPLE, 0});
auto vec = mysql.query<test_enum_t>();
// vec[0].color == Color::BLUE
// vec[0].fruit == APPLE通过静态方法自定义数据库中的字段名和表名:
struct person {
int id;
std::string name;
int age;
// 自定义字段别名(需与 YLT_REFL 注册顺序一致)
static constexpr auto get_alias_field_names(person *) {
return std::array{
ylt::reflection::field_alias_t{"person_id", 0},
ylt::reflection::field_alias_t{"person_name", 1},
ylt::reflection::field_alias_t{"person_age", 2}
};
}
// 自定义表名(默认使用结构体名 person)
static constexpr std::string_view get_alias_struct_name(person *) {
return "CUSTOM_TABLE_NAME";
}
};
YLT_REFL(person, id, name, age);ormpp 基于 iguana 的编译期反射,需要在结构体定义后使用 YLT_REFL 宏注册字段:
struct student {
int code;
std::string name;
int age;
std::optional<std::string> email;
};
// 注册所有字段(顺序决定数据库表中的列顺序)
YLT_REFL(student, code, name, age, email);
// 注册自增主键
REGISTER_AUTO_KEY(student, code);
// 注册冲突键(用于 replace / upsert)
REGISTER_CONFLICT_KEY(student, name);ormpp 自动将 C++ 类型映射为对应数据库的 SQL 类型:
| C++ 类型 | MySQL | PostgreSQL | SQLite |
|---|---|---|---|
bool |
BOOLEAN | integer | INTEGER |
char |
TINYINT | char | INTEGER |
short |
SMALLINT | smallint | INTEGER |
int |
INTEGER | integer | INTEGER |
int64_t |
BIGINT | bigint | INTEGER |
uint64_t |
BIGINT UNSIGNED | bigint | INTEGER |
float |
FLOAT | real | FLOAT |
double |
DOUBLE | double precision | DOUBLE |
std::string |
TEXT | text | TEXT |
std::string_view |
TEXT | text | TEXT |
std::array<char, N> |
VARCHAR(N) | varchar(N) | VARCHAR(N) |
blob (std::vector) |
BLOB | bytea | BLOB |
enum / enum class |
INTEGER | integer | INTEGER |
std::optional<T> |
同 T 类型 | 同 T 类型 | 同 T 类型 |
ormpp 内置了数据库连接池,支持自动创建、回收和健康检查,避免频繁创建/销毁连接带来的性能开销。
#include "connection_pool.hpp"
#include "mysql.hpp"
// 初始化连接池(单例,全局只需初始化一次)
// 参数:最大连接数, host, user, password, db, timeout, port
ormpp::connection_pool<ormpp::mysql>::instance().init(
10, "127.0.0.1", "root", "12345", "testdb", 5, 3306);
// 从连接池中获取连接(智能指针,超出作用域自动归还)
auto conn = ormpp::connection_pool<ormpp::mysql>::instance().get();
if (conn) {
conn->create_datatable<person>();
conn->insert(p);
auto result = conn->query_s<person>();
// conn 析构时自动归还到连接池
}- 自动回收:连接使用完毕后通过自定义 deleter 自动归还到池中
- 超时等待:获取连接时最多等待 3 秒,超时返回
nullptr - 健康检查:自动检测连接是否存活(
ping),失效连接会自动重建 - 空闲超时:连接空闲超过 8 小时会自动重建,避免数据库端超时断开
- 线程安全:连接池本身是线程安全的(
get()/init()内部使用std::mutex保护),但从池中获取的单个连接对象不可跨线程并发使用。如需多线程并发查询,每个线程应独立get()一个连接。
ormpp 支持基于 ASIO/async_simple 的异步 MySQL 查询,适合高并发场景。
cmake -B build -DENABLE_MYSQL_ASYNC=ON#include <asio.hpp>
#include "mysql_async.hpp"
struct person {
int id;
std::string name;
int age;
};
YLT_REFL(person, id, name, age);
asio::awaitable<void> run_mysql_async() {
ormpp::mysql_async async_mysql(co_await asio::this_coro::executor);
bool connected =
co_await async_mysql.connect("127.0.0.1", "root", "12345", "testdb");
if (!connected) {
co_return;
}
// 异步查询
auto result = co_await async_mysql.query_s<person>();
for (auto& p : result) {
std::cout << p.name << std::endl;
}
// 异步插入
person p{0, "tom", 20}; // id=0 表示自增
int affected = co_await async_mysql.insert(p);
}
int main() {
asio::io_context io;
asio::co_spawn(io, run_mysql_async(), asio::detached);
io.run();
}#include <asio.hpp>
#include "async_connection_pool.hpp"
#include "mysql_async.hpp"
asio::awaitable<void> run_pool() {
auto executor = co_await asio::this_coro::executor;
auto pool =
std::make_shared<ormpp::async_connection_pool<ormpp::mysql_async>>(
executor);
bool initialized =
co_await pool->init(10, "127.0.0.1", "root", "12345", "testdb");
if (!initialized) {
co_return;
}
auto conn = co_await pool->get();
if (conn) {
auto result = co_await conn->query_s<person>();
}
}
int main() {
asio::io_context io;
asio::co_spawn(io, run_pool(), asio::detached);
io.run();
}注意:异步接口需要 C++20 协程支持,编译器要求 GCC 10+、Clang 13+ 或 MSVC 2019 16.8+。
ormpp 底层依赖 iguana 的编译期反射。iguana 的部分反射元数据采用**懒加载(Lazy Initialization)**策略:首次查询某个实体类型时才会初始化字段映射信息。
在多线程场景下,如果多个线程同时首次查询同一类型,可能因并发初始化导致字段名映射错乱、字符串内存损坏(double-free)等问题。
| 对象 | 线程安全性 | 说明 |
|---|---|---|
connection_pool |
✅ 线程安全 | init()、get() 等操作内部使用 std::mutex 保护 |
单个 dbng / 连接对象 |
❌ 非线程安全 | 不可跨线程共享同一个连接实例 |
query / insert / update |
依赖连接对象 | 底层调用数据库 C API,本身无额外锁保护 |
推荐做法:每个工作线程通过 connection_pool::get() 独立获取连接,用完自动归还。严禁多个线程同时操作同一个连接对象。
在启动工作线程之前,于主线程中显式调用 ormpp::init_reflection<T>() 触发一次性的反射初始化:
#include "utility.hpp"
struct person {
int id;
std::string name;
int age;
};
YLT_REFL(person, id, name, age);
int main() {
// 1. 单线程预初始化(主线程)
ormpp::init_reflection<person>();
// 2. 此后可安全地在多线程中并发查询
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back([]() {
dbng<mysql> mysql;
mysql.connect("127.0.0.1", "root", "12345", "testdb");
auto result = mysql.query<person>(); // 线程安全
});
}
for (auto &t : threads) t.join();
}ormpp 内部对 SQL 字段列表缓存(get_fields<T>())已使用 std::call_once 保护,确保多线程首次并发访问时只初始化一次。但仍强烈建议在应用层做预初始化,因为 iguana 层面的反射元数据初始化不在 ormpp 控制范围内。
- ✅ 单线程顺序查询:无需额外处理
⚠️ 多线程同时首次查询同一类型:需要预初始化- ✅ 多线程查询不同类型(每个类型首次查询单线程):无需额外处理
- ❌ 多线程共享同一连接对象:不支持,必须使用连接池或每线程独立连接
- ✅ 支持组合键(已通过链式调用
primary_key(col1, col2)实现) - ✅ 多表查询谓词(
where,group_by,having,order_by,join,limit,offset已支持) - ✅ 日志支持(可通过 AOP 切面
warper_connect<log>实现) - ✅ 连接池(已实现,见连接池)
- ✅ 异步 MySQL(已实现,见异步 MySQL)
- 🔄 增加获取错误消息的公开接口
- 🔄 支持更多的数据库(如 SQL Server、Oracle 等)
- 🔄 完善事务的嵌套支持
- 🔄 增加批量插入/更新的性能优化
历史版本
| 功能 | 版本 | 说明 |
|---|---|---|
| 链式查询 | v1.0+ | select().from().where() 类型安全构建器 |
| 聚合查询 | v1.0+ | count(), sum(), avg(), min(), max() |
| JOIN | v1.0+ | inner_join(), left_join() |
| 连接池 | v1.0+ | 单例连接池,自动回收 |
| 异步 MySQL | v1.0+ | ASIO/async_simple 协程支持 |
| AOP 切面 | v1.0+ | warper_connect 日志/校验切面 |
| 范围分区 | v1.0+ | MySQL/PostgreSQL/SQLite 统一分区 API |
| 线程安全 | master | init_reflection<T>() 预初始化 + call_once 保护 |
线上讨论: 项目讨论