CRUD是什么?
用我自己的话解释,就是`fisco-bcos`提供的区块链表格的增删改查,`CRUD`分别代表`Create`创建、`Read`读取(查询)、`Update`修改、`Delete`删除四个操作。
本文基于作者个人的坎坷摸索经验总结,有任何错误欢迎批评指正....
首先环境需要`Table.sol`合约,这里会提供一份(不要对`Table.sol`源码有任何改动)
pragma solidity ^0.4.24;
contract TableFactory {
function openTable(string) public constant returns (Table); // 打开表
function createTable(string,string,string) public returns(int); // 创建表
}
// 查询条件
contract Condition {
//等于
function EQ(string, int) public;
function EQ(string, string) public;
//不等于
function NE(string, int) public;
function NE(string, string) public;
//大于
function GT(string, int) public;
//大于或等于
function GE(string, int) public;
//小于
function LT(string, int) public;
//小于或等于
function LE(string, int) public;
//限制返回记录条数
function limit(int) public;
function limit(int, int) public;
}
// 单条数据记录
contract Entry {
function getInt(string) public constant returns(int);
function getAddress(string) public constant returns(address);
function getBytes64(string) public constant returns(byte[64]);
function getBytes32(string) public constant returns(bytes32);
function getString(string) public constant returns(string);
function set(string, int) public;
function set(string, string) public;
function set(string, address) public;
}
// 数据记录集
contract Entries {
function get(int) public constant returns(Entry);
function size() public constant returns(int);
}
// Table主类
contract Table {
// 查询接口
function select(string, Condition) public constant returns(Entries);
// 插入接口
function insert(string, Entry) public returns(int);
// 更新接口
function update(string, Entry, Condition) public returns(int);
// 删除接口
function remove(string, Condition) public returns(int);
function newEntry() public constant returns(Entry);
function newCondition() public constant returns(Condition);
}
TableFactory合约创建表
固定合约地址为`0x1001`。这句话的意思是,创建`TableFactory`时参数内固定写`0x1001`
TableFactory tf; //将工厂作为全局变量,方便反复调用
string public TABLE_NAME = "t_test4"; //表名作为全局变量,方便反复调用
constructor() public { //使用构造方法对其进行初始化
tf = TableFactory(0x1001); //创建表工厂对象固定使用地址0x1001
tf.createTable(TABLE_NAME,"id","item_name, item_age"); //表名,主键名(目前只支持单个主键),表的其他字段名(字段之间以英文逗号分隔)
}
这个合约相当于表工厂,功能只有两个:
- `createTable`创建表:返回错误码(int256),错误码详见下表
- `opentTable`打开表:返回合约Table的地址,当表名不存在时返回空地址
这里需要注意,一个地址部署的合约,其表名一旦创建,再次重复部署使用相同的表名的话,会直接对原来创建过的表进行操作。
想要一个表名相同的全新表的话,就只能换个地址再部署了
我这里就是最开始建表后,如果中途修改表格,再添加表的列,键位增加。预编译是没有问题,但是再次调用新增函数就报错:`Receipt message: PrecompiledError`
Entry合约
`Entry`代表记录对象,一个`Entry`对象代表一行记录,一个表内容就是有很多行Entry对象组成的。
其接口如下:
Entries合约
`Entries`是记录集合对象,`Entries`用于存放Entry对象,查询数据后会返回这个`Entries`。
其接口如下:
Condition合约
查询、更新和删除记录时指定的过滤条件对象。这里需要注意,`Condition`条件为空表示不过滤,也可以根据需要使用条件过滤。但就算`Condition`条件为空也会有一个对主键值的过滤条件。
CRUD的主键不具备唯一性!!!
小白可以理解为类别,不管增删改查都必须带上主键值
其接口如下:
Table合约
用于对表进行增删改查操作的对象,`Condition`和`Entry`都是通过此合约创建,其接口如下:
注意看,CRUD四个操作都是有主键值作为条件的,因此需要谨记主键值不唯一!!!
案例实操:
第一步:引入Table.sol
使用`import "./Table.sol";`导入也好,直接粘贴在需要的合约文件内也罢,都是可以的。
import "./Table.sol";
第二步:创建表
在TableTest.sol合约文件中,创建表的核心代码如下:
contract TableDemo001 {
TableFactory tf; //将工厂作为全局变量,方便反复调用
string public TABLE_NAME = "t_test4"; //表名作为全局变量,方便反复调用
constructor() public { //使用构造方法对其进行初始化
tf = TableFactory(0x1001); //创建表工厂对象固定使用地址0x1001
tf.createTable(TABLE_NAME,"id","item_name, item_age"); //表名,主键名(目前只支持单个主键),表的其他字段名(字段之间以英文逗号分隔)
}
}
createTable执行原理:createTable执行成功之后,将会在区块链系统表_sys_tables_(区块链启动会自动创建该表,专门记录区块链中所有表的信息)中插入t_test的表信息,即表名,主键名和其他字段名,但并没有正式创建该表。当对t_test表进行增删改查操作时,会首先判断t_test表是否存在,若不存在,则会查询_sys_tables_表获取t_test表的信息,如果查询有t_test表信息,则创建该表,否则执行失败。t_test表存在,则继续执行增删改查操作。
这一步是可选操作:比如新合约只是读写旧合约创建的表,则不需创建表这步操作。
第三步:针对表进行CRUD操作
在TableTest.sol合约文件中,插入记录核心代码如下:
function insert(string memory id, string memory item_name, int item_age) public returns(int) {
Table table = tf.openTable(TABLE_NAME); //通过表名打开表
require(table != address(0),"table name not exist"); //检查表是否存在
Entry entry = table.newEntry(); //创建Entry对象,相当于插入一行数据
// 设置Entry对象,即设置记录的字段值
entry.set("id", id);
entry.set("item_name",item_name);
entry.set("item_age",item_age);
// 调用Table的insert方法插入记录
// 返回插入影响的记录数,值为1则插入成功,否则插入失败
int count = table.insert(id, entry);
return count;
}
查询记录核心代码如下:
// 因为无论如何都必须带上主键值,所以最普通的查询方法都相当于ById了
// 使用""空字符串或"*"星号通配符作为主键值,都无法实现查询所有
function selectById(string memory id) public returns(int[],string[],int[]) {
Table table = tf.openTable(TABLE_NAME);
require(table != address(0), "table name not exist");
//创建Condition对象,条件为空表示不过滤,也可以根据需要使用条件过滤
Condition condition = table.newCondition();
Entries entries = table.select(id, condition);
int size = entries.size(); // 获取记录集合的大小
string[] memory item_name_list = new string[](uint256(size));
int[] memory item_age_list = new int[](uint256(size));
int[] memory id_list = new int[](uint256(size));
// 遍历记录集合
// 将记录的三个字段值分别存放到三个数组中,方便方法返回查询的数据
for(int i = 0; i < size; ++i){
// 根据索引获取记录集合中的记录(获取每一行的数据)
Entry entry = entries.get(i);
// 根据记录的字段名查询字段值
// 注意字段值的类型不同需要选择相应的get方法
id_list[uint256(i)] = entry.getInt("id");
item_name_list[uint256(i)] = entry.getString("item_name");
item_age_list[uint256(i)] = entry.getInt("item_age");
}
return (id_list,item_name_list,item_age_list);
}
更新记录核心代码如下:
function updateByAge(string memory item_age, string memory id) public returns(int) {
Table table = tf.openTable(TABLE_NAME);
require(table != address(0),"table name not exist");
Entry entry = table.newEntry();
entry.set("item_age",item_age); // 设置需要修改的属性
Condition condition = table.newCondition(); // id = 1 && age >=10 && age <=15
condition.GE("item_age",10);
condition.LE("item_age",15);
// 调用Table的update方法更新记录
// 返回更新影响的记录数,值大于0则更新成功,否则更新失败
int count = table.update(id, entry, condition);
require(count > 0, "update count <= 0");
return count;
}
删除记录核心代码如下:
function removeByNameAndAge(string memory id, string memory item_name, int item_age) public returns(string memory) {
Table table = tf.openTable(TABLE_NAME);
require(table != address(0), "table name not exist");
Condition condition = table.newCondition(); // id = 1 && item_name ==10 && item_age ==15
condition.EQ("item_name", item_name);
condition.EQ("item_age",item_age);
int count = table.remove(id,condition);
if(count > 0){
return "yes";
}else {
return "no";
require(count <= 0, "remove count <= 0");
}
}
完整代码如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.24;
// 启用 ABI 编码器 V2
pragma experimental ABIEncoderV2;
// import "./Table.sol";
contract TableDemo001 {
TableFactory tf; //将工厂作为全局变量,方便反复调用
string public TABLE_NAME = "t_test4"; //表名作为全局变量,方便反复调用
constructor() public { //使用构造方法对其进行初始化
tf = TableFactory(0x1001); //创建表工厂对象固定使用地址0x1001
tf.createTable(TABLE_NAME,"id","item_name, item_age"); //表名,主键名(目前只支持单个主键),表的其他字段名(字段之间以英文逗号分隔)
}
function insert(string memory id, string memory item_name, int item_age) public returns(int) {
Table table = tf.openTable(TABLE_NAME); //通过表名打开表
require(table != address(0),"table name not exist"); //检查表是否存在
Entry entry = table.newEntry(); //创建Entry对象,相当于插入一行数据
// 设置Entry对象,即设置记录的字段值
entry.set("id", id);
entry.set("item_name",item_name);
entry.set("item_age",item_age);
// 调用Table的insert方法插入记录
// 返回插入影响的记录数,值为1则插入成功,否则插入失败
int count = table.insert(id, entry);
return count;
}
// 因为无论如何都必须带上主键值,所以最普通的查询方法都相当于ById了
// 使用""空字符串或"*"星号通配符作为主键值,都无法实现查询所有
function selectById(string memory id) public returns(int[],string[],int[]) {
Table table = tf.openTable(TABLE_NAME);
require(table != address(0), "table name not exist");
//创建Condition对象,条件为空表示不过滤,也可以根据需要使用条件过滤
Condition condition = table.newCondition();
Entries entries = table.select(id, condition);
int size = entries.size(); // 获取记录集合的大小
string[] memory item_name_list = new string[](uint256(size));
int[] memory item_age_list = new int[](uint256(size));
int[] memory id_list = new int[](uint256(size));
// 遍历记录集合
// 将记录的三个字段值分别存放到三个数组中,方便方法返回查询的数据
for(int i = 0; i < size; ++i){
// 根据索引获取记录集合中的记录(获取每一行的数据)
Entry entry = entries.get(i);
// 根据记录的字段名查询字段值
// 注意字段值的类型不同需要选择相应的get方法
id_list[uint256(i)] = entry.getInt("id");
item_name_list[uint256(i)] = entry.getString("item_name");
item_age_list[uint256(i)] = entry.getInt("item_age");
}
return (id_list,item_name_list,item_age_list);
}
function updateByAge(string memory item_age, string memory id) public returns(int) {
Table table = tf.openTable(TABLE_NAME);
require(table != address(0),"table name not exist");
Entry entry = table.newEntry();
entry.set("item_age",item_age); // 设置需要修改的属性
Condition condition = table.newCondition(); // id = 1 && age >=10 && age <=15
condition.GE("item_age",10);
condition.LE("item_age",15);
// 调用Table的update方法更新记录
// 返回更新影响的记录数,值大于0则更新成功,否则更新失败
int count = table.update(id, entry, condition);
require(count > 0, "update count <= 0");
return count;
}
function removeByNameAndAge(string memory id, string memory item_name, int item_age) public returns(string memory) {
Table table = tf.openTable(TABLE_NAME);
require(table != address(0), "table name not exist");
Condition condition = table.newCondition(); // id = 1 && item_name ==10 && item_age ==15
condition.EQ("item_name", item_name);
condition.EQ("item_age",item_age);
int count = table.remove(id,condition);
if(count > 0){
return "yes";
}else {
return "no";
require(count <= 0, "remove count <= 0");
}
}
// 查询所有的方法,使用""空字符串或"*"星号通配符作为主键值,都无法实现查询所有
// function selectAll() public returns(int[],string[],int[]) {
// Table table = tf.openTable(TABLE_NAME);
// require(table != address(0), "table name not exist");
// Condition condition = table.newCondition();
// Entries entries = table.select("*", condition);
// int size = entries.size();
// string[] memory item_name_list = new string[](uint256(size));
// int[] memory item_age_list = new int[](uint256(size));
// int[] memory id_list = new int[](uint256(size));
// for(int i = 0; i < size; ++i){
// Entry entry = entries.get(i);
// id_list[uint256(i)] = entry.getInt("id");
// item_name_list[uint256(i)] = entry.getString("item_name");
// item_age_list[uint256(i)] = entry.getInt("item_age");
// }
// return (id_list,item_name_list,item_age_list);
// }
}
pragma solidity ^0.4.24;
contract TableFactory {
function openTable(string) public constant returns (Table); // 打开表
function createTable(string,string,string) public returns(int); // 创建表
}
// 查询条件
contract Condition {
//等于
function EQ(string, int) public;
function EQ(string, string) public;
//不等于
function NE(string, int) public;
function NE(string, string) public;
//大于
function GT(string, int) public;
//大于或等于
function GE(string, int) public;
//小于
function LT(string, int) public;
//小于或等于
function LE(string, int) public;
//限制返回记录条数
function limit(int) public;
function limit(int, int) public;
}
// 单条数据记录
contract Entry {
function getInt(string) public constant returns(int);
function getAddress(string) public constant returns(address);
function getBytes64(string) public constant returns(byte[64]);
function getBytes32(string) public constant returns(bytes32);
function getString(string) public constant returns(string);
function set(string, int) public;
function set(string, string) public;
function set(string, address) public;
}
// 数据记录集
contract Entries {
function get(int) public constant returns(Entry);
function size() public constant returns(int);
}
// Table主类
contract Table {
// 查询接口
function select(string, Condition) public constant returns(Entries);
// 插入接口
function insert(string, Entry) public returns(int);
// 更新接口
function update(string, Entry, Condition) public returns(int);
// 删除接口
function remove(string, Condition) public returns(int);
function newEntry() public constant returns(Entry);
function newCondition() public constant returns(Condition);
}