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

Как обновить только подмножество атрибутов сущности с помощью JPA и Hibernate @DynamicUpdate

Узнайте, как создается инструкция UPDATE с помощью JPA и Hibernate и как обновить только подмножество атрибутов сущности с помощью @DynamicUpdate.

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

Вступление

Один из моих подписчиков в Твиттере попросил меня ответить на следующий вопрос о StackOverflow .

Цель этой статьи-объяснить различные методы управления инструкцией UPDATE SQL, которая выполняется всякий раз, когда объект изменяется уровнем доступа к данным.

Модель предметной области

Давайте предположим, что у нас есть следующая Запись сущность:

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

    @Id
    private Long id;

    private String title;

    private long score;

    @Column(
        name = "created_on", 
        nullable = false, 
        updatable = false
    )
    private Timestamp createdOn;

    @Transient
    private String creationTimestamp;

    public Post() {
        this.createdOn = new Timestamp(
            System.currentTimeMillis()
        );
    }

    public String getCreationTimestamp() {
        if(creationTimestamp == null) {
            creationTimestamp = DateTimeFormatter
            .ISO_DATE_TIME.format(
                createdOn.toLocalDateTime()
            );
        }
        return creationTimestamp;
    }

    @Override
    public String toString() {
        return String.format(
            "Post{\n" +
            "  id=%d\n" +
            "  title='%s'\n" +
            "  score=%d\n" +
            "  creationTimestamp='%s'\n" +
            '}', id, title, score, getCreationTimestamp()
        );
    }

    //Getters and setters omitted for brevity
}

Столбцы только для вставки

Сущность Post имеет атрибут CreatedOn , который следует устанавливать только при первом создании сущности. Любое последующее ОБНОВЛЕНИЕ не позволяет изменять этот столбец базы данных, поэтому обновляемый атрибут связанного @Столбца аннотации имеет значение false .

@Column(
    name = "created_on", 
    nullable = false, 
    updatable = false
)
private Timestamp createdOn;

Для атрибута сущности, который никогда не должен изменяться после сохранения сущности (столбцы только для вставки), следует использовать @Столбец(обновляемый) .

Вычисляемые атрибуты сущности

Атрибут Post сущность CreatedOn является Временная метка , которую мы, возможно, захотим распечатать в журналах приложений, используя ISO_DATE_TIME DateTimeFormatter . Чтобы избежать вычисления представления объекта String каждый раз, когда нам нужно регистрировать эту сущность, атрибут отметка времени создания будет хранить это значение.

@Transient
private String creationTimestamp;

Однако мы не хотим, чтобы этот атрибут сохранялся. Следовательно, нам нужно аннотировать его с помощью @Transient . Таким образом, Hibernate будет игнорировать этот атрибут при переводе изменений состояния сущности в инструкцию SQL .

Предполагая, что мы сохранили следующую сущность:

doInJPA(entityManager -> {
    Post post = new Post();
    post.setId(1L);
    post.setTitle("High-Performance Java Persistence");
    entityManager.persist(post);
});

При извлечении, регистрации и изменении этой сущности:

doInJPA(entityManager -> {
    Post post = entityManager.find(Post.class, 1L);
    LOGGER.info("Fetched post: {}", post);
    post.setScore(12);
});

Получается следующий результат:

SELECT p.id AS id1_0_0_,
       p.created_on AS created_2_0_0_,
       p.score AS score3_0_0_,
       p.title AS title4_0_0_
FROM   post p
WHERE  p.id = 1

-- Fetched post: Post{
  id=1
  title='High-Performance Java Persistence'
  score=0
  creationTimestamp='2016-10-10T16:48:25.566'
}

UPDATE post
SET    score = 12,
       title = 'High-Performance Java Persistence'
WHERE  id = 1

Как вы можете видеть, ни создатель , ни Метка времени создания не включены в инструкцию UPDATE SQL.

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

Динамические обновления

Предыдущая инструкция UPDATE включает все столбцы таблицы, даже если фактически изменяется только подмножество. Использование одного и того же оператора SQL полезно при использовании кэширования операторов JDBC . Однако, если таблица базы данных сильно проиндексирована, мы не хотим обновлять некоторые записи индекса, которые не были изменены, как объяснил Маркус Винанд .

По этой причине Hibernate предлагает аннотацию @DynamicUpdate . Все, что нам нужно сделать, это добавить эту аннотацию на уровне сущности:

@Entity(name = "Post")
@Table(name = "post")
@DynamicUpdate
public class Post {
    //Code omitted for brevity
}

Теперь при выполнении предыдущего тестового случая, в котором был изменен атрибут оценка , выполняется следующая инструкция UPDATE :

UPDATE post
SET    score = 12,
WHERE  id = 1

Блестяще!

Чтобы обновить только измененные столбцы, сущность должна использовать аннотацию @DynamicUpdate , которая также обязательна при использовании оптимистической блокировки без версий .

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