跳转至

数据库

数据库接口

五种数据库

IRdbCore 的数据库创建基类根据数据库类型不同,使用不同的数据库基类。我们提供了五种数据库支持,对应如下:

名称 接口
MySql IRdbMysqlDatabaseInterface
Sqlite IRdbSqliteDatabaseInterface
Postgres IRdbPostgreDatabaseInterface
SqlServer IRdbSqlServerDatabaseInterface
MariaDb IRdbMariaDbDatabaseInterface

如果用户想实现自己的数据库类型支持,那么他的命名也应该像上面的接口的命名规则一样,IRdbXXXXDabatabaseInterface, 如果你要实现一个名为Doggy的数据库,那么他的名称就应该是IRdbDoggyDatabaseInterface

IRdbXxxDatabaseInterface

在上面诸多的数据库接口中,其实大同小异。我们以 IRdbSqliteDatabaseInterface 举例。他的接口如下:

1
2
3
4
5
6
7
8
9
template<typename T, bool enabled = true>
class IRdbSqliteDatabaseInterface : public IRdbDatabaseInterface<T, IRdbSqliteDialect, enabled>
{
public:
    IRdbSqliteDatabaseInterface() = default;

public:
    virtual QSqlDatabase openDatatbase(const IRdbSource &) override;
};

在这个基类中实现了 openDatabase 这个函数。该函数传入了IRdbSource 这个参数,这个参数的获取是在更深的基类中 getSource 函数中实现,这个函数始终是纯虚函数,用户实现数据库的实例时需要重载这个函数。我们在后面还会看到这个函数。

IRdbDatabaseInterface

在上面我们看见, IRdbSqliteDatabaseInterface 继承于 IRdbDatabaseInterface,其实所有的数据库基类都继承于 IRdbDatabaseInterface, 他的实现如下:

template<typename T, typename Dialect, bool enabled = true>
class IRdbDatabaseInterface : public IRdbDatabaseWare, public ITaskWareUnit<T, IRdbCatagory>, public ISingletonUnit<T>
{
public:
    IRdbDatabaseInterface();

public:
    const IRdbDialect& getDialect() const;

public:
    virtual QString getClassName() const final;

public:
    virtual double $order() const final;
    virtual void $task() final;
};

我们在继承 IRdbDatabaseInterface时需要传入三个模板参数。一个是CRTP 的类名,就是具体数据库类的名称。 第二个参数Dialect 是当前实现数据库的方言实现,这个类处理不同数据库的语言差异,我们在之后的Dialect部分讲述。第三个参数 bool enabled是表明当前的数据库是否连接测试,如果测试不成功,则会报错。

IRdbDatabaseInterface 继承类有三个,第一个参数是 IRdbDatabaseWare,这个我们之后再说这个。第二个参数是 ITaskWareUnit ,这个类是任务注册系统,我们在类中重载的$order()$task() 两个函数则是从 ITaskWareUnit中继承得来, $order()是用于任务的排序,$task()则是用于任务的执行。 第三个参数 ISingletonUnit<T> 则是给与类型 T 一个 T& instance() 函数,所以我们引用数据库的时候,是通过meyers singleton单例使用。

IRdbDatabaseWare

他的实现如下:

class IRdbDatabaseWare
{
public:
    explicit IRdbDatabaseWare(const IRdbDialect&);
    virtual ~IRdbDatabaseWare();

public:
    ISqlQuery createQuery();

public:
    virtual IRdbSource getSource() const = 0;
    virtual QString getClassName() const = 0;
    QStringList getRdbTables() const;
    QStringList getRdbViews() const;
    virtual void dropTable(const IRdbTableInfo& table);
    virtual void dropView(const IRdbViewInfo& view);

protected:
    virtual QSqlDatabase openDatatbase(const IRdbSource&) = 0;

public:
    const IRdbDialect& m_dialect;
    QSqlDatabase m_db;
};

这个基类保存了IRdbDialect& m_dialect引用。这个是数据库语句的具体实现。

值得注意的是 ISqlQuery createQuery() 这个函数,这个函数创建了ISqlQuery对象,用户可以使用这个对象来进行数据库操作,我们在Model中进行的各种增删改查操作都是用 ISqlQuery对象。

