Рубрики
Без рубрики

Идентификаторы Hibernate и UUID

Узнайте, как сохраняются атрибуты сущности UUID при использовании JPA и гибернации как для назначенных, так и для автоматически сгенерированных идентификаторов.

Автор оригинала: Vlad Mihalcea.

Вступление

В этой статье мы рассмотрим, как сохраняются атрибуты сущности UUID при использовании JPA и Hibernate как для назначенных, так и для автоматически сгенерированных идентификаторов.

В моем предыдущем посте Я говорил о UUID суррогатных ключах и случаях использования , когда есть более подходящие, чем более распространенные идентификаторы с автоматическим увеличением.

Тип базы данных UUID

Существует несколько способов представления 128-битного UUID, и всякий раз, когда у меня возникают сомнения, я предпочитаю обращаться к Stack Exchange за советом эксперта.

Поскольку идентификаторы таблиц обычно индексируются, чем компактнее тип базы данных, тем меньше места потребуется для индекса. От наиболее эффективного до наименее эффективного, вот наши варианты:

  1. Некоторые базы данных ( PostgreSQL , SQL Server ) предлагают выделенный тип хранилища UUID
  2. В противном случае мы можем хранить биты в виде массива байтов (например, RAW(16) в Oracle или стандартном ДВОИЧНОМ(16) типе)
  3. В качестве альтернативы мы можем использовать 2 больших (64-разрядных) столбца, но составной идентификатор менее эффективен, чем один столбец
  4. Мы можем сохранить шестнадцатеричное значение в столбце CHAR(36) (например, 32 шестнадцатеричных значения и 4 тире), но это займет больше всего места, следовательно, это наименее эффективная альтернатива

Hibernate предлагает множество идентифицированных стратегий на выбор, и для идентификаторов UUID у нас есть три варианта:

  • назначенный генератор сопровождается генерацией UUID логики приложения
  • шестнадцатеричный “uuid” генератор строк
  • более гибкий “uuid2” генератор, позволяющий нам использовать java.lang.UUID , 16-байтовый массив или шестнадцатеричное строковое значение

Генератор UUID, назначенный для режима гибернации

Назначенный генератор позволяет логике приложения управлять процессом генерации идентификатора сущности. Просто опуская определение генератора идентификаторов, Hibernate рассмотрит назначенный идентификатор. В этом примере используется ДВОИЧНЫЙ(16) тип столбца, поскольку целевой базой данных является HSQLDB .

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @Column(columnDefinition = "BINARY(16)")
    private UUID id = UUID.randomUUID();
    
    private String title;

    public UUID getId() {
        return id;
    }

    public Post setId(UUID id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Post setTitle(String title) {
        this.title = title;
        return this;
    }
}

Сохранение сущности:

entityManager.persist(
    new Post().setTitle("High-Performance Java Persistence")
);

Генерирует ровно одну инструкцию INSERT:

INSERT INTO post (
    title, 
    id
) 
VALUES (
    'High-Performance Java Persistence', 
    [72, 101, 87, -123, -35, 18, 65, -21, -84, -90, 83, -104, -112, -41, -62, -54]
)

Давайте посмотрим, что произойдет при выполнении слияния вместо этого:

entityManager.merge(
    new Post().setTitle("High-Performance Java Persistence")
);

На этот раз мы получаем как ВЫБОР, так и ВСТАВКУ:

SELECT 
    p.id as id1_0_0_, 
    p.title as title2_0_0_ 
FROM 
    post p 
WHERE 
    p.id = [32, -57, 116, 87, 106, 104, 76, -95, -102, 25, -74, 119, 30, -50, -12, -84]
    
INSERT INTO post (
    title, 
    id
) 
VALUES (
    'High-Performance Java Persistence', 
    [32, -57, 116, 87, 106, 104, 76, -95, -102, 25, -74, 119, 30, -50, -12, -84]
)

Метод persist принимает временный объект и присоединяет его к текущему менеджеру объектов Hibernate. Если уже имеется присоединенный объект или если текущий объект отсоединен, создается исключение.

Операция слияния скопирует текущее состояние объекта в существующую сохраняемую сущность. Эта операция работает как для временных, так и для отдельных объектов, но для временных объектов сохранение намного эффективнее, чем операция слияния.

Для присвоенных идентификаторов слияние всегда потребует выбора SQL, поскольку Hibernate не может знать, существует ли уже сохраненная сущность с тем же идентификатором. Для других генераторов идентификаторов Hibernate ищет нулевой идентификатор, чтобы определить, находится ли объект в переходном состоянии.

Если вы используете метод Spring Data SimpleJpaRepository#save(S entity) , то вам нужно быть осторожным при использовании сущностей с присвоенными идентификаторами.

