FISCO BCOS CRUD使用指南

发布时间:2020-11-06 来源:本站



作者:白兴强|FISCO BCOS核心开发者


本文将介绍 FISCO BCOS的CRUD功能,帮助开发者更高效便捷地开发区块链应用。


为什么设计CRUD功能?


在FISCO BCOS 1.0中,节点采用MPT数据结构,通过LevelDB将数据存储于本地,这种模式受限于本地磁盘大小,当业务量增大时数据会急剧膨胀,要进行数据迁移也非常复杂,给数据存储带来较大成本和维护难度。


为了突破容量和性能瓶颈,FISCO BCOS 2.0针对底层存储进行了重新设计,实现了分布式存储,带来了容量和性能上的提升。得益于分布式存储采用了库表结构,FISCO BCOS 2.0设计一套CRUD(Create增加、Read读取、Update更新和Delete删除),让接口更加顺其自然。CRUD面向库表的开发方式符合业务开发习惯,同时也为业务开发提供了另外一种选择(以往只能用Solidity合约),从而让区块链应用开发更加便利。


CRUD有哪些优势?


CRUD的核心设计思想是提供面向SQL编程的区块链应用开发规范。其好处显而易见,主要体现在两升两降。


提升开发区块链应用的效率


CRUD类似传统业务SQL编程开发模式,大大降低了合约开发难度。开发者将合约当做数据库的存储过程,将区块链数据的读写操作转换为面向表的读写操作,简单易用,极大提升了开发区块链应用的效率。


提升区块链应用的性能


CRUD底层逻辑基于预编译合约实现,其数据存储采用分布式存储,读写数据产生的交易不再进入缓慢的EVM虚拟机执行,而是由高速的预编译合约引擎执行,因此提高了合约的数据读写效率,使得基于CRUD开发的区块链应用具有更高的性能。


降低合约维护和升级的复杂度


CRUD合约的逻辑与存储分离,这样开发者就只需要关心核心业务逻辑,数据不再与特定合约绑定,更方便合约升级。当合约逻辑需要变更时,可以部署新合约,新合约可以读写旧合约创建的表和数据。


降低面向SQL业务的迁移成本


传统商业应用很大一部分通过数据库表的方式管理数据,通过CRUD合约可以将面向SQL设计的商业应用非常顺滑的迁移到区块链上,降低业务的迁移成本。


CRUD如何使用?


介绍了CRUD的两升两降优势,想必大家非常关注CRUD具体如何使用? 其实,CRUD简单易用,目前可以用两种方式使用CRUD功能,分别是CRUD合约和SDK CRUD Service接口。


CRUD合约


开发者与区块链交互的媒介主要是智能合约,CRUD功能自然会集成在智能合约。集成方式非常轻,Solidity合约只需要引入FISCO BCOS官方提供的Table.sol抽象接口合约文件即可。Table.sol包含分布式存储专用的智能合约接口,其接口实现在区块链节点,可以创建表,并对表进行增删改查操作。引入了该抽象接口的合约称为CRUD合约,以区别于没有引用该接口的Solidity合约。


Table.sol抽象接口合约文件包括以下抽象合约接口,下面分别进行介绍。


TableFactory合约


用于创建和打开表,其固定合约地址为0x1001,接口如下:



Entry合约


Entry代表记录对象,一个Entry对象代表一行记录,其接口如下:




Entries合约


Entries是记录集合对象,Entries用于存放Entry对象,其接口如下:


Condition合约


查询、更新和删除记录时指定的过滤条件对象,其接口如下:



Table合约


用于对表进行增删改查操作的对象,其接口如下:


有了以上对Table.sol抽象接口合约的了解,现在可以正式进行CRUD合约的开发。以官方提供的CRUD合约TableTest.sol为示例,介绍其开发的三步曲:


第一步:引入Table.sol


将Table.sol合约放入TableTest.sol同级目录,并在TableTest.sol合约文件中引入Table.sol,其代码如下:



import "./Table.sol";



第二步:创建表


在TableTest.sol合约文件中,创建表的核心代码如下:



// 创建TableFactory对象,其在区块链上的固定地址是0x1001
TableFactory tf =TableFactory(0x1001);
// 创建t_test表,表的主键名为name,其他字段名为item_id和item_name
int count =tf.createTable("t_test", "name","item_id,item_name");



注:


第三步:针对表进行CRUD操作


在TableTest.sol合约文件中,插入记录核心代码如下:



TableFactory tf =TableFactory(0x1001);
// 打开创建的t_test表
Table table =tf.openTable("t_test");
// 创建Entry对象,即创建一条空记录
Entry entry = table.newEntry();
// 设置Entry对象,即设置记录的字段值
entry.set("name", name);
entry.set("item_id",item_id);
entry.set("item_name",item_name);
// 调用Table的insert方法插入记录
// 返回插入影响的记录数,值为1则插入成功,否则插入失败
int count = table.insert(name,entry);



查询记录核心代码如下:



TableFactory tf =TableFactory(0x1001);
Table table =tf.openTable("t_test");
// 创建Condition对象,条件为空表示不过滤,也可以根据需要使用条件过滤
Condition condition =table.newCondition();
// 调用Table的select方法查询记录,获得记录集合
Entries entries =table.select(name, condition);
// 获取记录集合的大小
int size = entries.size();
bytes32[] memoryuser_name_bytes_list = new bytes32[](uint256(size));
int[] memory item_id_list = newint[](uint256(size));
bytes32[] memoryitem_name_bytes_list = new bytes32[](uint256(size));
// 遍历记录集合
// 将记录的三个字段值分别存放到三个数组中,方便方法返回查询的数据
for(int i = 0; i< size; ++i) { // 根据索引获取记录集合中的记录 Entry entry = entries.get(i); // 根据记录的字段名查询字段值 // 注意字段值的类型不同需要选择相应的get方法 user_name_bytes_list[uint256(i)] =entry.getBytes32("name"); item_id_list[uint256(i)] =entry.getInt("item_id"); item_name_bytes_list[uint256(i)] =entry.getBytes32("item_name"); }



更新记录核心代码如下:



TableFactory tf =TableFactory(0x1001);
Table table =tf.openTable("t_test");
Entry entry = table.newEntry();
entry.set("item_name",item_name);
Condition condition =table.newCondition();
// 设置过滤条件
condition.EQ("name",name);
condition.EQ("item_id",item_id);
// 调用Table的update方法更新记录
// 返回更新影响的记录数,值大于0则更新成功,否则更新失败
int count = table.update(name, entry,condition);



删除记录核心代码如下:



TableFactory tf =TableFactory(0x1001);
Table table =tf.openTable("t_test");
Condition condition =table.newCondition();
condition.EQ("name",name);
condition.EQ("item_id",item_id);
// 调用Table的remove方法删除记录
// 返回删除影响的记录数,值大于0则删除成功,否则删除失败
int count = table.remove(name,condition);



SDKCRUD Sevice接口


通过CRUD合约,我们可以看到只要合约方法涉及到数据读写操作,则首先打开对应的表,然后调用读写相关接口就可以进行区块链数据的读写。


同时,我们注意到,CRUD合约的开发方式还是离不开编写合约、编译合约、部署合约,最后才能调用合约实现相关功能。


那么有没有更方便,更简洁的方式呢?比如开发者合约都不用写,不用部署合约,就可以读写区块链上的数据。答案显而易见,我们已经实现了这样的极致易用需求。


FISCO BCOS SDK提供CRUD Service数据上链接口,这些接口实现的原理是调用区块链内置的一个预编译的CRUD合约,专门负责对用户表进行增删改查操作。


Java SDKCRUD Service实现在(Python SDK和Nodejs SDK类似)org.fisco.bcos.web3j.precompile.crud.CRUDService类,其接口如下:



以上接口覆盖了表的创建、查看和增删改查操作。用户只需要调用SDK的接口就能完成相关操作,具体示例以下地址查看。

https://github.com/FISCO-BCOS/web3sdk/blob/master/src/integration-test/java/org/fisco/bcos/precompile/CRUDServiceTest.java


其中调用写接口会产生与调用CRUD合约接口等效的交易,需要共识节点共识一致后才会落盘存储。值得关注的是,利用CRUD Service接口,FISCO BCOS控制台针对每个接口实现了易用的sql语句命令,欢迎访问以下地址体验。

https://fisco-bcos-documentation.readthedocs.io/zh_CN/latest/docs/manual/console.html


两种CRUD使用方式的比较


可能用户有这样的疑问,CRUDService接口如此简洁易用,那么是不是我们就只依靠这组接口完成区块链业务呢?其实也不尽然,相比于CRUD合约,其局限主要在两点:


有哪些Effective CRUD最佳实践?


CRUD功能虽然简单易用,但也需要清晰的知道如何高效的使用CRUD。


条款1:绝大多数情况下请选择CRUD合约开发区块链应用


区块链应用开发中,如果确定仅仅只是上链数据且数据只采用用户表存储,不依赖合约执行相关业务逻辑,那么考虑使用CRUD Service接口。否则,推荐使用CRUD合约。另外,为了方便开发和调试,可以随时利用CRUD Service的读接口(select和desc接口)或者利用控制台对应的相关命令进行数据查询。

条款2:Table.sol文件内容不可修改


Table.sol文件定义的是CRUD功能相关的抽象接口,每个接口区块链均有对应的具体实现。如果用户修改该文件,会导致交易执行失败或功能异常等问题。

条款3:CRUD用户表的主键不唯一


用户习惯于关系型数据库中的主键字段是唯一的,因此默认CRUD用户表中的主键也是唯一的,其实并不是。插入多条记录时,可以指定相同的主键。主键不唯一是由于CRUD表中存储的是主键到对应Entries的映射,进而基于主键进行增删改查。因此,调用CRUD接口(插入、更新、查询和删除记录)时,均需要传入主键值(不是主键名)。另外,主键值过长,会影响读写效率,建议主键值不要设置太长。


条款4:使用CRUD合约时,表字段过多,使用数组或struct封装字段参数


由于Solidity合约语言的限制,一个合约方法中的局部变量个数不得超过16个,否则会出现"Stacktoo deep, try removing local variables"编译错误。因此对于表字段比较多的情况,可以考虑将多个字段参数使用数组或struct封装,以减少局部变量个数,使其满足编译条件。


条款5:使用CRUD合约时,方法中如果涉及表操作,均需要先打开表


TableFactory抽象合约有openTable(string)方法,该方法返回打开的表对象。我们可能会将该表对象设置为合约的全局变量,方便下次操作表时直接使用。注意这是错误操作,因为每次打开表时,其获取的表对象注册在一个临时地址上,在下一个区块之后,该地址不再有效,因此不能将表对象设置为全局变量使用,而是当方法需要操作表之前,均需要打开表。另外,由于TableFactory在区块链中注册的是固定地址0x1001,因此可以将TableFactory对象设置为全局变量,不必每次均创建该对象。


条款6:使用CRUD合约,可以采用表和状态变量混合存储数据


CRUD合约是具备CRUD功能的Solidity合约,因此Solidity之前的状态变量存储方式依然可以使用。当存储一个变量值时,可能直接使用一个变量存储比较方便,因此可以既定义几个状态变量,同时也创建几个用户表进行数据存储。对于比较结构化的数据,推荐使用表的存储方式。


条款7:采用权限控制管理用户表


CRUD合约的逻辑与用户表数据分离,因此通过合约接口的限制已控制不住用户表的写操作(包括插入、更新和删除操作)。为了避免任何账户均可以写用户表,推荐使用权限控制来管理用户表(点击参考权限控制具体使用)。指定特定账户才有权限写用户表。


分享至: