Skip to content

C++ 增删查改

qiuwenchen edited this page Apr 25, 2024 · 4 revisions

增删查改是数据库最常用的功能,因此 WCDB C++ 对其进行了特别的封装,使其通过一行代码即可完成操作。

插入操作

插入操作有 insertObjectsinsertOrReplaceObjectsinsertOrIgnoreObjects 三个接口。故名思义,第一个只是单纯的插入数据,当数据出现冲突时会失败。而后面两个在主键冲突等约束冲突出现时, insertOrReplaceObjects会把新数据会覆盖旧数据,insertOrIgnoreObjects则是会跳过插入冲突数据,但不产生错误。下面是 insertObjects 的函数原型, 另外两个函数与此相同,只是名字不同:

template<class ObjectType>
bool insertObjects(const ValueArray<ObjectType> &objs,
                   const UnsafeStringView &table,
                   const Fields &fields = Fields())

以已经完成模型绑定的类 Sample 为例:

// Sample.hpp
class Sample {
public:
    Sample();//必须要有默认构造函数
    Sample(int identifier, const std::string& content);//非必须实现的构造函数,只是为了演示方便
    int identifier;
    std::string content;
    WCDB_CPP_ORM_DECLARATION(Sample)
};

// Sample.cpp
WCDB_CPP_ORM_IMPLEMENTATION_BEGIN(Sample)
WCDB_CPP_SYNTHESIZE(identifier)
WCDB_CPP_SYNTHESIZE(content)
WCDB_CPP_ORM_IMPLEMENTATION_END

bool ret = database.createTable<Sample>("sampleTable");
    
Sample object(1, "insert");
ret &= database.insertObjects<Sample>(object, "sampleTable");// 插入成功

ret &= database.insertObjects<Sample>(object, "sampleTable");// 插入失败,因为主键 identifier = 1 已经存在

object.content = "insertOrReplace";
ret &= database.insertOrReplaceObjects<Sample>(object, "sampleTable");// 插入成功,且 content 的内容会被替换为 "insertOrReplace"

object.content = "insertOrIgnore";
ret &= database.insertOrIgnoreObjects<Sample>(object, "sampleTable");// 插入成功,且 content 的内容不会被更改,还是"insertOrReplace"

关于自增插入,可参考模型绑定 - 自增属性一章。

需要插入的对象多时,还可以使用批量插入。当插入的对象数大于 1 时,WCDB 会自动开启事务,进行批量化地插入,以获得更好的性能。

Sample object2(2, "insert2");
Sample object3(3, "insert3");
// 两个对象要么一起插入成功,要么一起插入失败
ret &= database.insertObjects<Sample>({object2, object3}, "sampleTable");

除了插入整个对象,也可以只插入对象的部分属性:

Sample object4(4, "insert4");
// 部分插入,没有指定 WCDB_FIELD(Sample::content), "insert4"这个内容没有写入数据库,content字段的值为空
ret = database.insertObjects<Sample>(object4, "sampleTable", WCDB_FIELD(Sample::identifier));

这里的insertObjects的第三个参数传入的实际是WCDB::Fields对象,是一个描述对象字段组合的列表,在读写的场合用来指定读写对象的部分成员,我们会在语言集成查询中进一步介绍。

删除操作

删除操作可以使用deleteObjects接口,后面可按需传入 whereorderslimitoffset参数以删除部分数据,这个接口的原型是:

bool deleteObjects(const UnsafeStringView &table,
                   const Expression &where = Expression(),
                   const OrderingTerms &orders = OrderingTerms(),
                   const Expression &limit = Expression(),
                   const Expression &offset = Expression());

以下是删除接口的示例代码:

// 删除 sampleTable 中所有 identifier 大于 1 的行的数据
ret = database.deleteObjects("sampleTable", WCDB_FIELD(Sample::identifier) > 1);

// 删除 sampleTable 中 identifier 降序排列后的前 2 行数据
ret = database.deleteObjects("sampleTable",
                             WCDB::Expression(),//不需要传的参数可以用函数声明中的默认值跳过
                             WCDB_FIELD(Sample::identifier).asOrder(WCDB::Order::DESC),
                             2);

