文章
· 二月 22, 2023 阅读大约需 12 分钟

InterSystems IRIS的面向对象数据库特性

面向对象编程的优势

在应用程序开发时,我们使用的大多数开发语言都是面向对象编程 object-oriented programming (OOP)语言,例如大家熟悉的Java、.NET。而TIOBE的2023年2月的最新开发语言流行排行榜上,前5大语言都是面向对象编程语言,连排名第六的Visual Basic都有了越来越多的OO特性:

为什么使用面向对象编程这么流行?因为它有诸多优势:

  • 封装:将数据和操作数据的代码封装在一个单元中,在确定范围的数据上进行编程。方便代码的开发、管理与分享。
  • 抽象:将业务数据概括为不同的对象类型,从而进行业务分组开发、简化程序。
  • 继承:一个类可以从另一个类继承它属性和行为,从而实现更大范围的代码复用。
  • 多态:多个对象可以创建自一个类,且可以有不同的行为。一段灵活的代码能实现多种形态的业务,它进一步降低了代码开发量和调用难度。

而这4个优势正是面向对象编程的核心特征。

 

关系型数据库的对象/关系错配

虽然面向对象编程是绝对的主流,但数据通常被保存到关系型数据库中。关系型数据库的行和列二维关系与复杂的对象并不匹配:

  • 面向对象的核心特性:封装、抽象、继承、多态,关系数据库都无法直接支持。
  • 对象可以表达复杂的多维度、多类型的数据集,关系性数据库只能表达二维(行和列)、简单类型的数据集。例如对于集合类型-列表或数组,面向对象可以直接将其作为对象的属性,而关系型数据库只能用额外的关联子表或序列化为字符串/字符流等进行表达。
  • 面向对象间通过继承、引用等方式建立对象间关系,关系性数据库通过外键建立表之间的关系。例如人和患者这两个业务概念,在面向对象编程中可以是两个有继承关系的对象类 – 子类患者的属性和方法多数是由人继承来的;而在关系数据库中,只能表达为没有关系的两张表,无法体现这种关系。
  • 在表达和操作数据的方式上, 面向对象基于封装原则,其中数据和操作该数据的代码被组合到一个单元中。而关系数据库基于规范化原则,其中数据被分成单独的表以最小化数据冗余并提高查询性能。例如患者和其病历这两个业务概念,在面向对象编程中病历是患者对象类中的一个集合属性,通过患者可以直接操作病历集和每一份病历;而在关系数据库中,只能表达为通过外键关联的两张表:患者表和病历表。还要保证一个业务对象操作时对应多张表的事务一致性问题。

 

简单比喻一下这种错配,这就像一辆车,我们使用面向对象方式操作时,是以整车的形式操纵它;但存这辆车时,我们要把它先拆成零件 – 发动机、轮胎、车架… - 分类保存,取车时,再把零件组装起来才能上路。

 

为了应对这种对象关系错配,通常有三种方法:

  • 自己开发数据映射逻辑,将一个对象拆成对应的多张表,自己维护保存对象数据时的SQL(可能是复杂的存储过程)和从SQL获取数据后组合为对象的代码。
  • 使用对象关系映射 (ORM)框架,例如Hibernate,来自动实现对象模型和关系模型间的映射。它增加了系统的复杂度、降低了性能和可靠性。
  • 直接使用面向对象数据库管理系统。

 

InterSystems IRIS的面向对象特性

InterSystems Caché和InterSystems IRIS作为多模型数据库管理系统,其面向对象数据库特性在全球面向对象数据库排名中分列第1、第2位。它提供了丰富的面向对象特性和对包括SQL的支持。

 

完整的OO特性

InterSystems IRIS提供了完整的面向对象特性:封装、多重继承、多态。

封装

