C++数据库连接库调研

一、调研范围

调研了几个c++数据库连接库:

  1. CppDB (http://cppcms.com/sql/cppdb/)
  2. sqlpp11 (https://github.com/rbock/sqlpp11)
  3. odb (https://www.codesynthesis.com/products/odb/)
  4. ormpp (https://github.com/qicosmos/ormpp)

二、比较结果

CppDB

sqlpp11

odb

ormpp

是否为orm

支持原生sql语句

C++版本

C++ 03

C++ 11

C++ 03

C++ 17

额外编译工具

三、比较依据

  1. ORM解释:

对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。

在c++语境中,ORM就是指将数据库表的记录和c++中类对象联系起来,数据库的表对应c++中的类。

     2.为什么只考察这四个库?

CppDB是典型的非ORM库,单相较于mysql等原生API,它封装了MySQL, PostgreSQL, Sqlite3的api,提供统一的访问接口,使用简便。

ODB是传统的c++ ORM库,sqlpp11是c++11时期的ORM库,ormpp是c++17时期的ORM库,使用最新的c++ 17 feature.

    3.C++的ORM和Java等语言的有何不同?

Java是动态语言,支持反射功能,在运行时可以将类的字段名取出,从而可以映射到数据库表的字段名。但是c++在运行时无法取出类的字段名,因为早在编译阶段即已经将消除了名称等信息。如何将类的字段名同数据库表的字段名映射起来是个关键问题。ODB的思路是在c++类声明时添加特殊注释,再使用工具将c++源码转化,生成新的代码。其中工具识别源码中的特殊注释,自动生成对应字段名的数据库操作代码。Sqlpp11类似,不过是根据数据库表的建表语句,使用工具自动生成c++代码。Ormpp使用了c++ 17的最新feature,编译期反射,即在编译阶段可以知道类字段的字符串名(没有在运行时,以免产生额外的性能负担),这样利用宏就可以在编译期将类自动字符串名注册,生成对应的数据库操作代码,所以Ormpp不需要借助额外的中间工具。

   4.支持原生sql语句重要吗?

在我看来,不支持原生sql语句的orm框架都属于重度设计,过分强调orm概念。实际使用不现实,因为很多复杂的查询操作难以用它们自创的那套蹩脚的查询语言实现。

下面看一下这几个库的使用代码来感受一下

典型的ORM例子如下:

Ormpp使用demo

#include "dbng.hpp"

using namespace ormpp;


struct person
{
        int id;
        std::string name;
        int age;

};

REFLECTION(person, id, name, age)

int main()

{
        person p = {1, "test1", 2};
        person p1 = {2, "test2", 3};
        person p2 = {3, "test3", 4};

        std::vector<person> v{p1, p2};

        dbng<mysql> mysql;

        mysql.connect("127.0.0.1", "dbuser", "yourpwd", "testdb");

        mysql.create_datatable<person>();


        mysql.insert(p);
        mysql.insert(v);

        mysql.update(p);
        mysql.update(v);

        auto result = mysql.query<person>(); //vector<person>

        for(auto& person : result){
                 std::cout<<person.id<<" "<<person.name<<" "<<person.age<<std::endl;
        }

        mysql.delete_records<person>();

        //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();

}

典型的非ORM使用例子,CppDB使用demo:

//                                                                            
//  Copyright (C) 2010-2011  Artyom Beilis (Tonkikh) <artyomtnk@yahoo.com>    
//                                                                            
//  Distributed under:
//
//                   the Boost Software License, Version 1.0.
//              (See accompanying file LICENSE_1_0.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt)
//
//  or (at your opinion) under:
//
//                               The MIT License
//                 (See accompanying file MIT.txt or a copy at
//              http://www.opensource.org/licenses/mit-license.php)
//

#include <cppdb/frontend.h>
#include <iostream>
#include <ctime>

int main()
{
        try {
                cppdb::session sql("sqlite3:db=db.db");               
                sql << "DROP TABLE IF EXISTS test" << cppdb::exec;

                sql<<   "CREATE TABLE test ( "
                        "   id   INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "
                        "   n    INTEGER,"
                        "   f    REAL, "
                        "   t    TIMESTAMP,"
                        "   name TEXT "
                        ")  " << cppdb::exec;
               
                std::time_t now_time = std::time(0);

                std::tm now = *std::localtime(&now_time);
                cppdb::statement stat;
            
                stat = sql <<
                        "INSERT INTO test(n,f,t,name) "
                        "VALUES(?,?,?,?)"
                        << 10 << 3.1415926565 << now <<"Hello 'World'";

                stat.exec();
                std::cout<<"ID: "<<stat.last_insert_id() << std::endl;
                std::cout<<"Affected rows "<<stat.affected()<<std::endl;            

                stat.reset();

                stat.bind(20);
                stat.bind_null();
                stat.bind(now);
                stat.bind("Hello 'World'");
                stat.exec();
                cppdb::result res = sql << "SELECT id,n,f,t,name FROM test";

                while(res.next()) {
                        double f=-1;
                        int id,k;
                        std::tm atime;
                        std::string name;
                        res >> id >> k >> f >> atime >> name;
                        std::cout <<id << ' '<<k <<' '<<f<<' '<<name<<' '<<asctime(&atime)<< std::endl;
                }

                res = sql << "SELECT n,f FROM test WHERE id=?" << 1 << cppdb::row;
                if(!res.empty()) {
                        int n = res.get<int>("n");
                        double f=res.get<double>(1);
                        std::cout << "The values are " << n <<" " << f << std::endl;
                }
        }

        catch(std::exception const &e) {
                std::cerr << "ERROR: " << e.what() << std::endl;
                return 1;
        }

        return 0;

}

通过这两个例子的对比可以看出,ORM能够大大简化对数据库的操作,包括基本的查找、插入、删除、更新等操作。

看一个sqlpp11的例子:

数据库表建表语句为:

CREATE TABLE foo (
    id bigint,
    name varchar(50),
    hasFun bool
);
 

常见操作如下:

TabFoo foo;
Db db(/* some arguments*/);

 
// selecting zero or more results, iterating over the results
for (const auto& row : db(select(foo.name, foo.hasFun).from(foo).where(foo.id > 17 and foo.name.like("%bar%"))))
{
    if (row.name.is_null())
        std::cerr << "name is null, will convert to empty string" << std::endl;
    std::string name = row.name;   // string-like fields are implicitly convertible to string
    bool hasFun = row.hasFun;          // bool fields are implicitly convertible to bool
}

 
// selecting ALL columns of a table
for (const auto& row : db(select(all_of(foo)).from(foo).where(foo.hasFun or foo.name == "joker")))
{
    int64_t id = row.id; // numeric fields are implicitly convertible to numeric c++ types
}

这些”.from”、“.where”等限制条件函数极其丑陋,不如原生sql语句。因为原生sql语句是通用的,极具灵活性。这些orm自带的限制条件函数都是用处极小的DSL。

四、最终选型:

对于没有历史负担的新项目而言,使用Ormpp是最好的选择。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