Skip to content

Java|Kotlin 增删查改

qiuwenchen edited this page Apr 25, 2024 · 2 revisions

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

插入操作

插入操作有 insertObjectinsertOrReplaceObjectinsertOrIgnoreObject 三类接口。故名思义,前者只是单纯的插入数据,当数据出现冲突时会失败,而后者在主键冲突等约束冲突出现时,新数据会覆盖旧数据。

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

//Java
Sample sample = new Sample();
sample.id = 1;
database.insertObject(sample,  DBSample.allFields(), "sampleTable"); // 插入成功

database.insertObject(sample,  DBSample.allFields(), "sampleTable"); // 插入失败,因为主键 id = 1 已经存在

sample.content = "insertOrReplace";
database.insertOrReplaceObject(sample,  DBSample.allFields(), "sampleTable"); // 插入成功,且 content 的内容会被替换为 "insertOrReplace"

sample.content = "insertOrIgnore";
database.insertOrIgnoreObject(sample,  DBSample.allFields(), "sampleTable"); // 插入成功,但 content 的内容不会更改,还是 "insertOrReplace"
//Kotlin
val sample = Sample()
sample.id = 1
database.insertObject(sample, DBSample.allFields(), "sampleTable") // 插入成功

database.insertObject(sample, DBSample.allFields(), "sampleTable") // 插入失败,因为主键 id = 1 已经存在

sample.content = "insertOrReplace"
database.insertOrReplaceObject(sample, DBSample.allFields(), "sampleTable") // 插入成功,且 content 的内容会被替换为 "insertOrReplace"

sample.content = "insertOrIgnore"
database.insertOrIgnoreObject(sample, DBSample.allFields(), "sampleTable") // 插入成功,但 content 的内容不会更改,还是 "insertOrReplace"

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

insert 方法的原型为:

// insert、insertOrReplace、insertOrIgnore只有方法名不同,其他参数都一样。
public <T> void insertObject(
  T object,          // 需要插入的对象
  Field<T>[] fields, // 需要插入的字段
  String tableName   // 表名
) throws WCDBException

这里需要特别注意的是 fields 参数,它是 Field 对象的数组。我们会在语言集成查询进一步介绍。这里只需了解,它可以传入模型绑定中定义的字段,传入所有字段可以直接用DBSample.allFields(),也可以传入部分字段,这样只会插入指定的字段,这就构成了部分插入。

以下是一个部分插入的例子:

//Java
Sample sample = new Sample();
sample.id = 1;
sample.content = "insert";
database.insertObject(sample,  new Field[]{DBSample.id}, "sampleTable");// 部分插入,没有指定content字段
//Kotlin
val sample = Sample()
sample.id = 1
sample.content = "insert"
database.insertObject(sample, arrayOf(DBSample.id), "sampleTable")  // 部分插入,没有指定content字段

这个例子中,指定了只插入 id 字段,因此其他没有指定的字段,会使用 模型绑定中定义的默认值 或 空 来代替。这里 content 没有定义默认值,因此其数据为空。

除了插入单个对象的接口,也有插入多个对象的系列接口: insertObjectsinsertOrReplaceObjectsinsertOrIgnoreObjects 。这类接口第一个参数可以传入Collection<T>集合类型数据,这批对象要么一起插入成功,要么一起插入失败。

//Java
Sample sample2 = new Sample();
sample2.id = 2;
sample2.content = "insert2";
Sample sample3 = new Sample();
sample3.id = 3;
sample3.content = "insert3";
database.insertObjects(Arrays.asList(sample2, sample3),  DBSample.allFields(), "sampleTable");
//Kotlin
val sample2 = Sample()
sample2.id = 2
sample2.content = "insert2"
val sample3 = Sample()
sample3.id = 3
sample3.content = "insert3"
database.insertObjects(listOf(sample2, sample3), DBSample.allFields(), "sampleTable")

插入是最常用且比较容易操作卡顿的操作,因此 WCDB Java/Kotlin 对其进行了特殊处理。 当插入的对象数大于 1 时,WCDB Java/Kotlin 会自动开启事务,进行批量化地插入,以获得更好的性能。

删除操作

删除操作主要是通过deleteObjects系列方法,它支持不同的传入参数组合,其中参数最全的一个方法声明为:

public void deleteObjects(
  String tableName,     // 表名
  Expression condition, // 符合删除的条件
  OrderingTerm order,   // 排序的方式
  long limit,           // 删除的个数
  long offset           // 从第几个开始删除
) throws WCDBException

删除方法会删除表内的数据,并通过 conditionorderlimitoffset 参数来确定需要删除的数据的范围。

这四个组合起来可以理解为:将 tableName 表内,满足 condition 的数据,按照 order 的方式进行排序,然后从头开始第 offset 行数据后的 limit 行数据删除。

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

// 删除 sampleTable 中所有 identifier 大于 1 的行的数据
database.deleteObjects("sampleTable", DBSample.id.gt(1));

// 删除 sampleTable 中 identifier 降序排列后的前 2 行数据
database.deleteObjects("sampleTable", DBSample.id.order(Order.Desc), 2);

