文章目录

  • 第八章 Caché 使用持久对象
  • 保存对象
    • 回滚
    • 保存对象和事务
  • 测试保存对象的存在性
    • 使用ObjectScript测试对象是否存在
    • 使用SQL测试对象是否存在
  • 打开保存的对象
    • 多次调用%OpenId()
    • 并发
  • Swizzling(懒加载)
  • 从磁盘重新加载对象
  • 读取存储值
  • 删除保存的对象
    • %DeleteId()方法
    • %DeleteExtent()方法
    • %KillExtent()方法
  • 访问对象标识符
  • 对象并发选项
    • 为什么指定并发?
    • 并发值
    • 并发性懒加载对象
  • 版本检查(并发参数的替代方法)

第八章 Caché 使用持久对象

%Persistent类是可以保存(写入磁盘)的对象的API。本章描述了如何使用这个API。本章中的信息适用于%Persistent的所有子类。

保存对象

若要将对象保存到数据库,请调用其%save()方法。例如:

/// d ##class(PHA.OP.MOB.Test).TestSaveObject()
ClassMethod TestSaveObject()
{Set obj = ##class(Sample.Person).%New()Set obj.Name = "姚鑫"Set obj.SSN="111-11-1112"Set sc = obj.%Save()q sc
}
DHC-APP> w ##class(PHA.OP.MOB.Test).TestSaveObject()
1

方法 %Save()的返回的是:返回一个%Status值,该值指示保存操作是否成功。例如,如果对象具有无效的属性值或违反唯一性约束,则可能发生故障;

调用对象上的%Save()会自动保存所有被修改对象:也就是说,如果需要,所有嵌入的对象、集合、流、引用对象和涉及对象的关系都会自动保存。整个保存操作作为一个事务执行:如果任何对象保存失败,整个事务将失败并回滚(对磁盘不做任何更改;所有内存中的对象值都是调用%Save()之前的值。

当对象第一次被保存时,%Save()方法的默认行为是自动为其分配一个对象ID值,该值用于以后在数据库中查找对象。在默认情况下,使用$Increment函数生成ID;另外,类可以根据具有idkey索引的属性值使用用户提供的对象ID(在本例中,属性值不能包含字符串“||”)。一旦赋值,就不能更改特定对象实例的对象ID值(即使它是用户提供的ID)。

可以使用% ID()方法找到保存对象的对象ID值:

ClassMethod TestSaveObjectOpen()
{   Set person = ##class(Sample.Person).%OpenId(6)Write "Object ID: ",person.%Id(),!q ""
}
DHC-APP>w ##class(PHA.OP.MOB.Test).TestSaveObjectOpen()
Object ID: 6

更详细地说,%Save()方法执行以下操作:

  1. 首先,它构建一个称为“SaveSet”的临时结构。SaveSet是一个简单的图,它包含对每个对象的引用,这些对象可以从被保存的对象中访问到。(通常,当一个对象类A的一个属性的值是另一个对象类B时,A的一个实例可以“访问”B的一个实例)。SaveSet的目的是确保涉及复杂的相关对象集的save操作得到尽可能有效的处理。SaveSet还解决对象之间的任何save order依赖项。

当将每个对象添加到SaveSet时,如果存在,将调用它的%OnAddToSaveSet()回调方法。

  1. 然后,依次访问SaveSet中的每个对象,并检查它们是否被修改(也就是说,自从打开或最后保存对象以来,它们的属性值是否被修改)。如果一个对象被修改了,那么它将被保存。
  2. 在保存之前,对每个修改后的对象进行验证(对其属性值进行测试;调用它的%OnValidateObject()方法(如果存在);测试唯一性约束);如果对象有效,则执行保存。如果任何对象无效,则调用%Save()将失败,并回滚当前事务。
  3. 在保存每个对象之前和之后,如果存在% OnBeforeSave()和%OnAfterSave()回调方法,则调用它们。

这些回调传递一个Insert参数,该参数指示对象是被插入(第一次保存)还是被更新。

如果其中一个回调方法失败(返回一个错误代码),那么对%Save()的调用将失败,并且当前事务将回滚。

如果当前对象未修改,则%Save()不将其写入磁盘;它返回成功,因为对象不需要保存,因此,不可能存在保存失败。事实上,%Save()的返回值表明,保存操作要么完成了请求的所有操作,要么无法完成请求的所有操作(而且不明确是否向磁盘写入任何内容)。

重要:在多进程环境中,一定要使用适当的并发控制;

回滚

%Save()方法自动将其SaveSet中的所有对象保存为单个事务。如果这些对象中的任何一个保存失败,那么整个事务将回滚。在这个回滚,Caché 做以下工作:

  1. 返回分配的ID。
  2. 可以恢复删除的ID。
  3. 可以恢复修改过的位和数据。
  4. 调用%OnRollBack()回调方法(如果已实现),用于已成功序列化的任何对象。Caché 不会调用此方法,对于未成功序列化的对象(即无效的对象)。

保存对象和事务

如前所述,%Save()方法自动将其SaveSet中的所有对象保存为单个事务。如果这些对象中的任何一个保存失败,那么整个事务将回滚。但是,如果希望将两个或多个不相关的对象保存为单个事务,则必须将对%save()的调用封装在显式事务中:也就是说,必须使用TSTART命令启动事务,并使用TCOMMIT命令结束事务。

例如:

/// d ##class(PHA.OP.MOB.Test).TestSaveTransaction()
ClassMethod TestSaveTransaction()
{// start a transactionTSTARTSet obj = ##class(Sample.Person).%New()Set obj.Name = "姚鑫"Set obj.SSN="111-11-1113"Set sc = obj.%Save()w sc,!// save second object (if first was save)If ($$$ISOK(sc)) {Set obj2 = ##class(Sample.Person).%New()Set obj2.Name = "姚鑫"Set obj2.SSN="111-11-1114"Set sc = obj2.%Save()}w sc,!// if both saves are ok, commit the transactionIf ($$$ISOK(sc)) {TCOMMIT}
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestSaveTransaction()
1
1

再次调用

DHC-APP>d ##class(PHA.OP.MOB.Test).TestSaveTransaction()
0 °?Sample.Person:SSNKey:^Sample.PersonI("SSNKey"," 111-11-1113")??%SaveData+18^Sample.Person.1    DHC-APP??"e^%SaveData+18^Sample.Person.1^7(e^%SerializeObject+9^Sample.Person.1^2e^%Save+8^Sample.Person.1^5.e^zTestSaveTransaction+6^PHA.OP.MOB.Test.1^1d^^^0
0 °?Sample.Person:SSNKey:^Sample.PersonI("SSNKey"," 111-11-1113")??%SaveData+18^Sample.Person.1    DHC-APP??"e^%SaveData+18^Sample.Person.1^7(e^%SerializeObject+9^Sample.Person.1^2e^%Save+8^Sample.Person.1^5.e^zTestSaveTransaction+6^PHA.OP.MOB.Test.1^1d^^^0

关于这个例子有两点需要注意:

  1. %Save()方法知道是否在一个封闭的事务中调用它(因为系统变量$TLEVEL将大于0)。
TL1:DHC-APP>w $tlevel
1

如果事务中的%Save()方法失败,则回滚整个事务(调用TROLLBACK命令)。这意味着应用程序必须测试显式事务中对%Save()的每个调用,如果一个调用失败,则跳过对其他对象调用%Save(),并跳过调用最后的TCOMMIT命令。

测试保存对象的存在性

有两种基本的方法来测试一个特定的对象实例是否存储在数据库中:

  • 使用 ObjectScript
  • 使用 SQL

在这些示例中,ID是一个整数,默认情况下,Caché 就是这样生成ID的.下一章将介绍如何定义一个类,使ID基于对象的惟一属性。

使用ObjectScript测试对象是否存在

%ExistsId()类方法检查指定的ID;如果指定的对象存在于数据库中,则返回true值(1),否则返回false(0)。所有从%Persistent继承的类都可以使用它。例如:

/// d ##class(PHA.OP.MOB.Test).TestObjectScriptExists()
ClassMethod TestObjectScriptExists()
{Write ##class(Sample.Person).%ExistsId(1),!   // should be 1 Write ##class(Sample.Person).%ExistsId(-1),!  // should be 0
}
TL1:DHC-APP>d ##class(PHA.OP.MOB.Test).TestObjectScriptExists()
1
0

这里,第一行应该返回1,因为是Sample.Person从%Persistent继承,SAMPLES数据库为该类提供数据。
还可以使用%Exists()方法,该方法需要一个OID而不是ID。

使用SQL测试对象是否存在

要测试使用SQL保存的对象是否存在,可以使用SELECT语句来选择其%ID字段与给定ID匹配的行。(已保存对象的标识属性被投影为%ID字段。)

例如,使用嵌入式SQL:

/// d ##class(PHA.OP.MOB.Test).TestSQLExists()
ClassMethod TestSQLExists()
{&sql(SELECT %ID FROM Sample.Person WHERE %ID = '1')Write SQLCODE,!  &sql(SELECT %ID FROM Sample.Person WHERE %ID = '-1')Write SQLCODE,!
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestSQLExists()
0
100

在这里,第一种情况结果SQLCODE为0(表示成功),因为Sample.Person从%Persistent继承,sample数据库为该类提供数据。第二种情况结果SQLCODE为100,这意味着语句成功执行,但是没有返回任何数据。符合预期想象,因为系统不会自动生成小于零的ID值。

打开保存的对象

要打开一个对象(将对象实例从磁盘加载到内存中),使用%OpenId()方法,方法如下:

classmethod %OpenId(id As %String, concurrency As %Integer = -1, ByRef sc As %Status = $$$OK) as %ObjectHandle
  • id 是要打开的对象的ID。在这些示例中,ID是一个整数。下一章将介绍如何定义一个类,使ID基于对象的惟一属性。
  • concurrency 用于打开对象的并发级别(锁定)。
  • sc 通过引用传递的是一个%Status值,该值指示调用是否成功或失败。

如果方法可以打开给定对象,则返回OREF。如果无法找到或以其他方式打开对象,则返回空值("")。

例如:

/// d ##class(PHA.OP.MOB.Test).TestOpeningSavedObjects()
ClassMethod TestOpeningSavedObjects()
{Set person = ##class(Sample.Person).%OpenId(10)Write "Person: ",person,!   Set person = ##class(Sample.Person).%OpenId(-10)Write "Person: ",person,!
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestOpeningSavedObjects()
Person: 1@Sample.Person
Person:

注意,在Cache Basic中,OpenId命令相当于%OpenId()方法:

/// d ##class(PHA.OP.MOB.Test).TestOpeningSavedObjectsBasic()
ClassMethod TestOpeningSavedObjectsBasic() [ Language = basic ]
{person = OpenId Sample.Person(1)PrintLn "Name: " & person.Name
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestOpeningSavedObjectsBasic()
Name: yaoxin

还可以使用%Open()方法,该方法需要一个OID而不是ID。

多次调用%OpenId()

如果在Caché 进程中多次调用相同ID的%OpenId(),那么在内存中只创建一个对象实例:所有对%OpenId()的后续调用都将返回对已加载到内存中的对象的引用。

下面的例子说明了这一点:

/// d ##class(PHA.OP.MOB.Test).TestMultiple()
ClassMethod TestMultiple() [ Language = basic ]
{personA = OpenId Sample.Person(1)personA.Name = "Black,Jimmy Carl"personB = OpenId Sample.Person(1)PrintLn "NameA: " & personA.NamePrintLn "NameB: " & personB.Name
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestMultiple()
NameA: Black,Jimmy Carl
NameB: Black,Jimmy Carl

并发

方法的输入是一个可选的并发参数。此参数指定用于打开对象实例的并发级别(锁的类型)。

如果%OpenId()方法无法获取对象上的锁,则会失败。

若要提高或降低对象的当前并发设置,请使用%OpenId()重新打开该对象并指定不同的并发级别。例如,

 Set person = ##class(Sample.Person).%OpenId(6,0)

打开0的并发和以下有效地升级到4的并发:

Set person = ##class(Sample.Person).%OpenId(6,4)

Swizzling(懒加载)

如果打开(加载到内存中)一个持久对象的实例,并使用它引用的对象,那么这个引用的对象将被自动打开。这个过程被称为swizzling;它有时也被称为“懒加载”。

例如,下面的代码打开Sample.Employee一个实例,并自动懒加载相关Sample.Company对象引用它使用点语:

/// d ##class(PHA.OP.MOB.Test).TestSwizzling()
ClassMethod TestSwizzling()
{// Open employee "101"Set emp = ##class(Sample.Employee).%OpenId(101)// Automatically open Sample.Company by referring to it:Write "Company: ",emp.Company.Name,!
}

当一个对象被swizzled时,它将使用默认的原子读并发值打开。

当没有对象或变量引用swizzled对象时,它就从内存中删除。

注意:所谓懒加载,就是对象引用。

从磁盘重新加载对象

要用存储在数据库中的值重新加载内存中的对象,请调用它的%reload()方法。

/// d ##class(PHA.OP.MOB.Test).TestReload()
ClassMethod TestReload()
{// Open person "1"Set person = ##class(Sample.Person).%OpenId(1)Write "Original value: ",person.Name,!// 修改对象Set person.Name = "Black,Jimmy Carl"Write "Modified value: ",person.Name,!// 重新加载对象Do person.%Reload()Write "Reloaded value: ",person.Name,!
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestReload()
Original value: yaoxin
Modified value: Black,Jimmy Carl
Reloaded value: yaoxin

读取存储值

假设打开了一个持久对象的实例,修改了它的属性,然后希望在保存对象之前查看数据库中存储的原始值。最简单的方法是使用SQL语句(SQL总是针对数据库执行;而不是针对内存中的对象)。

例如:

/// d ##class(PHA.OP.MOB.Test).TestReading()
ClassMethod TestReading()
{// Open person "1"Set person = ##class(Sample.Person).%OpenId(1)Write "Original value: ",person.Name,!// 修改对象Set person.Name = "Black,Jimmy Carl"Write "Modified value: ",person.Name,!// 重新加载对象Set id = person.%Id()&sql(SELECT Name INTO :nameFROM Sample.Person WHERE %ID = :id)Write "Disk value: ",name,!
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestReading()
Original value: yaoxin
Modified value: Black,Jimmy Carl
Disk value: yaoxin

删除保存的对象

持久性接口包括从数据库中删除对象的方法。

%DeleteId()方法

方法的作用是:删除存储在数据库中的对象。具体方法如下:

classmethod %DeleteId(id As %String, concurrency As %Integer = -1) as %Status
  • id 是打开的对象的ID
  • concurrency 删除对象时使用的并发级别(锁定)。

例如:

 Set sc = ##class(MyApp.MyClass).%DeleteId(id)

%DeleteId()返回一个%Status值,该值指示对象是否被删除。

%DeleteId()在删除对象之前调用%OnDelete()回调方法(如果存在)。%OnDelete()返回一个%Status值;如果%OnDelete()返回一个错误值,那么对象将不会被删除,当前事务将回滚,%DeleteId()返回一个错误值。

注意,%DeleteId()方法对内存中的任何对象实例都没有影响。

您还可以使用%Delete()方法,该方法需要OID而不是ID。

%DeleteExtent()方法

%DeleteExtent()方法:删除继承内的所有对象(以及对象的子类)。具体来说,它遍历整个继承并在每个实例上调用%DeleteId()方法。

%KillExtent()方法

%KillExtent()方法直接删除存储对象范围的全局变量(不包括与流关联的数据)。它不调用%DeleteId()方法,也不执行引用完整性操作。此方法只是为了在开发过程中为开发人员提供帮助。(它类似于在旧的关系数据库产品中找到的TRUNCATE表命令。)

注意:%KillExtent()仅用于开发环境,不应在实际应用程序中使用。%KillExtent()绕过约束和用户实现的回调,可能会导致数据完整性问题。

访问对象标识符

如果保存了对象,那么它有一个ID和一个OID,这是磁盘上使用的永久标识符。如果对象有一个OREF,则可以使用它来获得这些标识符。
要查找与OREF关联的ID,请调用对象的%ID()方法。例如:

write oref.%Id()

要查找与OREF关联的OID,有两个选项:

  1. 可以调用对象的%Oid()方法。例如:
write oref.%Oid()
  1. 可以访问对象的%%OID属性。由于此属性名包含%字符,因此必须将名称括在双引号中。例如:
 write oref."%%OID"
/// d ##class(PHA.OP.MOB.Test).TestIdentifiers()
ClassMethod TestIdentifiers()
{// Open person "1"Set person = ##class(Sample.Person).%OpenId(1)Write "Original value: ",person.Name,!Write person.%Id(),!Write person.%Oid(),!Write person."%%OID",!
}
DHC-APP>d ##class(PHA.OP.MOB.Test).TestIdentifiers()
Original value: yaoxin
1
1Sample.Person
1Sample.Person
<BREAK>zTestIdentifiers+7^PHA.OP.MOB.Test.1
DHC-APP 2d1>zw person.%Oid()
$lb("1","Sample.Person")DHC-APP 2d1>zw person."%%OID"
$lb("1","Sample.Person")DHC-APP 2d1>

对象并发选项

在打开或删除对象时,适当地指定并发性非常重要。可以在几个不同的级别指定并发性:

  1. 可以为正在使用的方法指定并发参数。

%Persistent类的许多方法都允许您指定这个参数,一个整数。此参数确定如何将锁用于并发控制。如果未指定并发参数,则Caché 将使用正在处理的类的DEFAULTCONCURRENCY类参数的值;

  1. 可以为关联的类指定DEFAULTCONCURRENCY类参数。所有持久类都从%persistent继承此参数,%persistent将该参数定义为获得进程默认并发性的表达式;

可以在类中重写此参数,并指定硬编码的值或表达式,通过自己的规则来确定并发性

  1. 可以设置进程的默认并发性。为此,使用 $system.OBJ.SetConcurrencyMode() 方法(该方法是%SYSTEM.OBJ的SetConcurrencyMode()方法))。

与其他情况一样,必须使用一个允许的并发值。$system.OBJ.SetConcurrencyMode() 方法对为DEFAULTCONCURRENCY类参数指定显式值的任何类无效。

为什么指定并发?

下面的场景演示了在读取或写入对象时适当控制并发性的重要性。考虑以下场景:

  1. 进程A打开一个对象。
SAMPLES>set o=##class(Sample.Person).%OpenId(1)SAMPLES>w o
1@Sample.Person
  1. 进程B从磁盘中删除该对象:
SAMPLES>set sc=##class(Sample.Person).%DeleteId(5)SAMPLES>w sc
1
  1. 进程A使用%Save()保存对象并接收到成功状态。
SAMPLES>set sc=o.%Save()SAMPLES>w sc
1

检查磁盘上的数据表明,对象没有写入磁盘。

这是一个没有足够并发控制的并发操作的例子。例如,如果进程A可能将该对象保存回磁盘,那么它应该以并发3或4打开该对象(这些值将在本章后面讨论)。在这种情况下,进程B将被拒绝访问(并发冲突导致失败),或者必须等待,直到进程a释放该对象。

并发值

可能的并发值如下:

  • 0 没有锁。没有使用锁。
  • 1 原子的读。获取锁并根据需要释放锁,以确保读取的对象将作为原子操作执行。

对于%LoadData(),Caché 获取对象上的共享锁(如果需要确保原子读)。在完成读操作后释放锁。

没有为新对象获取锁。

%SaveData()在保存过程中获得一个独占锁。

  • 2 共享锁。与1(原子读)相同,只是%LoadData()总是获取一个共享锁。

对于%LoadData(),Caché 在所有情况下都获取对象上的共享锁。在完成读操作后释放锁。

没有为新对象获取锁。

%SaveData()在保存过程中获得一个独占锁。

  • 3 共享/保留锁。对于%LoadData(),Caché为对象获取共享锁。当对象被析构(从内存中删除)时,锁被释放。

最初不会为新对象获取锁,但在保存期间会获取锁;

%SaveData()获取任何对象的独占锁,包括新对象。这个锁将一直保持到对象被析构(从内存中删除)。

  • 4 排他锁。当一个现有对象被打开或一个新对象最初被保存到数据库(插入)时,获取一个排他锁;当对象被析构时,锁被释放。

注意:

  • 共享锁(S锁):共享 (S) 用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。

如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。

  • 排他锁(X锁):用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。

如果事务T对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。

我们在操作数据库的时候,可能会由于并发问题而引起的数据的不一致性(数据冲突)

  • 乐观锁
    乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。

并发性懒加载对象

属性引用的对象在访问时使用默认并发性进行swizzle。(如果正在swizzle的对象已经在内存中,那么swizzle实际上不会打开该对象—它只是引用内存中现有的对象;在这种情况下,对象的当前状态将保持不变,并发性也将保持不变。)

例如,如果一个对象(Obj1)有一个引用另一个对象(Obj2)的属性(Prop1),那么在访问Prop1时,Obj2将被swizzle;Obj2具有默认并发性,除非它已经打开。

有两种方法可以覆盖此默认行为:

  • 通过调用指定新并发性的%Open()方法更新swizzled对象上的并发性。例如:
 Do person.Spouse.%Open(person.Spouse.%Oid(),4,.status)

其中%Open()的第一个参数指定OID,第二个参数指定新的并发性,第三个参数(通过引用传递)接收方法的状态。

在刷对象之前设置进程的默认并发性。例如:

Set olddefault = $system.OBJ.SetConcurrencyMode(4)

此方法以新的并发模式作为参数,并返回以前的并发模式。

当不再需要不同的并发模式时,请按以下方式重置默认并发模式:

Do $system.OBJ.SetConcurrencyMode(olddefault)

版本检查(并发参数的替代方法)

在打开或删除对象时,可以实现版本检查,而不是指定并发参数。为此,需要指定一个名为VERSIONPROPERTY的类参数。所有持久类都有这个参数。定义持久类时,启用版本检查的过程是:

  1. 创建类型为%Integer的属性,该属性保存类的每个实例的可更新版本号。
  2. 对于该属性,将InitialExpression关键字的值设置为0。
  3. 对于类,将VERSIONPROPERTY类参数的值设置为该属性的名称。不能由子类将VERSIONPROPERTY的值更改为其他属性。

这将版本检查合并到类实例的更新中。

实现版本检查时,每次更新类的实例(通过对象或SQL)时,由version property指定的属性都会自动递增。在递增属性之前,Caché将其内存值与存储值进行比较。如果它们不同,则指示并发冲突并返回错误;如果它们相同,则属性将递增并保存。

注意:可以使用这组特性来实现乐观并发。

若要对VERSIONPROPERTY引用名为InstanceVersion的属性的类在SQL update语句中实现并发检查,代码如下:

 SELECT InstanceVersion,Name,SpecialRelevantField,%IDFROM my_tableWHERE %ID = :myid// Application performs operations on the selected rowUPDATE my_tableSET SpecialRelevantField = :newMoreSpecialValueWHERE %ID = :myid AND InstanceVersion = :myversion

其中myversion是用原始数据选择的version属性的值。

重要:如果从第二个对象引用具有version property参数的对象,并且第二个对象的保存失败,则默认情况下第一个对象的version属性将递增。你的代码应该检查这个场景,并适当地处理它。特别是,如果无法保存第二个对象,则代码应将第一个对象的version属性还原为其先前的值。否则,当保存第一个对象时,将收到以下错误:

ERROR #5800: Concurrency failure on update: object versions not the same for 'ConcurrencyFail.ClassA:1'

第八章 Caché 使用持久对象相关推荐

  1. 第十八章 Caché 命令大全 NEW 命令

    文章目录 第十八章 Caché 命令大全 NEW 命令 重点 大纲 参数 描述 NEW的限制 参数 pc variable 示例 注意 使用`NEW`功能的位置 `new`和`kill` `Inclu ...

  2. wxWidgets:持久对象概述

    wxWidgets:持久对象概述 wxWidgets:持久对象概述 使用永久窗口 定义自定义持久化窗口 wxWidgets:持久对象概述 持久对象只是在销毁时自动保存状态并在重新创建时自动恢复状态的对 ...

  3. 【数据库系统】管理持久对象的模式

    持久对象的管理模式 持久对象的管理是应用程序设计的重要问题,解决好这个问题需要好的设计模式集合,如PEAA. 持久对象的管理模式主要有: 标识映射:给内存中的所有持久对象都指定OID,再将这些OID映 ...

  4. 核心对象+持久对象全析(3)

    二.状态转化方法 前面说到对象之间的转化过程是通过使用方法来实现的,这些方法是很重要的,先看张图 上面这张图在三篇文章中都有用到,它详细描述了持久对象三种状态的转换过程及具体的转化方法,另外还有垃圾回 ...

  5. 核心对象+持久对象全析(2)

    一.状态解析       Hibernate的持久对象主要分为三个状态,Transient.Persistent.Detached,其中Transient称为瞬态,没有被数据库管理,Hibernate ...

  6. 核心对象+持久对象全析(1)

    一.Hibernate核心 1.核心解析图 一个非常简单的Hibernate体系结构图: 从上图可以看出,Hibernate使用数据库和配置信息来为应用程序提供持久化服务.应用程序创建的持久化对象收到 ...

  7. 第六十八章 Caché 函数大全 $WASCII 函数

    文章目录 第六十八章 Caché 函数大全 $WASCII 函数 大纲 参数 描述 示例 第六十八章 Caché 函数大全 $WASCII 函数 返回识别代理项对的字符对应的数字代码. 大纲 $WAS ...

  8. Jpa持久对象状态,一级缓存,二级缓存

    1JPA持久对象的状态 1.1. 临时状态(transient):瞬时状态 刚刚用new语句创建,没有和entityManager发生关系 没有被持久化,不处于entityManager中.该对象成为 ...

  9. 第二十八章 Caché 命令大全 TSTART 命令

    文章目录 第二十八章 Caché 命令大全 TSTART 命令 重点 大纲 参数 描述 嵌套事务 SQL Transactions 参数 pc 示例 第二十八章 Caché 命令大全 TSTART 命 ...

最新文章

  1. xgboost重要参数2为主但不全要参照1
  2. php api接口调试,PHP进行API接口测试
  3. UA MATH523A 实分析3 积分理论例题 Fubini定理计算简单二重积分的一个例题
  4. python爬取的信息条数比页面显示多_Python爬取分析北京二手房数据?数据结果真的太吓人了...
  5. 再见,齐达内,再见,法国队
  6. [原+转]CSS hack 小技巧 让你的CSS 兼容ff ie6.0 ie7.0
  7. php 常用正则运算
  8. (十九)【AAAI2021】Knowledge-Enhanced Hierarchical Graph Transformer Network for Multi-Behavior Recommend
  9. python pychart三维_详解python模块pychartdir安装及导入问题
  10. 阿里云oss对象存储:给图片添加(多行)文字图片水印
  11. aop实现mysql读写分离_mysql读写分离(1)---springboot+aop+tk.mybatis实现对mysql的读写分离...
  12. jquery audio在微信或者客户端需要点击两次才能正常播放的问题解决方案
  13. iOS开发值苹果手机各种尺寸详细表以及iPhoneX、iPhoneXS、iPhoneXR、iPhoneXSMax屏幕适配
  14. MEPR500+电子护照阅读器|识读模块嵌入式安装与应用说明
  15. 如何用sql语句查询年龄最小或最大的信息
  16. 隐私黑产:2亿余条个人信息卖了2000余万元
  17. ElasticSearch 在 Spring 项目中的实践
  18. mac装linux iso不用u盘安装,在任何mac上用u盘安装OSX和Windows10双系统的方法(支持老电脑、不用Bootcamp)...
  19. html浏览器标题闪动,如何实现网页标题的闪动提示
  20. Web笔记(二)Tomcat 使用总结

热门文章

  1. 洛谷 P4389 付公主的背包 多项式exp
  2. 接口自动化—mock服务、用例依赖
  3. 源码分析 - Spring Security OAuth2 生成 token 的执行流程
  4. grub命令 u盘安装linux,通过grub-install命令把grub安装到u盘-总结
  5. Qt 窗体显示在屏幕最上面
  6. 最大概率法分词中词频惩罚因子的作用探究
  7. IIS 应用程序池自动停止处理
  8. 125万“墨子量子奖“的获得者,研究成果连发《自然》和《科学》
  9. 效率源希捷自校准配套专修软件 免费
  10. 根据字符出现的频次排序