在类里面还有一组关于表操作的内容,getRdbTables() 函数是获取所有的数据库表名称,getRdbViews()是获取所有的数据库视图名称。dropTable(const IRdbTableInfo& table) 是删除数据库表, dropView(const IRdbViewInfo& view) 是删除数据库视图。这一组是对于数据库实体的操作。

getClassName() 函数是获取当前类的名称。

getSource() 是前面提到的函数,他返回的是IRdbSouce 对象。这个是用于连接数据库的信息。

IRdbSource

IRdbSource包含了driverName,databaseName 等一系列的信息,用于连接数据库。不同的数据库有不同的使用方式。

IRdbDialect

我们可以直接看看他的实现:

class IRdbDialectWare
{
public:
    IRdbDialectWare() = default;
    virtual ~IRdbDialectWare() = default;

public:
    virtual QString databaseType() const = 0;

public:
    virtual QString createTableSql(const IRdbTableInfo& info) const; 
    virtual QString dropTableSql(const IRdbTableInfo& info) const;
    virtual QString dropViewSql(const IRdbViewInfo& info) const;

    QString countSql(const IRdbEntityInfo& info) const;
    QString countSql(const IRdbEntityInfo& info, const IRdbCondition& condition) const;

    virtual void insert(ISqlQuery &query, const IRdbTableInfo &info, void *) const;
    virtual void insert(ISqlQuery& query, const IRdbTableInfo& info, const void*) const;

    void insertAll(ISqlQuery& query, const IRdbTableInfo& info, QVector<const void*>) const;


    QString findOneSql(const IRdbEntityInfo& info, const IRdbCondition& condition) const;
    QString findAllSql(const IRdbEntityInfo& info) const;
    QString findAllSql(const IRdbEntityInfo &info, const IRdbCondition& condition) const;
    QString findColumnSql(const IRdbEntityInfo& info, const QStringList& columns) const;
    QString findColumnSql(const IRdbEntityInfo& info, const QStringList& columns, const IRdbCondition&) const;

    QString existSql(const IRdbEntityInfo&, const IRdbCondition&) const;

    QString updateOne(const IRdbTableInfo& info, const QStringList& columns) const;
    QString updateWhere(const IRdbTableInfo& info, const QVariantMap& map, const IRdbCondition& condition) const;

    QString deleteTableSql(const IRdbEntityInfo& info) const;
    QString deleteTableSql(const IRdbEntityInfo& info, const IRdbCondition& condition) const;
    QString truncateTableSql(const IRdbEntityInfo& info) const;

    virtual QString getSqlType(const IRdbTableInfo& info, int index) const = 0;

public:
    QString conditionToSql(const IRdbCondition&) const;

protected:
    QString toWhereSql(const IRdbCondition&) const;
    QString toOrderBySql(const IRdbCondition&) const;
    QString toGroupBySql(const IRdbCondition&) const;
    QString toHavingSql(const IRdbCondition&) const;
    QString toLimitSql(const IRdbCondition&) const;

    QString fromWhereClause(const IRdbWhereClause&) const;
    QString fromOrderByClause(const IRdbOrderByClause&) const;
    virtual QString fromLimitClause(const IRdbLimitClause&) const;
    QString fromGroupByClause(const IRdbGroupByClause&) const;
    QString fromHavingClause(const IRdbHavingClause&) const;
    virtual QString createSqlCommonKeyClause(const IRdbTableInfo& info, int index) const;

public:
    virtual void bindParameter(QSqlQuery& query, const QString& field, const QVariant& value) const;
    virtual QString quoteName(const QString& name) const;

protected:
    static QString getVividName(const QString&);
};

上面的代码可以看见,他会生成一系列的sql语句。这是因为不同的数据库之间的SQL语法规则大致相同,但小部分不同,所以对于相同的部分,我们写在基类中,对于不同的部分,我们使用虚函数进行重载。

不同数据库的区别

Sqlite

他的创建方式如下:

// SqliteDb.h
#pragma once

#include "rdb/database/IRdbSqliteDatabaseInterface.h"

class SqliteDb : public IRdbSqliteDatabaseInterface<SqliteDb>
{
public:
    SqliteDb() = default;

public:
    virtual IRdbSource getSource() const final;
};

// SqliteDb.cpp
#include "SqliteDb.h"

IRdbSource SqliteDb::getSource() const
{
    IRdbSource source;
    source.databaseName = "abcde.db";
    source.driverName = "QSQLITE";
    return source;
}

该创建的类会在程序根目录下生成 abcde.db的数据库文件。

MySql

该数据库支持在 windows下没有默认支持。用户需要手动编译动态链接库文件进行支持。关于如何编译请自行搜索相关教程。

为了方便,以下的数据库完全是在 Docker 环境中进行配置的,所以密码等内容不涉密。

他的代码如下:

//MySqlDb.h
#pragma once

#include "rdb/database/IRdbMySqlDatabaseInterface.h"
using namespace IWebCore;

class MySqlDb : public IRdbMysqlDatabaseInterface<MySqlDb>
{
public:
    MySqlDb() = default;

public:
    virtual IRdbSource getSource() const final;
};

// MySqlDb.cpp
#include "MySqlDb.h"

IRdbSource MySqlDb::getSource() const
{
    IRdbSource source;
    source.driverName = "QMYSQL";
    source.databaseName = "TestDb";
    source.host = "127.0.0.1";
    source.user = "root";
    source.password = "xxxx";
    source.port = 3306;
    return source;
}

MariaDb

同上,列出具体的代码如下:

// MariaDb.h
#pragma once

#include "rdb/database/IRdbMariaDbDatabaseInterface.h"

class MariaDb : public IRdbMariaDbDatabaseInterface<MariaDb>
{
public:
    MariaDb() = default;

public:
    virtual IRdbSource getSource() const final;
};

// MariaDb.cpp
#include "MariaDb.h"

IRdbSource MariaDb::getSource() const
{
    IRdbSource source;
    source.driverName = "QMYSQL";
    source.databaseName = "TestDb";
    source.host = "127.0.0.1";
    source.user = "root";
    source.password = "xxxx";
    source.port = 3305;
    return source;
}

SqlServer

SqlServer是通过ODBC 进行配置的,相关代码如下:

// SqlServerDb.h
#pragma once

#include "rdb/database/IRdbSqlServerDatabaseInterface.h"

class SqlServerDb : public IRdbSqlServerDatabaseInterface<SqlServerDb>
{
public:
    SqlServerDb() = default;

public:
    virtual IRdbSource getSource() const final;
};

//SqlServerDb.cpp
#include "SqlServerDb.h"

IRdbSource SqlServerDb::getSource() const
{
    IRdbSource source;
    source.driverName = "QODBC3";
    source.databaseName = "TestDb";
    source.user = "sa";
    source.password = "xxxxxxxx";
    source.host = "localhost";
    source.port = 1433;

    return source;
}

Postgres

Postgres 是Qt 自带支持的数据库类型。使用代码如下:

// PostgresDb.h
#pragma once

#include "rdb/database/IRdbPostgreDatabaseInterface.h"

class PostgreDb : public IRdbPostgreDatabaseInterface<PostgreDb>
{
public:
    PostgreDb() = default;

public:
    virtual IRdbSource getSource() const final;
};

// PostgresDb.cpp
#include "PostgreDb.h"

IRdbSource PostgreDb::getSource() const
{
    IRdbSource source;
    source.driverName = "QPSQL";
    source.databaseName = "TestDb";
    source.user = "postgres";
    source.password = "xxx";
    source.host = "127.0.0.1";
    source.port = 5432;

    return source;
}

扩展数据库类型

用户如果要扩展数据库类型,需要继继承以下的基类创建用户自己的类型。

Dialect

用户需要继承 IRdbDialectInterface 形成自己数据库的Dialect 方言。在继承的时候,用户需要区分哪些函数需要重载。如果需要重载的函数没标记 virtual,用户可以在基类上直接修改,标记好virtual,再在子类中继承该基类。建议保持命名规则统一,生成的Dialect 名称为 IRdbXxxxDialect

Database

用户创建的第二个类为 DatabaseInterface 类。该类可以参考其他的 DatabaseInterface的写法。建议命名规则统一,生成的类为 `IRdbXxxDatabaseInterface.