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

Как объединить генератор, назначенный для гибернации, с последовательностью или столбцом идентификаторов

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

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

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

Назначенный генератор не принимает аннотацию @GeneratedValue , и сопоставление идентификаторов выглядит следующим образом:

@Id
private Long id;

Чтобы использовать столбец идентификаторов, необходимо указать аннотацию @GeneratedValue :

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

То же самое относится и к использованию последовательности баз данных:

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;

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

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

Во-первых, все наши сущности реализуют следующий интерфейс:

public interface Identifiable {
    T getId();
}

Чтобы объединить столбец идентификаторов со стратегией присвоенных идентификаторов, нам необходимо создать следующую стратегию пользовательских идентификаторов:

public class AssignedIdentityGenerator 
    extends IdentityGenerator {

    @Override
    public Serializable generate(SessionImplementor session, 
        Object obj) {
        if(obj instanceof Identifiable) {
            Identifiable identifiable = (Identifiable) obj;
            Serializable id = identifiable.getId();
            if(id != null) {
                return id;
            }
        }
        return super.generate(session, obj);
    }
}

Чтобы использовать этот генератор идентификаторов, сопоставление сущностей выглядит следующим образом:

@Entity(
public class Post implements Identifiable {

    @Id
    @GenericGenerator(
        name = "assigned-identity", 
        strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.AssignedIdentityGenerator"
    )
    @GeneratedValue(
        generator = "assigned-identity", 
        strategy = GenerationType.IDENTITY
    )
    private Long id;

    @Version
    private Integer version;

    public Post() {
    }

    public Post(Long id) {
        this.id = id;
    }

    @Override
    public Long getId() {
        return id;
    }
}

При наличии этого сопоставления при выполнении следующего тестового набора:

doInJPA(entityManager -> {
    entityManager.persist(new Post());
    entityManager.persist(new Post(-1L));
    entityManager.persist(new Post());
    entityManager.persist(new Post(-2L));
});

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

INSERT INTO post (id, version) VALUES (DEFAULT, 0)
INSERT INTO post (version, id) VALUES (0, -1)
INSERT INTO post (id, version) VALUES (DEFAULT, 0)
INSERT INTO post (version, id) VALUES (0, -2)

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

public class AssignedSequenceStyleGenerator 
    extends SequenceStyleGenerator {

    @Override
    public Serializable generate(SessionImplementor session, 
        Object obj) {
        if(obj instanceof Identifiable) {
            Identifiable identifiable = (Identifiable) obj;
            Serializable id = identifiable.getId();
            if(id != null) {
                return id;
            }
        }
        return super.generate(session, obj);
    }
}

Единственная разница в том, что на этот раз мы расширяем генератор последовательностей .

Сопоставление сущностей выглядит следующим образом:

@Entity
public class Post implements Identifiable {

    @Id
    @GenericGenerator(
        name = "assigned-sequence",
        strategy = "com.vladmihalcea.book.hpjp.hibernate.identifier.AssignedSequenceStyleGenerator",
        parameters = @org.hibernate.annotations.Parameter(
            name = "sequence_name", 
            value = "post_sequence"
        )
    )
    @GeneratedValue(
        generator = "assigned-sequence", 
        strategy = GenerationType.SEQUENCE
    )
    private Long id;

    @Version
    private Integer version;

    public Post() {
    }

    public Post(Long id) {
        this.id = id;
    }

    @Override
    public Long getId() {
        return id;
    }
}

При запуске предыдущего тестового набора Hibernate генерирует следующие инструкции SQL:

CALL NEXT VALUE FOR post_sequence
CALL NEXT VALUE FOR post_sequence
INSERT INTO post (version, id) VALUES (0, 1) 
INSERT INTO post (version, id) VALUES (0, -1)
INSERT INTO post (version, id) VALUES (0, 2) 
INSERT INTO post (version, id) VALUES (0, -2)

Хотя вы, возможно, и не столкнетесь с таким требованием, важно знать, что Hibernate обладает высокой степенью расширения, позволяя настраивать встроенные стратегии сопоставления.