当前访客身份:游客 [ 登录  | 注册加入尚学堂]
启用新域名sxt.cn
新闻资讯

Hibernate和UUID标示符

helloworld 发表于 2年前  | 评论(0 )| 阅读次数(639 )|   0 人收藏此文章,   我要收藏

简介

前一篇博客中,我们讨论了UUID代理键(surrogate keys)以及比自增标示符更适用的使用场景

UUID的数据库生成方式

有很多种方式可表达一个128位UUID。每当遇到疑问时,我喜欢用stack exchange来作为权威参考。

由于表ID一般会建索引,经过压缩的数据类型会占用更少的空间。按照效率由高到低有如下几种选择:

  1. 一部分数据库(PostgreSQL, SQL Server) 提供了专门的UUID 存储数据类型。
  2. 另外还可把若干bit存放到字节数组(比如Oracle 的RAW(16) 或标准类型的BINARY(16) )。
  3. 或可用2个bigint列(64位),但这种复合列的ID的效率低于单一列。
  4. 把16进制值存入一个char(36)列(其包含32个16进制数和4个”-“),但这会占用很多磁盘空间,所以只是一个低效备选方案。

Hibernate提供了多种id策略供选择,针对UUID有以下三种:

  • 由应用程序逻辑来分配的uuid生成。
  • 16进制的字符串uuid生成器。
  • uuid2生成器更灵活些。它可以借用 java.lang.UUID ,这是16个字节数据或16进制字符串。

(程序)自定义生成方式

自定义生成方式可以让应用程序的逻辑来处理实体ID的生成过程。Hibernate通过简单的忽略ID生成器定义来处理自定义ID。由于数据库使用了HSQLDB,所以下面示例使用BINARY(16) 列类型。

    @Entity(name = "assignedIdentifier")
    public static class AssignedIdentifier {
        @Id
        @Column(columnDefinition = "BINARY(16)")
        private UUID uuid;
            public AssignedIdentifier() {
        }
            public AssignedIdentifier(UUID uuid) {
            this.uuid = uuid;
        }
    }

持久化实体:

    session.persist(new AssignedIdentifier(UUID.randomUUID()));
    session.flush();

具体生成一个INSERT语句:

    Query:{[insert into assignedIdentifier (uuid) values (?)][[B@76b0f8c3]}

我们可以看看使用merge后会发生什么:

session.merge(new AssignedIdentifier(UUID.randomUUID()));
session.flush();

这次既有select查询又有insert:

Query:{[select assignedid0_.uuid as uuid1_0_0_ from assignedIdentifier assignedid0_ where assignedid0_.uuid=?][[B@23e9436c]} 
Query:{[insert into assignedIdentifier (uuid) values (?)][[B@2b37d486]}

持久化方法会把transient实体添加到当前Hibernate session中。不过,如果session已有其他实体或当前实体已被去除,就会抛出一个exception。如果需要,针对transient实体和 已去除的实体,合并操作会复制当前对象的状态到已持久化的实体中。但transient实体的持久化比合并要高效很多。

对于已生成的ID,由于Hibernate并不能知道库中有重复ID,所以合并还需要一次select查询。对于其他的ID生成 器,Hibernate会找一空ID来判断该实体有没有处于transient状态。因此,如果使用自定义ID,那么Spring Data包里面 SimpleJpaRepository#save(S entity) 方法并不是最好选择:

    @Transactional
    public <S extends T> S save(S entity) {
        if (entityInformation.isNew(entity)) {
            em.persist(entity);
            return entity;
        } else {
            return em.merge(entity);

这个方法对于自定义ID会调用em.merge()而不是em.persist()。所以每次插入新实体都需要先select然后才能insert,效率自然会低些。

UUID生成器

这次我们不再用程序分配ID,而是让Hibernate来生成。如果遇到空ID,Hibernate就认为是transient实体,会给出一个新ID值。另外,merge方法也不再需要先select查询了。

UUIDHexGenerator

UUID hex generator 是最古老的UUID identifier 生成器,也是一种 “uuid”类型。可生成一个32位 16进制UUID字符串(也带有分隔符),模式遵循:8{sep}8{sep}4{sep}8{sep}4。

这个生成器不受IETF RFC 4122 约束,使用该模式的数字表达式例如8-4-4-4-12。

    @Entity(name = "uuidIdentifier")
    public static class UUIDIdentifier {
        @GeneratedValue(generator = "uuid")
        @GenericGenerator(name = "uuid", strategy = "uuid")
        @Column(columnDefinition = "CHAR(32)")
        @Id
        private String uuidHex;
    }

持久化和合并一个transient 实体:

    session.persist(new UUIDIdentifier());
    session.flush();
    session.merge(new UUIDIdentifier());
    session.flush();

对每个操作生成一个 INSERT 语句:

Query:{[insert into uuidIdentifier (uuidHex) values (?)][2c929c6646f02fda0146f02fdbfa0000]}
Query:{[insert into uuidIdentifier (uuidHex) values (?)][2c929c6646f02fda0146f02fdbfc0001]}

最好检查传给SQL INSERT和select的string参数值。

UUIDGenerator

最新的UUID generator受IETF RFC 4122约束 (variant 2),并且可提供插件式生成策略。注册“uuid2″ 类型,提供更广的类型供选择。

  • java.lang.UUID
  • 一个16字节的数组
  • 一个16进制字符串值
    @Entity(name = "uuid2Identifier")
    public static class UUID2Identifier {    
        @GeneratedValue(generator = "uuid2")
        @GenericGenerator(name = "uuid2", strategy = "uuid2")
        @Column(columnDefinition = "BINARY(16)")
        @Id
        private UUID uuid; 
    }

持久化和合并一个transient 实体的方法使用方式:

    session.persist(new UUID2Identifier());
    session.flush();
    session.merge(new UUID2Identifier());
    session.flush();

对每个操作生成一个 INSERT 语句:

    Query:{[insert into uuid2Identifier (uuid) values (?)][[B@68240bb]}
    Query:{[insert into uuid2Identifier (uuid) values (?)][[B@577c3bfa]}

由于我们配置了@Id column定义,这个insert使用字节数组:

  • 具体更多代码可查看 GitHub.
分享到:0
关注微信,跟着我们扩展技术视野。每天推送IT新技术文章,每周聚焦一门新技术。微信二维码如下:
微信公众账号:尚学堂(微信号:bjsxt-java)
声明:博客文章版权属于原创作者,受法律保护。如果侵犯了您的权利,请联系管理员,我们将及时删除!
(邮箱:webmaster#sxt.cn(#换为@))
北京总部地址:北京市海淀区西三旗桥东建材城西路85号神州科技园B座三层尚学堂 咨询电话:400-009-1906 010-56233821
Copyright 2007-2015 北京尚学堂科技有限公司 京ICP备13018289号-1 京公网安备11010802015183