当前位置: 首页 > news >正文

使用 Oracle 数据库进行基于 JSON 的应用程序开发

本文为“JSON-based Application Development with Oracle Database (and MongoDB compatibility)”翻译+阅读笔记。

副标题为:版本 19c 和 21c,本地和云,自治JSON 数据库以及适用于 MongoDB 的 Oracle 数据库 API,版本为2022年2月,1.1版。

目的

本文档概述了 Oracle Database 19c 和 21c 版本中包含的功能和增强功能以​​及相关的 Oracle 技术。旨在帮助您理解为什么现代应用程序开发经常使用 JSON 作为数据持久化格式,以及为什么 Oracle Database 中的 JSON 功能非常适合满足当今开发人员寻求文档存储来持久化、查询和处理应用程序数据的需求。

模式灵活的应用程序开发

现代应用程序开发在不断变化的环境中进行。用户期望应用程序能够适应快速变化的业务需求,并能够即时交付更新。所有这些都意味着开发人员需要一种灵活的数据持久性机制,以便在应用程序发展过程中最大程度地减少停机时间或DBA的参与。关系模型缺乏这种灵活性:表具有静态的“形状”,应用程序更改需要修改它们(例如添加新列),这通常需要数据库管理员(DBA)来完成。此外,现有数据可能需要修改以适应新的模式。更重要的是:关系方法需要预先进行模式设计:应用程序的对象(例如“客户订单”)被规范化到存储对象值的表和列。一个应用程序对象通常会被规范化到多个表。这意味着简单的put或get操作现在需要对所有参与表进行插入和查询,并满足正确的连接条件。开发人员必须理解这种映射并使用 SQL 来表达它。

这里说的lacks flexibility 和upfront schema design都是事实,但并不表示不好。JSON 文档存储提供了另一种可能性,何时用关系模型,何时用文档存储,要看具体情况。

这种方法虽然已被证明可以持续数十年,但对于现代应用程序开发来说通常被认为过于僵化、正式和缓慢。此外,由于应用程序和数据库的变化通常必须同步,因此停机的可能性更高,运营成本也更高。

文档存储(也称为文档数据库)的工作方式不同,它不需要预先定义架构schema last)。相反,应用程序数据以文档形式建模,通常采用 JSON 格式。每个文档都是自描述的(由命名的键/值对组成),因此无需外部架构来理解这些值。此外,不同的文档可以具有不同的键/值对,这使得应用程序可以轻松地通过动态添加新的键/值对来演进,而无需修改现有的数据/文档。因此,使用文档进行数据持久化可以提供开发人员所要求的灵活存储机制。

由于基于 JSON 的 API 无处不在,处理 JSON 的另一个要求也随之而来:REST 服务使用 JSON 作为输入和输出。如果第三方 API 发生更改,不再与该表匹配,将这些 JSON 值映射到表可能会导致应用程序崩溃。相反,JSON 数据最好“原样”(as is,即不经改变的)存储在支持 JSON 数据查询的数据库中。

NoSQL 文档存储的局限性

开发人员通常倾向于使用 NoSQL 产品,因为它们被认为比关系数据库更易于使用。典型的 NoSQL 文档存储将 JSON 文档组织在集合中。由于数据模型简单,仅由集合和文档组成,这些系统提供的功能也比较简单,在报告或分析用例方面尤其有限Join和Aggregation都不是Document Store擅长的)。

如果出现此类需求,开发人员通常会部署第二个(关系型)数据库并存储两次数据;这通常需要一个 ETL 流程(提取、转换、加载)将数据转换为关系格式。

此外,NoSQL 文档存储通常不支持复杂的事务和引用完整性约束,因此数据一致性现在成了开发人员需要解决的问题。所需的“变通方法”会增加系统复杂性,降低安全性,导致不一致,并产生新的问题,例如跨不同数据库的时间点恢复。

由于这种额外的复杂性,总体拥有成本往往很高,并且不再能兑现简易 NoSQL 产品的承诺。

使用 Oracle 数据库作为文档存储

Oracle 数据库提供与专用 NoSQL 文档存储相同的应用程序开发体验:它可以存储、管理和索引 JSON 文档,并提供与常见 NoSQL 产品类似的 NoSQL 文档存储 API。它甚至支持与 MongoDB(最流行的文档存储之一)兼容的 API。

