域对象作为ID会产生一些复杂/细微的问题:
序列化/反序列化
如果将对象存储为键,则序列化对象图将变得极为复杂。stackoverflow
由于递归,在对JSON或XML进行天真序列化时,您会得到错误。然后,您将必须编写一个自定义的序列化程序,该序列化程序将实际对象转换为使用其ID,而不是序列化对象实例并创建递归。
传递对象以确保类型安全,但仅存储ID,然后可以使用一个访问器方法,该方法在调用相关实体时会延迟加载相关实体。二级缓存将处理后续的调用。
细微的参考泄漏:
如果像在那里那样在构造函数中使用域对象,则将创建循环引用,这将非常困难,以允许回收未被有效使用的对象的内存。
理想情况:
不透明ID与int / long:
An id
应该是完全不透明的标识符,不包含有关其标识的信息。但是它应该提供一些验证,证明它是其系统中的有效标识符。
原始类型打破了这一点:
int
,long
并且String
是最常用的原料类型的RDBMS系统标识符。有实际原因的历史可以追溯到几十年,他们都妥协,要么配合到节能space
或保存time
或两者兼而有之。
顺序编号是最严重的违规者:
使用顺序ID时,默认情况下会将时间语义信息打包到ID中。这是不坏,直到它被使用。当人们开始编写对id的语义质量进行排序或过滤的业务逻辑时,他们为未来的维护人员带来了痛苦。
String
字段是有问题的,因为天真的设计师会将信息(通常也是时间语义)也打包到内容中。
这些也使得不可能创建分布式数据系统,因为12437379123
它不是全局唯一的。当您在系统中获得足够的数据时,几乎可以保证分布式系统中的另一个节点将创建具有相同编号的记录的机会。
然后黑客开始解决它,整个事情演变成一堆蒸腾的烂摊子。
当您开始尝试与其他系统共享数据时,忽略巨大的分布式系统(集群)也将成为一个噩梦。特别是当另一个系统不受您控制时。
您最终遇到了完全相同的问题,即如何使您的ID在全球范围内唯一。
创建和标准化UUID的原因如下:
UUID
可能会遇到上面列出的所有问题,具体取决于Version
您使用的方法。
Version 1
使用MAC地址和时间来创建唯一ID。这很不好,因为它带有有关位置和时间的语义信息。这本身不是问题,而是天真的开发人员开始依赖该信息进行业务逻辑的时候。这还会泄漏可在任何入侵尝试中利用的信息。
Version 2
使用用户UID
或GID
domian UID
或GUI
代替用户的时间Version 1
就像Version 1
数据泄漏一样糟糕,并且冒着将这些信息用于业务逻辑的风险。
Version 3
相似,但用绝对具有语义含义MD5
的某些内容的哈希表替换MAC地址和时间byte[]
。没有数据泄漏的烦恼,byte[]
无法从中恢复UUID
。这为您确定性地创建UUID
实例形式和某种外部键提供了一种好方法。
Version 4
仅基于随机数是一个很好的解决方案,它绝对不包含语义信息,但不能确定地重新创建。
Version 5
就像Version 4
但使用sha1
代替md5
。
域密钥和事务数据密钥
我对域对象ID的偏好是使用,Version 5
或者由于某种技术原因而Version 3
限制使用Version 5
。
Version 3
非常适合可能分散在许多计算机上的事务数据。
除非您受空间限制,否则请使用UUID:
它们被保证是唯一的,可以从一个数据库中转储数据并将其重新加载到另一个数据库中,而您不必担心实际引用不同域数据的重复ID。
Version 3,4,5
完全不透明,这就是应该的样子。
您可以使用a作为主键的一列UUID
,然后可以使用复合唯一索引来代替原来的自然复合主键。
存储并没有必须CHAR(36)
要么。您可以将它存储UUID
在给定数据库的本机字节/位/数字字段中,只要它仍可索引即可。
遗产
如果您有原始类型并且不能更改它们,则仍然可以在代码中将它们抽象出来。
使用Version 3/5
的UUID
,你可以通过在Class.getName()
+ String.valueOf(int)
作为byte[]
,有一个不透明的参考关键是重新创建和确定性。