InterSystems IRIS可以将数据、逻辑和复杂的模型关系封装在类中。在类中,可以定义:

  • 属性: 支持任意复杂的属性类型,包括简单数据类型、对象型类型、关系型类型、集合类型、透明类型、计算类型等。并支持对属性的复杂约束,例如取值范围约束、模式约束、唯一性约束等
  • 参数:静态属性
  • 方法:操作对象的方法,可以是静态方法或实例方法。方法可以是私有方法,从而不会被继承。InterSystems IRIS提供方法生成器的概念:方法是创建方法实现的程序。使用方法生成器,可以创建类、为不同的情况自动生成特定的应用代码。
  • 索引:用于加速数据检索等索引,支持多种索引类型,包括普通索引、位图索引、位片索引;支持复合索引
  • 查询:封装好的对数据的查询方法,可以基于对象或SQL构建对数据的查询,并可封装为SQL视图SQL存储过程
  • 持久化方案:可以动态调整的底层多维存储方案。调整持久化方案,不影响以对象和SQL方式对数据的操作与查询代码

 

/// 人
Class User.Person Extends (%Persistent, %XML.Adaptor, %Populate)
{
/// 属性: 姓名
/// 类型: 字符串
Property Name As %String;
/// 属性: 性别
/// 类型: 字符串
/// 约束取值范围为男和女
Property Gender As %String(VALUELIST = ",男,女");
/// 属性: 家庭住址
/// 类型: User.Address
Property HomeAddress As User.Address;
/// 索引: 基于Gender的位图索引
Index bmIndGender On Gender [ Type = bitmap ];
}

 

支持纯虚类开发,同时InterSystems IRIS提供庞大的、功能完善的类库,方便用户直接使用或继承。

多重继承

多重继承让一个子类可以同时从多个的父类那里继承属性和行为(方法)。InterSystems IRIS通过多重继承能力,可以快速构建代码。子类可以覆盖父类的属性和方法。

/// 患者,继承与人
Class User.Patient Extends User.Person
{
/// 患者病案号
Property MRN As %String;
}

 

 

多态

InterSystems IRIS多态特性让程序可以用父类打开子类的实例,运行这个实例上的方法时,执行的是子类覆盖过的方法。通过多态特性,避免父类的代码臃肿、降低维护难度。

例如1号实例是人,6号实例是患者。都用类User.Person打开对应对象,并用XMLExport输出对象的XML序列化结果,可以看到6号患者的记录输出了它独有的病案号信息:

USER>do ##class(User.Person).%OpenId(1).XMLExport()

<Person><Name>Ximines,Joe U.</Name><Gender></Gender><HomeAddress><City>Washington</City><Street>3006 Washington Blvd</Street></HomeAddress></Person>

 

USER>do ##class(User.Person).%OpenId(6).XMLExport()

<Patient><Name>Geoffrion,Clint R.</Name><Gender></Gender><HomeAddress><City>Fargo</City><Street>908 Franklin Place</Street></HomeAddress><MRN>H7768</MRN></Patient>

 

 

透明的持久化

通过继承父类%Persistent,任何的类都可以自动获得透明的持久化 (存取到数据库) 能力

  • 能够使用方法来新建、打开、修改、保存和删除对象。
  • 打开一个对象实例并引用对象类型的属性时,系统也会自动打开该对象。这个过程被称为swizzling(也被称为懒惰加载)。然后就可以对该对象进行操作。在下面的例子中,当User.Person对象被打开时,相应的User.Address对象被刷新了。

 

Set person=##class(User.Person).%OpenId(3)
Set person.Name="张小明"
Set person.HomeAddress.City="北京"
Do person.%Save()

同样,当保存一个对象时,系统也会自动保存其所有的对象类型属性 - 这被称为深度保存。InterSystems IRIS可以通过选项设置执行浅保存。

 

ACID

InterSystems IRIS的面向对象编程提供事务管理和锁机制,完全支持ACID事务。

在打开一个持久化对象时,可以指定并发锁定的程度,因为持久化对象有可能被多个用户或多个进程使用。锁定程度选项包括:无锁、原子读、共享锁、共享/保留锁、排他/保留锁。

在操作多个持久化对象时,可以将这些操作纳入一个事务进行管理,从而确保事务完整提交或全部回滚。

 