// 删除 sampleTable 中 content 非空的数据,按 identifier 降序排列后的前 3 行的后 2 行数据
ret = database.deleteObjects("sampleTable",
                             WCDB_FIELD(Sample::content).notNull(),
                             WCDB_FIELD(Sample::identifier).asOrder(WCDB::Order::DESC),
                             2,
                             3);

// 删除 sampleTable 中的所有数据
ret = database.deleteObjects("sampleTable");

这里的 wherelimitoffset 本质都是遵循 WCDB::Expression 的对象,可以是数字、字符串、字段或其他更多的组合。同样地,我们会在语言集成查询进一步介绍。

删除接口不会删除表本身,开发者需要调用 dropTable 接口删除表。

更新操作

更新数据库的时候可以使用C++对象或结构体来更新数据库,也可以直接使用具体值来更新。使用C++对象或结构体的接口为: updateObject,可以使用whereorderslimitoffset参数来指定更新范围,下面是函数原型:

template<class ObjectType>
bool updateObject(const ObjectType &obj,
                  const Fields &fields,
                  const UnsafeStringView &table,
                  const Expression &where = Expression(),
                  const OrderingTerms &orders = OrderingTerms(),
                  const Expression &limit = Expression(),
                  const Expression &offset = Expression());

下面是示例代码:

Sample object;
object.content = "update";

// 将 sampleTable 中所有 identifier 大于 1 且 content 字段不为空 的行的 content 字段更新为 "update"
ret = database.updateObject(object,
                            WCDB_FIELD(Sample::content),
                            "sampleTable",
                            WCDB_FIELD(Sample::identifier) > 1 && WCDB_FIELD(Sample::content).notNull());

// 将 sampleTable 中前三行的 content 字段更新为 "update"
ret = database.updateObject(object,
                            WCDB_FIELD(Sample::content),
                            "sampleTable",
                            WCDB::Expression(),//不需要传的参数可以用函数声明中的默认值跳过
                            3);

而使用值来更新数据库的接口分别是 updateRow,可以使用whereorderslimitoffset参数来指定更新范围。row 是WCDB::Value的数组,支持绑定字段的类型都可以转变为WCDB::Value。下面是接口原型:

bool updateRow(const OneRowValue &row,
               const Columns &columns,
               const UnsafeStringView &table,
               const Expression &where = Expression(),
               const OrderingTerms &orders = OrderingTerms(),
               const Expression &limit = Expression(),
               const Expression &offset = Expression())

下面是使用示例:

// 将 sampleTable 中所有 identifier 大于 1 且 content 字段不为空的行的 content 字段更新为 "update"
ret = database.updateRow("update",
                         WCDB_FIELD(Sample::content),
                         "sampleTable",
                         WCDB_FIELD(Sample::identifier) > 1 && WCDB_FIELD(Sample::content).notNull());

// 将 sampleTable 中 identifier 为 1 行的 identifier 字段更新为2,content 字段更新为 "update2"
ret = database.updateRow({2, "update"},
                         Sample::allFields(),
                         "sampleTable",
                         WCDB_FIELD(Sample::identifier) == 1);

这里用到的Sample::allFields()等价于{WCDB_FIELD(Sample::identifier), WCDB_FIELD(Sample::content)}allFields是获取ORM类所有DB配置的字段的便捷方法

查询操作

查找接口接口较多,但大部分都是为了简化操作而提供的便捷接口,和更新接口类似,实现上主要分为对象查找和值查找两大类接口。

对象查询操作

getFirstObjectgetAllObjects 都是对象查找的接口,他们直接返回已进行模型绑定的对象或结构体,它们都可以使用whereorderslimitoffset参数来指定查询范围。 而 getFirstObject 等价于 limit为 1 时的 getAllObjects 接口。不同的是,它直接返回 Object 对象,而不是一个数组,使用上更便捷。 getFirstObject的原型如下,getAllObjects的原型与此相同:

template<class ObjectType>
Optional<ObjectType> getFirstObject(const UnsafeStringView &table,
                                    const Expression &where = Expression(),
                                    const OrderingTerms &orders = OrderingTerms(),
                                    const Expression &offset = Expression())

以下是对象查找操作的示例代码:

// 返回 sampleTable 中的所有数据
WCDB::OptionalValueArray<Sample> allObjects = database.getAllObjects<Sample>("sampleTable");

