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

Самый быстрый способ обновления строки таблицы при использовании Hibernate и Oracle

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

Oracle предоставляет несколько псевдоколонок, и СТРОКИ являются одной из них. Столбец ROWID псевдо указывает адрес базовой записи базы данных, и , согласно документации Oracle , это самый быстрый способ ссылки на строку таблицы.

Как объяснено на Спросите ТОМА , есть некоторые операции, которые могут привести к изменению идентификатора строки (например, разбиение на разделы или сжатие таблиц). Если это так, то вам не следует полагаться на псевдоколонку ROWID, так как ее значение больше не соответствует.

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

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

Первое, что нужно сделать, это аннотировать объект JPA, используя специфичный для гибернации @RowId .

Учитывая, что у нас есть следующие две сущности:

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

    @Id
    private Long id;

    private String title;

    @OneToMany(
        cascade = CascadeType.ALL, 
        mappedBy = "post",
        orphanRemoval = true
    )
    private List comments = new ArrayList<>();

    //Getters and setters omitted for brevity

    public void addComment(PostComment comment) {
        comments.add(comment);
        comment.setPost(this);
    }

    public void removeComment(PostComment comment) {
        comments.remove(comment);
        comment.setPost(null);
    }
}

@Entity(name = "PostComment")
@Table(name = "post_comment")
@RowId( "ROWID" )
public class PostComment {

    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne(
        fetch = FetchType.LAZY
    )
    private Post post;

    private String review;

    //Getters and setters omitted for brevity

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof PostComment)) return false;
        return 
            id != null && 
            id.equals(((PostComment) o).getId());
    }

    @Override
    public int hashCode() {
        return getClass().hashCode();
    }
}

Если вы задаетесь вопросом о необычных вариантах реализации equals и хэш-кода, вам следует прочитать следующую статью .

Сопоставление @RowId( "ROWID" ) указывает Hibernate использовать псевдоколонку ROWID при доступе к записи базы данных при выполнении инструкции UPDATE .

Предполагая, что в нашей базе данных есть следующие объекты:

Post post = new Post();
post.setId(1L);
post.setTitle(
    "High-Performance Java Persistence"
);

entityManager.persist(post);

PostComment comment1 = new PostComment();
comment1.setReview("Great!");
post.addComment(comment1);

PostComment comment2 = new PostComment();
comment2.setReview("To read");
post.addComment(comment2);

PostComment comment3 = new PostComment();
comment3.setReview("Lorem Ipsum");
post.addComment(comment3);

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

Post _post = doInJPA( entityManager -> {
    return entityManager.createQuery(
        "select p " +
        "from Post p " +
        "join fetch p.comments " +
        "where p.id = :id", Post.class)
    .setParameter( "id", 1L )
    .getSingleResult();
} );

List_comments = _post.getComments();

_post.getComments().get( 0 )
.setReview( 
    "Must read!" 
);
_post.removeComment( _comments.get( 2 ) );

doInJPA( entityManager -> {
    entityManager.merge( _post );
} );

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

SELECT p.id AS id1_0_0_,
       p.title AS title2_0_0_,
       p.ROWID AS rowid_0_,
       c.post_id AS post_id3_1_1_,
       c.review AS review2_1_1_,
       c.ROWID AS rowid_1_,
       c.post_id AS post_id3_1_0__,
       c.id AS id1_1_0__
FROM   post p
INNER JOIN 
       post_comment c ON p.id = c.post_id
WHERE  p.id = 1

-- Merge SELECT query skipped for brevity

UPDATE post_comment 
SET    post_id = 1, 
       review = 'Must read!' 
WHERE  ROWID = AAAwmzAAEAAACZDAAA

DELETE FROM 
       post_comment 
WHERE  id = 3

Оператор SELECT включает псевдоколонку ROWID , которая хранится в текущем контексте сохранения. Во время перехода слияния состояния сущности Hibernate копирует состояние отделенной сущности во вновь выбранные версии сущности , и механизм проверки на наличие ошибок распространяет изменения в базу данных.

Только оператор UPDATE извлекает выгоду из столбца ROWID псевдо, оператор DELETE этого не делает (пока). HHH-11761 позаботится об этом ограничении, но оно будет рассмотрено только в версии 6.0.

Итак, почему вас вообще волнует ROWID ? В конце концов, у каждой сущности есть свой собственный идентификатор, и базовый Первичный ключ также индексируется.

Чтобы понять, почему стоит использовать псевдоколонку ROWID , лучше визуализировать план выполнения вышеупомянутого UPDATE оператора:

---------------------------------------------------------------------------------
| Id  | Operation                   | Name         | Rows  | Bytes | Cost (%CPU)|
---------------------------------------------------------------------------------
|   0 | UPDATE STATEMENT            |              |     1 |   537 |     1   (0)|
|   1 |  UPDATE                     | POST_COMMENT |       |       |            |
|   2 |   TABLE ACCESS BY USER ROWID| POST_COMMENT |     1 |   537 |     1   (0)|
---------------------------------------------------------------------------------

Принимая во внимание, что при обновлении строки по ее идентификатору План выполнения:

-------------------------------------------------------------------------
| Id  | Operation          | Name          | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------
|   0 | UPDATE STATEMENT   |               |     1 |   538 |     1   (0)|
|   1 |  UPDATE            | POST_COMMENT  |       |       |            |
|*  2 |   INDEX UNIQUE SCAN| SYS_C00281229 |     1 |   538 |     1   (0)|
-------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   2 - access("ID" = 1)

Хотя в этом тривиальном примере стоимость одинакова, на самом деле ДОСТУП К ТАБЛИЦЕ ПО пути доступа ПОЛЬЗОВАТЕЛЯ ROWID выполняется быстрее, чем сканирование по УНИКАЛЬНОМУ ИНДЕКСУ, поскольку экстрактор может загружать страницу данных напрямую, не обращаясь к индексу для извлечения ROWID .

Hibernate-это не просто инструмент ORM, а полноценная платформа доступа к данным, предлагающая всевозможные оптимизации производительности. Если вы используете Oracle и выполняете множество операторов UPDATE , имеет смысл использовать сопоставление @RowId .