SQL访问

对于事务性操作来说,面向对象开发非常合适;但对于批量数据操作和数据的查询检索来说,SQL是目前大家最熟悉和便捷的操作语言。多数面向对象数据库使用专用的API来操作数据,而不支持SQL,这影响了开发的便捷性、提高了开发门槛。

InterSystems IRIS在支持面向对象建模和开发的同时,也支持关系型的数据操作,这是它多模型特性的一部分。

父类%Persistent提供了InterSystems IRIS中对象 - SQL投射关系的框架。它提供如下核心能力:

  • 自动建立并维护对象-SQL的投射关系。这种投射是双向的:使用对象建模,会自动投射出SQL表模型;如果使用SQL DDL建模,同样会生成对应的对象模型。这种投射会选择合适的投射逻辑,例如将对象型属性投射为SQL子表、将关系型属性投射为SQL外键。
  • 开发者可以干预对象-SQL的投射关系,从而得到更适合自己的模型。例如对于对象模型中的列表集合类型,默认会投射为表的一个字段,而开发者可以通过设置将它投射为一张子表。
  • 对象-SQL投射并不会拷贝一份不同模型的数据,而是通过投射,将一份数据投射为不同的模型,供开发者在最适合的场景下使用最高效的模型。
  • 投射出的SQL和对象模型同时支持ACID的读写操作。也就是说使用对象方式建模,依然可以使用SQL插入、更新、删除数据。
  • 在类方法中,可以直接使用SQL;同样可以直接将类的Query发布为SQL视图、将类的方法发布为SQL存储过程。在持久化类中,可以定义SQL触发器。
  • 对SQL的对象模式扩展:为了在面向对象应用程序中更容易地使用SQL,InterSystems IRIS提供SQL的一系列对象扩展。其中最有趣的是使用隐式连接运算符("->")提供对象引用的能力,通常被称为 "箭头语法",它可以很大程度上简化SQL的编写。例如,假设你有一个Vendor类,它引用了另外两个类Contact和Region作为属性。在SQL投射上,会看到3张表:Vendor表、Contact表和Region表。要获得Vendor的完整信息,标准SQL需要类似于这样的实现:
SELECT Vendor.ID,Vendor.Name,Contact.Name
FROM Vendor
LEFT OUTER JOIN Contact ON Contact.ID = Vendor.ContactInfo
LEFT OUTER JOIN Region ON Region.ID = Vendor.Region AND Region.Name = '南极洲'

 

使用隐式连接运算符,可以在SQL中把引用类型的字段当作对象,使用相关类的属性

SELECT ID,Name,ContactInfo->Name
FROM Vendor
WHERE Vendor->Region->Name = '南极洲'

 

另外,InterSystems SQL层面也能体现出很多面向对象的特性。例如,上面的User.Person和User.Patient这两个类投射出Person表和Patient表,如果创建了5条Person的记录,又创建了5条Patient记录,在Patient表中将看到5条记录,而在Person表中将看到10条记录,因为这5个Patient也是Person。这种实现极大方便数据的查询和利用,而关系型数据库很难实现。

 

从Patient表中获取到5条Patient记录:

从Person表中,获取到10条记录,包括ID为6-10的Patient记录,因为它们也是Person记录:

 

数据分片

InterSystems IRIS提供数据分片技术(Sharding),将超大数据集分片到不同实例上,形成可弹性横向扩展的集群。

可以在类定义中将持久化类声明为需要数据分片保存的类。同时,可以在数据分片集群上的任何节点上执行对象方法,获得全数据分片集群上其它节点保存的对象数据,并更改它们。

 

列存储

InterSystems IRIS同时支持基于行存储和列存储的储存模型。列存储对于超大数据上的分析类查询能显著降低I/O、利用现代CPU/GPUSIMD(Single Instruction Multiple Data)芯片级优化极大提升性能。

对象模型可以直接声明成列存储模型:

Class Sample.TransactionHistory Extends %Persistent [ DdlAllowed, Final ]
  {
    Parameter STORAGEDEFAULT = "columnar";
    Parameter USEEXTENTSET = 1;
 
    Property AccountNumber As %Integer;
    Property TransactionDate As %Date;
    Property Description As %String(MAXLEN = 10);
    Property Amount As %Numeric(SCALE = 2);
    Property Type As %String(VALUELIST = "-Deposit-Withdrawal-Transfer");
 
    Index BitmapExtent [ Extent, Type = bitmap ];
  }

或仅对对象模型中的特定字段使用列存储的混合模型:

Class Sample.BankTransaction Extends %Persistent [ DdlAllowed ]
  {
    Parameter STORAGEDEFAULT = "row";
    Parameter USEEXTENTSET = 1;
    Property AccountNumber As %Integer;
    Property TransactionDate As %Date;
    Property Description As %String(MAXLEN = 100);
    Property Amount As %Numeric(SCALE = 2, STORAGEDEFAULT = "columnar");
    Property Type As %String(VALUELIST = "-Deposit-Withdrawal-Transfer");
    Index BitmapExtent [ Extent, Type = bitmap ];
  }

无论使用行存储、列存储、混合存储, OOP代码和SQL代码都无需任何修改。

 

多开发语言支持

InterSystems IRIS作为一个多模型数据库,可以向外提供关系型的访问方式;外部开发语言像连接关系型数据库一样,使用XDBCADO.NET连接InterSystems IRIS,并使用SQL操作数据。或者使用Entity Framework.NET对象映射到IRISSQL表。

 

对于面向对象开发而言,InterSystems提供更多的开发语言选择。

内部语言

在使用OOP开发InterSystems IRIS上的类时,使用ObjectScript建立对象模型和方法,也可以使用嵌入式Python开发类方法、或调用外部的Python库。

例如,我们可以直接用嵌入式Python,编写python方法,调用外部的Python库:

/// 使用Python的xmltodict库,将XML转换为JSON
ClassMethod XML2JSON(pXML As %String) [ Language = python ]
{
    import xmltodict, json

    obj = xmltodict.parse(pXML)
    return(json.dumps(obj,ensure_ascii=False))
}

 

 

外部语言

Java.NET Python都是流行的OOP语言。InterSystems IRIS可以直接将Java和.NET对象保存到数据库而无需做对象/关系映射,同时InterSystems IRIS的外部服务器(InterSystems External Servers)特性使InterSystems IRIS和这些OOP语言可以双向使用对方定义的对象。它包括以下特性:

即时访问

该特性使Java.NET Python 对象成为 InterSystems IRIS可以直接操作的对象。当你发出命令时,外部服务器会自动启动,提供对外部语言平台的即时访问,而不需要任何预先设置。

共享的会话

共享会话让 ObjectScript 和外部应用程序在相同的环境和事务中工作,共享相同的双向连接。

远程对象控制

通过代理对象,允许共享会话的任何一方实时控制另一方的目标对象。InterSystems IRIS可以创建和控制 Java.NET Python 对象,这些语言也可以创建和控制 InterSystems IRIS的对象。

完全可重入的双向连接

传统的单向客户端/服务器关系不再是一个障碍。通过双向重入,任何一方的新对象或方法调用都可以随时进入现有的共享会话。这使得共享会话中任何一方的应用程序都可以同时充当服务器和客户端,发起客户端的查询并为另一方的请求提供服务。

可重入的Native SDK方法

Native SDK方法也是完全可重入的,可以包含在共享会话中,使外部应用程序可以直接访问大量的InterSystems IRIS资源。

 

例如,在InterSystems IRIS中创建Java类的 java.util.Date的代理类实例,并执行其方法显示当前日期:

write $system.external.getJavaGateway().new("java.util.Date").toString()

 

 

除了对象型、关系型,InterSystems IRIS还支持多维(或键值对)和文档建模和数据操作方式。它的多模型特性可以帮助开发者用最合适的模型处理数据、获得最佳的开发效率和运行效率。

讨论 (0)1
登录或注册以继续