Метод сохранить реализован следующим образом:

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

Для назначенных идентификаторов этот метод может выбрать объединить вместо сохранить , если сущность также не предоставляет свойство @Version , поэтому запускает SELECT перед выполнением инструкции INSERT для каждой вновь вставленной сущности.

Ознакомьтесь с документацией Spring для получения более подробной информации о наилучшем способе использования метода save , предоставленного JpaRepository .

Автоматически сгенерированные идентификаторы UUID гибернации

На этот раз мы не будем присваивать идентификатор сами, а попросим Hibernate сгенерировать его от нашего имени. При обнаружении нулевого идентификатора Hibernate принимает временный объект, для которого он генерирует новое значение идентификатора. На этот раз для операции слияния не потребуется запрос select перед вставкой переходной сущности.

Генератор UUIDHexGenerator

Шестнадцатеричный генератор UUID является старейшим генератором идентификаторов UUID и зарегистрирован под типом “uuid” . Он может генерировать 32 шестнадцатеричное строковое значение UUID (он также может использовать разделитель), имеющее следующий шаблон: 8{sep}8{sep}4{sep}8{sep}4.

Этот генератор не соответствует IETF RFC 4122 , который использует представление цифр 8-4-4-4-12.

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid")
    @Column(columnDefinition = "CHAR(32)")
    private String id;

    private String title;

    public String getId() {
        return id;
    }

    public Post setId(String id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Post setTitle(String title) {
        this.title = title;
        return this;
    }
}

При сохранении Записи сущности:

entityManager.persist(
    new Post().setTitle("High-Performance Java Persistence")
);

Hibernate создает следующую инструкцию SQL INSERT:

INSERT INTO post (
    title, 
    id
) 
VALUES (
    'High-Performance Java Persistence', 
    8a80cb8172c0e9ff0172c0ea02e40000
)

И, при объединении временной Записи сущности:

entityManager.merge(
    new Post().setTitle("High-Performance Java Persistence")
);

Hibernate генерирует одну инструкцию SQL INSERT без необходимости запроса SELECT:

INSERT INTO post (
    title, 
    id
) 
VALUES (
    'High-Performance Java Persistence', 
    8a80cb8172c0e9ff0172c0ea03030001
)

Генератор UUID

Более новый генератор UUID соответствует стандарту IETF RFC 4122 (вариант 2) и предлагает подключаемые стратегии генерации. Он зарегистрирован под типом uuid2 и предлагает более широкий диапазон типов на выбор:

  • Более новый генератор
  • UUID
  • соответствует стандарту IETF RFC 4122 (вариант 2) и предлагает подключаемые стратегии генерации. Он зарегистрирован под типом

Поскольку генератор uuid2 является стратегией по умолчанию, используемой Hibernate, вам не нужно объявлять ее явно. Если идентификатор сущности имеет тип UUID и идентификатор сущности использует аннотацию @GeneratedValue , то будет использоваться стратегия uuid2 генератор:

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue
    @Column(columnDefinition = "BINARY(16)")
    private UUID id;

    private String title;

    public UUID getId() {
        return id;
    }

    public Post setId(UUID id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Post setTitle(String title) {
        this.title = title;
        return this;
    }
}

Сохранение или слияние переходной сущности:

При сохранении Записи сущности:

entityManager.persist(
    new Post().setTitle("High-Performance Java Persistence")
);

Hibernate создает следующую инструкцию SQL INSERT:

INSERT INTO post (
    title, 
    id
) 
VALUES (
    'High-Performance Java Persistence', 
    [90, 17, 87, -73, -69, 81, 77, -47, -102, 110, 74, -4, 85, -74, -24, -95]
)

И, при объединении временной Записи сущности:

entityManager.merge(
    new Post().setTitle("High-Performance Java Persistence")
);

Hibernate генерирует одну инструкцию SQL INSERT:

INSERT INTO post (
    title, 
    id
) 
VALUES (
    'High-Performance Java Persistence', 
    [-38, 35, 2, -55, 65, -127, 70, -51, -68, -34, 117, 111, -40, 4, -26, 63]
)

Эти запросы SQL-вставки используют массив байтов, как мы настроили определение столбца @Id .

Вывод

Хотя вы можете использовать идентификатор сущности UUID с JPA и гибернацией, это не всегда правильный выбор. Прежде всего, UUID требует 128 бит, и эта проблема может быть усилена столбцами внешнего ключа. Поскольку столбцы первичного ключа и внешних ключей обычно являются индексами, дополнительные требования к хранилищу также повлияют на индексы.

Именно по этой причине числовые идентификаторы сущностей обычно являются гораздо лучшим вариантом, особенно когда они генерируются последовательностью базы данных .