此外(与 NoSQL 产品不同),Oracle 数据库提供针对 JSON 文档的复杂 SQL 查询、报表、分析和机器学习功能。这使您可以集成 JSON 和关系数据,并将它们连接到同一个查询中

由于 JSON 特性已集成到 Oracle 数据库中,因此其所有企业级特性(例如可用性、安全性、可扩展性、性能和可管理性)均完全支持 JSON 数据。

在 Oracle 数据库中存储和管理 JSON 文档

Oracle 数据库 21c 版新增了 SQL 数据类型“JSON”,该数据类型采用针对快速查询和分段更新进行了优化的二进制格式。

早期版本(例如 19c)允许使用 VARCHAR2、CLOB 或 BLOB 列存储 JSON 文档。“IS JSON”SQL 检查约束可确保该列仅包含有效的 JSON 文档,从而使数据库能够识别该列正在用作 JSON 文档的容器。Oracle 的 JSON 功能专注于为模式灵活的开发和基于文档的存储提供全面支持。

因此,尽管 Oracle 数据库知道给定列包含 JSON 文档,但数据库在存储、索引和查询这些文档时,并不知道其内部结构(键/值对)。开发人员可以根据需要自由更改 JSON 文档的结构。

Oracle 数据库为其所有高级功能(包括灾难恢复、复制、压缩和加密)提供全面的 JSON 支持。此外,支持 Oracle 数据库的产品(例如 Oracle GoldenGate 和 Oracle Data Integrator,以及第三方工具)也无缝支持存储在数据库中的 JSON 文档。

自治 JSON 数据库

Oracle 数据库自 12.1.0.2 版本起就支持 JSON,并新增了许多 JSON 特性。

名为“自治 JSON 数据库”(AJD) 的托管数据库云服务提供了本技术报告中概述的功能,且价格远低于自治数据库系列的其他成员。

除了支持文档存储 API 之外,AJD 还完全能够运行任意 SQL 语句并将非 JSON 数据存储在关系表中。

由于 AJD 面向 JSON 开发人员,因此非 JSON 数据的大小限制为 20GB;如果需要更多数据,只需单击鼠标即可升级到自治事务处理 (ATP) 服务。

因此,AJD 并非一个需要不同技能或 API 的独立开发环境。

作为自治数据库平台的一部分,AJD 用户可充分受益于自治数据库的自主驱动、自主安全和自主修复功能。数据库正常运行时间最大化,自动扩展(最高可达配置 CPU 限制的三倍)功能以最低成本提供最高性能。
更多关于自治 JSON 数据库服务的信息,请访问:
https://www.oracle.com/autonomous-database/autonomous-json-database/

适用于 MongoDB 的 Oracle 数据库 API(用于自治数据库)

所有 Oracle 自治数据库(包括自治 JSON 数据库)均兼容 MongoDB:为 MongoDB 编写的工具、驱动程序和应用程序可以使用 MongoDB 原生 API 连接到 Oracle 自治数据库,该 API 将 MongoDB 数据库操作透明地转换为等效的 SQL/JSON 操作,然后在 Oracle 数据库上执行。

MongoDB 应用程序通过 MongoDB API 进行通信,就像它们仍然连接到 MongoDB 服务器一样。

开发人员可以继续使用他们的 MongoDB 技能和工具,同时还可以对 MongoDB 集合中的 JSON 数据运行 SQL 语句。这使得基于 JSON 数据进行实时 SQL 分析和机器学习成为可能。

还可以从关系数据生成 JSON,并将结果公开为与 MongoDB 兼容的集合,以便 MongoDB 应用程序可以轻松访问查询结果或关系数据。

Oracle Database API for MongoDB 还支持 Compass、mongosh (mongo shell) 和 mongoimport/mongorestore 等 MongoDB 工具,从而简化了迁移到 Oracle 的过程

截至目前(2022 年 2 月),Oracle Database API for MongoDB 最初仅在共享自治数据库 (ADDB) 上可用。详情请访问:
http://docs.oracle.com/en/database/oracle/mongodb-api/mgapi

简单的 Oracle 文档访问 API (SODA)

由于 MongoDB 的 Oracle 数据库 API 目前仅限于共享自治数据库,Oracle 提供了另一个通用的文档存储 API——云端(所有 Oracle 云数据库)和本地均可使用:简单 Oracle 文档访问 (SODA) API。