// 返回 sampleTable 中 identifier 小于 5 或 大于 10 的行的数据
WCDB::OptionalValueArray<Sample> objects = database.getAllObjects<Sample>("sampleTable", WCDB_FIELD(Sample::identifier) < 5 || WCDB_FIELD(Sample::identifier) > 10);

// 返回 sampleTable 中 identifier 最大的行的数据
WCDB::Optional<Sample> object = database.getFirstObject<Sample>("sampleTable",
                                                                //不需要传的参数可以用函数声明中的默认值跳过
                                                                WCDB::Expression(),
                                                                WCDB_FIELD(Sample::identifier).asOrder(WCDB::Order::DESC));

查询接口的返回数据都是WCDB::Optional类型,WCDB::Optionalsucceed函数返回true时,才表示结果有效,可以使用value函数获取到具体值。如果succeed函数返回false,则表示查询失败了,没有结果返回。

对象部分查询

与 "insert"、"update" 类似,对象查找操作也支持指定字段,这种接口有getFirstObjectWithFieldsgetAllObjectsWithFields 两个,区别还是前者查单个对象,后者查一组对象,这两个接口都可以使用whereorderslimitoffset参数来指定查找范围。 getFirstObjectWithFields的原型如下,getAllObjectsWithFields的原型与此相同:

template<class ObjectType>
Optional<ObjectType>
getFirstObjectWithFields(const UnsafeStringView &table,
                         const ResultFields &resultFields,
                         const Expression &where = Expression(),
                         const OrderingTerms &orders = OrderingTerms(),
                         const Expression &offset = Expression())

下面是使用示例:

// 返回 sampleTable 中的所有identifier字段,返回结果中的content内容为空
WCDB::OptionalValueArray<Sample> allObjects = database.getAllObjectsWithFields<Sample>("sampleTable", WCDB_FIELD(Sample::identifier));

值查询操作

值查询有下面四类接口,这些接口都可以使用whereorderslimitoffset参数来指定查找范围:

  • selectValue
  • selectOneColumn
  • selectOneRow
  • selectAllRow

试考虑,表中的数据可以想象为一个矩阵的存在,假设其数据如下:

identifier content
1 "sample1"
2 "sample1"
3 "sample2"
4 "sample2"
5 "sample2"

在不考虑 whereorderslimitoffset 参数的情况下:

  1. selectAllRow接口获取整个矩阵的所有内容,即返回值为二维数组。
  2. selectOneRow 接口获取某一横行的数据,即返回值为一维数组。
  3. selectOneColumn 接口获取某一纵列的数据,即返回值为一维数组。
  4. selectValue 接口获取矩阵中某一个格的内容。

以下是值查询操作的示例代码:

// 获取所有内容
WCDB::OptionalMultiRows allRows = database.selectAllRow(Sample::allFields(), "sampleTable");
printf("%lld", allRows.value()[2][0].intValue()); // 输出 3

// 获取第二行
WCDB::OptionalOneRow secondRow = database.selectOneRow(Sample::allFields(),
                                                       "sampleTable",
                                                       //不需要传的参数可以用函数声明中的默认值跳过
                                                       WCDB::Expression(),
                                                       1);
printf("%lld", secondRow.value()[0].intValue()); // 输出 3

// 获取第三行 content 列的值
WCDB::OptionalOneColumn contentColumn = database.selectOneColumn(WCDB_FIELD(Sample::content), "sampleTable");
printf("%s", contentColumn.value()[2].textValue().data()); // 输出 sample2

// 获取第二列第二行的值
WCDB::OptionalValue value = database.selectValue(WCDB_FIELD(Sample::content),
                                                 "sampleTable",
                                                 //不需要传的参数可以用函数声明中的默认值跳过
                                                 WCDB::Expression(),
                                                 WCDB::OrderingTerms(),
                                                 1);
printf("%s", value.value().textValue().data()); // 输出 sample1

// 获取 identifier 的最大值
WCDB::OptionalValue maxId = database.selectValue(WCDB_FIELD(Sample::identifier).max(), "sampleTable");
printf("%lld", maxId.value().intValue()); // 输出 5

这里 WCDB_FIELD(Sample::identifier).max() 是可以转换 WCDB::ResultColumn 的对象,用于查找 identifier 列的最大值。我们会在语言集成查询中进一步介绍。

Clone this wiki locally