// 删除 sampleTable 中 description 非空的数据,按 identifier 降序排列后的前 3 行的后 2 行数据
database.deleteObjects("sampleTable", DBSample.content.notNull(), DBSample.id.order(Order.Desc), 2, 3);

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

这里的 condition参数作为Expression 的对象,可以是数字、字符串、字段或其他更多的组合,可以表达任意sqlite支持的表达式,我们会在语言集成查询进一步介绍。

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

更新操作

更新数据库的时候可以使用Java/Kotlin对象来更新数据库,也可以直接使用具体值来更新。

对象更新

使用Java/Kotlin对象的接口为updateObject系列方法,可以通过fields参数指定更新的字段,使用conditionorderslimitoffset参数来指定更新范围。下面是参数最全的方法的原型:

public <T> void updateObject(
  T object,            //更新的对象
  Field<T>[] fields,   //需要更新的字段
  String tableName,    //更新的表名
  Expression condition,//筛选条件
  OrderingTerm order,  //排序方式
  long limit,          //更新的个数
  long offset          //从第几个开始更新
) throws WCDBException

以下是更新操作的示例代码:

//Java
Sample sample = new Sample();
sample.content = "update";
// 将 sampleTable 中所有 id 大于 1 且 content 字段不为空 的行的 content 字段更新为 "update"
database.updateObject(
  sample,
  new Field[]{DBSample.content},
  "sampleTable",
  DBSample.id.gt(1).and(DBSample.content.notNull()));

// 将 sampleTable 中前三行的 content 字段更新为 "update"
database.updateObject(
  sample, 
  new Field[]{DBSample.content}, 
  "sampleTable",
  DBSample.id.order(Order.Asc), 
  3);
//Kotlin
val sample = Sample()
sample.content = "update"
// 将 sampleTable 中所有 id 大于 1 且 content 字段不为空 的行的 content 字段更新为 "update"
database.updateObject(
  sample, 
  arrayOf(DBSample.content),
  "sampleTable",
  DBSample.id.gt(1).and(DBSample.content.notNull())
)

// 将 sampleTable 中前三行的 content 字段更新为 "update"
database.updateObject(
  sample, 
  arrayOf(DBSample.content),
  "sampleTable",
  DBSample.id.order(Order.Desc),
  3
)

值更新

使用值来更新主要是updateValueupdateRow两个系列接口。前者使用单个值来更新,这里的值可以是各种支持ORM的java基础类型,也可以是封装了这些基础类型的Value对象;后者是使用一行数据来更新,一行数据用Value组来表示。

updateValueupdateRow同样也可以通过columns参数指定更新的字段,使用conditionorderslimitoffset参数来指定更新范围。下面是参数最全的接口的原型:

public void updateValue(
  Value value,          //更新的值
  Column column,        //指定更新的列,Column是Field的基类
  String tableName,     //更新的表名
  Expression condition, //筛选条件
  OrderingTerm order,   //排序方式
  long limit,           //更新的个数
  long offset           //从第几个开始更新
) throws WCDBException
  
public void updateRow(
  Value[] row,          //更新的行数据
  Column[] columns,     //指定更新的列,Column是Field的基类
  String tableName,     //更新的表名
  Expression condition, //筛选条件
  OrderingTerm order,   //排序方式
  long limit,           //更新的个数
  long offset           //从第几个开始更新
) throws WCDBException

而 "with row" 接口则是通过 row 来对数据进行更新。row 是遵循 ColumnEncodable 协议的类型的数组。ColumnEncodable 协议会在后续[自定义字段映射类型][Swift-Custom-ORM-Column-Type]中进一步介绍。这里只需了解,能够进行字段映射的类型基本都遵循 ColumnEncodable 协议。

因此,与 "with object" 对应的示例代码为:

//Java
// 将 sampleTable 中所有 id 大于 1 且 content 字段不为空 的行的 content 字段更新为 "update"
database.updateValue(
  "update",
  DBSample.content,
  "sampleTable",
  DBSample.id.gt(1).and(DBSample.content.notNull()));

// 将 sampleTable 中前三行的 content 字段更新为 "update"
database.updateRow(
  new Value[]{new Value("update")}, 
  new Column[]{DBSample.content}, 
  "sampleTable",
  DBSample.id.order(Order.Asc),
  3);
//Kotlin
// 将 sampleTable 中所有 id 大于 1 且 content 字段不为空 的行的 content 字段更新为 "update"
database.updateValue(
  "update",
  DBSample.content,
  "sampleTable",
  DBSample.id.gt(1).and(DBSample.content.notNull())
)

// 将 sampleTable 中前三行的 content 字段更新为 "update"
database.updateRow(
  arrayOf(Value("update")),
  arrayOf(DBSample.content),
  "sampleTable",
  DBSample.id.order(Order.Asc),
  3
)

查找操作

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

对象查找操作

getFirstObjectgetAllObjects 系列接口都是对象查找的接口,他们直接返回已进行模型绑定的对象。它们都可以使用fields来指定查询的字段,使用conditionorderslimitoffset参数来指定查询范围。 getFirstObject 等价于 limit为 1 时的 getAllObjects 接口。不同的是,它直接返回 Object 对象,而不是一个数组,使用上更便捷。它们的参数最全版本的接口原型为:

public <T> T getFirstObject(
  Field<T>[] fields,    //指定查询的字段,查询所有字段可以用DBxxx.allFields()
  String tableName,     //查询的表名
  Expression condition, //筛选条件
  OrderingTerm order,   //排序方式
  long limit,           //查询的个数
  long offset           //从第几个开始读取
) throws WCDBException 

public <T> List<T> getAllObjects(
  Field<T>[] fields,    //指定查询的字段,查询所有字段可以用DBxxx.allFields()
  String tableName,     //查询的表名
  Expression condition, //筛选条件
  OrderingTerm order,   //排序方式
  long limit,           //查询的个数
  long offset           //从第几个开始读取
) throws WCDBException

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

//Java
// 返回 sampleTable 中的所有数据
List<Sample> allObjects = database.getAllObjects(DBSample.allFields(), "sampleTable");

// 返回 sampleTable 中 identifier 小于 5 或 大于 10 的行的数据
List<Sample> objects = database.getAllObjects(DBSample.allFields(), "sampleTable",
                                              DBSample.id.lt(5).or(DBSample.id.gt(10)));

// 返回 sampleTable 中 identifier 最大的行的数据
Sample object = database.getFirstObject(DBSample.allFields(), "sampleTable", 
                                        DBSample.id.order(Order.Desc));

// 返回 sampleTable 中的所有id字段,返回结果中的content内容为空
List<Sample> partialObjects = database.getAllObjects(new Field[]{DBSample.id}, "sampleTable");
//Kotlin
// 返回 sampleTable 中的所有数据
val allObjects = database.getAllObjects(DBSample.allFields(), "sampleTable")

// 返回 sampleTable 中 identifier 小于 5 或 大于 10 的行的数据
val objects = database.getAllObjects(DBSample.allFields(), "sampleTable",
                                     DBSample.id.lt(5).or(DBSample.id.gt(10)))

// 返回 sampleTable 中 identifier 最大的行的数据
val `object` = database.getFirstObject(DBSample.allFields(),"sampleTable",
                                       DBSample.id.order(Order.Desc))

// 返回 sampleTable 中的所有id字段,返回结果中的content内容为空
val partialObjects: List<Sample> = database.getAllObjects(arrayOf(DBSample.id), "sampleTable")

值查询操作

值查询有下面四类接口,这些接口都可以使用conditionorderslimitoffset参数来指定查找范围,以及使用columns参数来指定查找的字段:

  • getValue
  • getOneColumn,以及它的特定类型变体:getOneColumnIntgetOneColumnLonggetOneColumnFloatgetOneColumnDoublegetOneColumnStringgetOneColumnBLOB
  • getOneRow
  • getAllRows

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

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

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

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

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

//Java
// 获取所有内容
List<Value[]> allRows = database.getAllRows(DBSample.allFields(), "sampleTable");
System.out.print(allRows.get(2)[0].getInt());// 输出 3

// 获取第二行
Value[] secondRow = database.getOneRow(DBSample.allFields(), "sampleTable", DBSample.id.order(Order.Desc), 1);
System.out.print(secondRow[0]);// 输出 3

// 获取第二行 content 列的值
List<Value> contentColumn = database.getOneColumn(DBSample.content, "sampleTable");
System.out.print(contentColumn.get(2).getText());// 输出 sample2
// 直接获取第二行 content 列的字符串值,性能更好
List<String> contentStrings = database.getOneColumnString(DBSample.content, "sampleTable");
System.out.print(contentStrings.get(2));//输出 sample2

// 获取第二列第二行的值
Value value = database.getValue(DBSample.content, "sampleTable", DBSample.id.order(Order.Asc), 1);
System.out.print(value.getText());

// 获取 identifier 的最大值
Value maxId = database.getValue(DBSample.id.max(), "sampleTable");
System.out.print(maxId.getInt());// 输出 5
//Kotlin
// 获取所有内容
val allRows = database.getAllRows(DBSample.allFields(), "sampleTable")
print(allRows[2][0].int) // 输出 3

// 获取第二行
val secondRow = database.getOneRow(DBSample.allFields(),"sampleTable", DBSample.id.order(Order.Desc), 1)
print(secondRow[0]) // 输出 3

// 获取第三行 content 列的值
val contentColumn = database.getOneColumn(DBSample.content, "sampleTable")
print(contentColumn[2].text) // 输出 sample2
// 直接获取第二行 content 列的字符串值,性能更好
val contentStrings = database.getOneColumnString(DBSample.content, "sampleTable")
print(contentStrings[2]) //输出 sample2

// 获取第二列第二行的值
val value = database.getValue(DBSample.content, "sampleTable", DBSample.id.order(Order.Asc), 1)
print(value.text)

// 获取 identifier 的最大值
val maxId = database.getValue(DBSample.id.max(), "sampleTable")
print(maxId.int) // 输出 5
Clone this wiki locally