此 API 的设计初衷是支持模式灵活的应用程序开发,与 MongoDB 等常见的 NoSQL 文档存储 API 非常相似。

使用 SODA,开发者无需学习 SQL 即可处理 JSON 文档和集合。相反,可以通过简单的 API 直接调用针对集合和文档的数据库操作——该 API 不仅支持 REST,还支持 Java、Python、JavaScript (Node.js)、C 和 PL/SQL 等常用编程语言。

SODA for REST 是 Oracle REST 数据服务 (ORDS) 的一部分,可以通过任何支持 REST/HTTP 调用的语言调用。Java、Python、Node.js 和 C 驱动程序均为开源。

SODA 的概念模型与 MongoDB 非常相似:应用程序对象以 JSON 文档的形式存储在集合中。文档用键标识。集合用名称标识。异构集合允许存储非 JSON 对象,例如图像。多个集合驻留在客户端程序连接的数据库中。

可以使用 SODA 命令访问文档,通常用于简单的 CRUD 操作(创建、读取 + 查找、更新、删除),也可以使用 SQL:可以轻松地对同一 JSON 数据进行报告、分析或机器学习。

我们通过 REST 和 Java 的示例来说明 SODA API。SODA 文档以及驱动程序和教程的链接可在此处找到:
https://www.oracle.com/database/technologies/appdev/json.html

SODA示例

以下 Java 代码创建一个集合“orders”,并插入一个 JSON 文档。然后,它检索 SODA 分配给该文档的唯一键 (id)。SODA 也可以接受用户生成的键。

OracleRDBMSClient client = new OracleRDBMSClient(); 
OracleDatabase db = client.getDatabase(conn); 
OracleCollection orders = db.admin().createCollection("orders"); 
OracleDocument doc = orders.insertAndGet(db.createDocument('{…}')); 
String id = doc.getKey();

可以看到,数据库、集合和文档映射到 Java 类,这些类具有公开其功能的函数。
在 SODA for REST 中,HTTP 动词(例如 PUT、POST、GET 和 DELETE)映射到文档上的 SODA 操作。URL 包含文档的键或集合的名称,以及数据库主机名和授权凭据。SODA for REST 是一种 Oracle REST 数据服务,依赖 ORDS 进行身份验证和授权。由于篇幅原因,本示例省略了此部分。创建集合和插入文档这两个操作分别需要一次 REST 调用。第二次调用返回一个带有指定键 (id) 的 HTTP 响应:

curl -X PUT http://<authUrlToOrds>/soda/latest/orders
curl -X POST -H "Content-type: application/json"
--upload-file document.json http://<urlToORDS>/soda/latest/orders
{ "items": [{ 	"id": "A450557094D04957B36346F630CDDF9A", "etag":"C13578001CBBC84022DCF5F7209DBF0E6DFFCC626E3B0400C3", "lastModified": "2021-02-09T01:03:48.291462","created": "2021-02-09T01:03:48.291462" } ], "hasMore": false, "count": 1
}

上述示例展示了文档存储与传统 SQL 数据库的区别:新文档以 JSON 对象的形式添加到集合中。数据库对这些文档中包含的键没有任何限制。对于习惯于面向对象编程环境的开发人员来说,API 调用也更简单。注意:SODA for REST 与其他语言驱动程序(例如 Java)的区别在于,REST 是无状态的,因此所有 REST 操作都会立即提交,而语言驱动程序依赖于支持事务的数据库连接(多个操作可以设置为原子操作)。

现在,让我们使用 SODA 来检索文档。SODA 显然支持通过键获取文档,但一种更有趣的数据查询方式是查找所有满足搜索条件(以 JSON 文档形式表示)的文档,我们称之为 QueryByExample (QBE)。这是一个非常基本的 QBE,它会选择所有字段“region”的值为“north”,且第二个字段“quantity”的值为 10 或更大的文档:

{"region":"north", "quantity":{"$gte":10}}

(可以使用 skip 和 limit 对大量结果进行分页。)
以下 Java 代码片段执行 QBE 搜索,将结果限制为前 100 个文档并打印所有文档键。变量“qbe”的值为上述的QBE。

OracleCollection coll = database.openCollection("orders");
OracleCursor results = coll.find().filter(qbe).limit(100).getCursor();
while (results.hasNext()) {OracleDocument doc = results.next();System.out.println(doc.getKey()); 
}

