Hibernate 和 UUID 標示符

簡介html


在前一篇博客 (https://www.javacodegeeks.com/2014/06/database-primary-key-flavors.html)中,咱們討論了UUID代理鍵(surrogate keys)以及比自增標示符更適用的使用場景。java


UUID的數據庫生成方式git


有不少種方式可表達一個128位UUID。每當遇到疑問時,我喜歡用stack exchange來做爲權威參考。github


因爲表ID通常會建索引,通過壓縮的數據類型會佔用更少的空間。按照效率由高到低有以下幾種選擇:數據庫


  1. 一部分數據庫(PostgreSQL, SQL Server) 提供了專門的UUID 存儲數據類型。數組


  2. 另外還可把若干bit存放到字節數組(好比Oracle 的RAW(16) 或標準類型的BINARY(16) )。session


  3. 或可用2個bigint列(64位),但這種複合列的ID的效率低於單一列。app


  4. 把16進制值存入一個char(36)列(其包含32個16進制數和4個」-「),但這會佔用不少磁盤空間,因此只是一個低效備選方案。dom


Hibernate提供了多種id策略供選擇,針對UUID有如下三種:ide


  • 由應用程序邏輯來分配的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 (https://github.com/vladmihalcea/hibernate-master-class).

相關文章
相關標籤/搜索