REST 参数 action=query 表示 POST 包含 QBE 请求。

curl -X POST -H "Content-type: application/json" --data
'{"region":"north", "quantity":{"$gte":10}}' 
http://<urlToORDS>/ords/SCOTT/soda/latest/orders?action=query

对存储在 Oracle 数据库中的 JSON 内容进行分析和报告

如上所述,Oracle 数据库为应用程序开发提供了 NoSQL 文档存储的诸多优势。使用 Oracle 数据库的一大优势在于,它还能将 SQL 的全部功能应用于相同的 JSON 文档。这得益于 JSON 集合由自动创建的常规表支持。它们包含一个用于存储文档的 JSON 列,以及用于唯一键 (ID) 和元数据(例如创建日期)的附加列。订单集合由以下表支持:

SQL> describe "orders"
Name            Null?     Type
----------------------------------------
ID              NOT NULL  VARCHAR2(255)
CREATED_ON      NOT NULL  TIMESTAMP(6)
LAST_MODIFIED   NOT NULL  TIMESTAMP(6)
VERSION         NOT NULL  VARCHAR2(255)
JSON_DOCUMENT   		  JSON

Oracle 数据库支持多种 SQL 操作符来处理 JSON:

操作描述
IS JSON测试表达式是否包含 JSON
JSON_Value提取标量 SQL 值
JSON_Query提取 JSON 片段
JSON_Exists测试是否满足一个或多个条件
JSON_TextContains对 JSON 字段进行全文搜索
JSON_Table将 JSON 投影到关系模型
JSON_Object[Agg]生成 JSON 对象
JSON_Array[Agg]生成 JSON 数组
JSON_Transform修改 JSON,例如作为更新的一部分
JSON_Mergepatch合并两个 JSON 对象
JSON_Dataguide用于构建模式的 JSON 示例

许多运算符依赖于路径表达式来在 JSON 数据中进行导航,并可选地使用路径谓词过滤。运算符和路径表达式的详细描述可以在 JSON 开发者指南中找到:
https://docs.oracle.com/en/database/oracle/oracle-database/21/adjsn/

对于以下示例,我们假设此集合/表包含采购订单文件:

{"PONumber": 1600,"Reference": "ABULL-20140421","Requestor": "Alexis Bull","User": "ABULL","CostCenter": "A50","Instructions": {"name": "Alexis Bull","Address": {"street": "200 Sporting Green","city": "South San Francisco","state": "CA","zipCode": 99236,"country": "United States of America"},"Phone": [{"type": "Office","number": "823-555-9969"}]},"Special Instructions": "Counter to Counter","LineItems": [...]
}

使用 SQL 查询 JSON 数据的最简单方法是简单的点符号,它允许导航 JSON 结构并选择值。

SQL> select j.PO_DOCUMENT.Reference,j.PO_DOCUMENT.Requestor,j.PO_DOCUMENT.CostCenter,j.PO_DOCUMENT.Instructions.Address.cityfrom J_PURCHASEORDER jwhere j.PO_DOCUMENT.PONumber = 1600;REFERENCE            REQUESTOR     COSTCENTER   SHIPPINGINSTRUCTIONS
-------------------- -------------- ------------ ---------------------
ABULL-20140421       Alexis Bull   A50          South San Francisco

JSON_TABLE 是一个常用于将 JSON 数据映射到关系模型的表函数,以便访问它。这有很多好处:

  • 关系模型非常适合分析型查询,尤其是维度和事实存储在不同集合中的数仓查询。使用物化视图甚至可以“预先计算”这些连接。
  • 操作关系模型的工具可以用来处理 JSON,例如报表生成器、仪表板和机器学习,它们可以直接应用于 JSON 数据。
  • 数据分析师可以运用 SQL 语言和调优技能,无需手动将 JSON 数据映射到表或编写自定义代码。

JSON_TABLE 使用一组 JSON 路径表达式将 JSON 文档中的内容投影为虚拟表中的关系列。您可以在 SQL 查询的 FROM 子句中使用 JSON_TABLE 表达式,就像使用关系表一样。以下示例从 JSON 文档集合中投影一组列。每个 JSON 路径表达式从文档返回一个标量值,因此为每个文档生成虚拟表中的一行。

SQL> select jt.*from J_PURCHASEORDER p,JSON_TABLE(p.PO_DOCUMENT, '$' columnsPO_NUMBER NUMBER(10) path '$.PONumber',REFERENCE VARCHAR2(30 CHAR) path '$.Reference',REQUESTOR VARCHAR2(32 CHAR) path '$.Requestor',USERID VARCHAR2(10 CHAR) path '$.User',COSTCENTER VARCHAR2(16 CHAR) path '$.CostCenter',TELEPHONE VARCHAR2(16 CHAR) path '$.Instructions.Phone[0].number') jtwhere PO_NUMBER > 1599 and PO_NUMBER < 1602;PO_NUMBER   REFERENCE        REQUESTOR    USERID  COSTCENTER  TELEPHONE
----------- --------------- ------------- ------- ----------- --------------
1600        ABULL-20140421   Alexis Bull  ABULL  A50         909-555-7307
1601        ABULL-20140423   Alexis Bull  ABULL  A50         909-555-91192 rows selected.

JSON_TABLE 还支持带有嵌套数组的 JSON 文档:NESTED PATH 迭代嵌套的“LineItems”数组:嵌套数组外的值被重复(PO_NUMBER),因为它们适用于整个嵌套数组。

SQL> select jt.* from J_PURCHASEORDER p,JSON_TABLE(p.PO_DOCUMENT, '$' columns(PO_NUMBER NUMBER(10) path '$.PONumber',REFERENCE VARCHAR2(30 CHAR) path '$.Reference',NESTED PATH '$.LineItems[*]' columns(ITEMNO NUMBER(16) path '$.ItemNumber',DESCRIPTION VARCHAR2(32) path '$.Part.Description',UPCCODE VARCHAR2(14) path '$.Part.UPCCode',QUANTITY NUMBER(5,4) path '$.Quantity',UNITPRICE NUMBER(5,2) path '$.Part.UnitPrice')) jtwhere PO_NUMBER > 1599 and PO_NUMBER < 1602;PO_NUMBER   REFERENCE        ITEMNO DESCRIPTION                UPCCODE   QUANTITY  UNITPRICE
----------- ---------------  ------ -------------------------- --------  --------  ----------
1600        ABULL-20140421   1      One Magic Christmas        13131092  9         19.95
1600        ABULL-20140421   2      Lethal Weapon              8539162   5         19.95
1601        ABULL-20140423   1      Star Trek 34               9736600   1         19.95
1601        ABULL-20140423   2      New Blood                  4339605   8         19.95
1601        ABULL-20140423   3      The Bat                    1313111   3         19.95
1601        ABULL-20140423   4      Standard Deviants          6318650   7         27.95
1601        ABULL-20140423   5      Darkman 2                  2519203   7         19.957 rows selected.

使用 JSON_TABLE,可以将任意复杂的 JSON 结构投影到关系模型中。JSON_TABLE 查询可以作为视图公开——对于该视图的任何使用者来说,JSON 数据的访问方式就像一个由行和列组成的常规表,其中包含标量值。这也使得使用不支持 JSON 数据模型的关系工具成为可能。

JSON 数据指南

JSON_TABLE 的一个常见用途是创建关系视图,允许不了解 JSON 的用户和工具处理文档。JSON_Dataguide 可以通过对集合中的所有 JSON 文档进行采样并识别字段名称和数据类型来自动创建视图。以下示例展示了如何自动创建视图“order_view”。视图定义包含一个与上述类似的 JSON_Table 表达式。

DECLAREdg CLOB;  -- This variable stores the derived JSON schema
BEGIN-- JSON_Dataguide samples all documents and builds a JSON schemaSELECT JSON_Dataguide(json_document, dbms_json.FORMAT_HIERARCHICAL) INTO dg FROM orders;-- Using this JSON schema, a JSON_TABLE view can be automatically createddbms_json.create_view('order_view', 'orders', 'json_document', dg);
END

JSON 生成

Oracle 数据库还可以从关系以及 JSON 数据生成新的 JSON 数据。例如,这可以生成 JSON 格式的报告。
以下示例展示了如何连接示例表“员工”和“部门”中的数据,并将结果作为新的 JSON 文档返回。

SELECT JSON_ObjectAgg(d.name VALUE (SELECT JSON_ArrayAgg(JSON_Object(e.name))FROM employees eWHERE e.department_no = d.department_no))
FROM departments d;-----------------------------------------------
{"ACCOUNTING": [{"name": "CLARK"},{"name": "KING"},{"name": "MILLER"}],"RESEARCH": [{"name": "SMITH"},{"name": "JONES"},...]
}

请注意,SQL/JSON 生成运算符只是添加到常规 SQL 查询中,这再次展示了 JSON 和表在 O​​racle 数据库中的良好结合使用。还可以将生成的文档插入到集合中,以便 SODA 或 MongoDB API 访问。

结论:为什么使用 Oracle 数据库作为文档存储?

本技术报告介绍了 Oracle 数据库的特性,这些特性支持使用存储在集合中的 JSON 文档进行模式灵活的开发。如今,许多 NoSQL 系统都支持这种开发范式。为什么组织应该选择 Oracle 数据库而不是 NoSQL 系统呢?

Oracle数据库专为企业级应用而构建。许多现代企业级关系型数据库理所当然的功能,典型的NoSQL文档存储却无法提供

  • 复杂的索引、查询优化和并行执行
  • 完全符合ACID的事务,且不受大小/时长限制。
  • 高级安全功能,例如数据屏蔽和密钥管理
  • 数据管理功能,例如压缩和数据归档
  • 强大的备份功能,支持对象级时间点恢复
  • 内置过程式语言和服务器端函数

NoSQL 系统通常缺乏报告和分析操作的功能。随着 JSON 文档的数量和价值不断增长,对跨文档报告和分析功能的需求也日益增长。以前,开发人员必须从 NoSQL 导出数据,并应用复杂的 ETL(提取、转换和加载)流程,才能将其用于支持灵活报告的数据存储。虽然许多 NoSQL 系统现在都认识到需要使用表结构化格式来访问数据,有些系统甚至引入了类似 SQL 的基本语言,但 Oracle Database 凭借其先进的 SQL 分析功能和可扩展的并行 SQL 基础架构,如今已将成熟的 ISO 标准 SQL 的全部功能提供给 JSON 文档存储。

使用 NoSQL 文档存储的组织还必须面对数据可能被孤立(数据孤岛)起来的问题:他们的关系数据由一个数据库管理,而 JSON 文档则由另一个数据库管理。使用单独的 JSON 文档数据存储意味着,当需要将以 JSON 格式存储的信息与组织管理的其他类型的数据(通常包括关系数据)相结合时,即使是最基本的任务,也需要开发和维护特殊的应用程序代码需要集成)。

Oracle 融合数据库提供专为应用程序开发人员设计的文档存储功能,同时允许这些应用程序开发人员利用 Oracle 成熟数据库平台的所有其他优势。

相关文章:

  • Centos安装Dockers+Postgresql13+Postgis3.1
  • C++ 中 std::thread 的高级应用
  • 一篇文章学会开发第一个ASP.NET网页
  • leetcode刷题——判断对称二叉树(C语言版)
  • 若依项目部署小结
  • 【KWDB 创作者计划】_上位机知识篇---MQTT协议
  • Java单例模式详解:实现线程安全的全局访问点
  • Spring Security:企业级安全架构的设计哲学与工程实践
  • 块压缩[比如etc] vs 传统图片压缩 优缺点对比
  • 【KWDB 创作者计划】_深度学习篇---向量指令集
  • 使用rclone迁移minio文件
  • Kubelet 可观测性最佳实践
  • 【C/C++】插件机制:基于工厂函数的动态插件加载
  • 2025年渗透测试面试题总结-拷打题库13(题目+回答)
  • 【redis】主从复制
  • 程序员学英文之Shipment Claim 运输和索赔
  • Node.js学习
  • Vite/Rollup 模块热更新
  • Python内置函数---bytes()
  • MySQL基础增删改
  • 生态环境部:我国核电规模全球第一,总体安全可控
  • 上海小朋友喜欢读什么书?来看这份“少年儿童阅读报告”
  • 习近平对双拥工作作出重要指示
  • 新任乒协副主席马龙:感谢刘国梁,愿把经验传给年轻运动员
  • 外汇局:将持续强化外汇形势监测,保持汇率弹性,坚决对市场顺周期行为进行纠偏
  • 柬埔寨人民党中央外委会副主席:柬中友谊坚如钢铁,期待更多合作