Как сопоставить коллекции только для чтения в JPA / Hibernate, которые не вызывают обновления БД - PullRequest
3 голосов
/ 14 октября 2010

возможно ли создать отношения в hibernate / jpa, которые извлекаются, когда извлекается содержащая сущность, но никогда не приводят к обновлениям БД, когда сохраняющая сущность сохраняется?Я попытаюсь прояснить требование на примере.

У меня есть простая сущность B

@Entity
public class B {

    private int bId;

    @Id
    public int getBId() {
        return bId;
    }

    public void setBId(int aId) {
        bId = aId;
    }
}

И еще одна сущность A, которая содержит однонаправленное множество ко многимсопоставление с этим классом.

@Entity
public class A {

    private int aId;
    private List<B> bs;

    @Id
    public int getAId() {
        return aId;
    }

    public void setAId(int aId) {
        this.aId = aId;
    }

    @ManyToMany
    @JoinTable(name = "A_B",
            joinColumns = {@JoinColumn(name = "AID")},
            inverseJoinColumns = {@JoinColumn(name = "BID")}
            )    
    public List<B> getBs() {
        return bs;
    }

    public void setBs(List<B> aBs) {
        bs = aBs;
    }
 }

Когда сущность A извлекается из базы данных и впоследствии объединяется следующим образом

    A a = em.find(A.class, 1);
    a.getBs().size();
    em.merge(a);

, объединение приводит к следующим операторам SQL

Hibernate: 
    delete 
    from
        A_B 
    where
        AID=?
Hibernate: 
    insert 
    into
        A_B
        (AID, BID) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        A_B
        (AID, BID) 
    values
        (?, ?)

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

Поэтому мой вопрос таков: возможно ли отобразить такие коллекции «только для чтения» и избежать изменений в БД?

С наилучшими пожеланиями

Томас

Обновление:

Вот таблицы и данные, которые я использую:

CREATE TABLE A (
        AID INTEGER NOT NULL
    )
    DATA CAPTURE NONE ;

CREATE TABLE B (
        BID INTEGER NOT NULL
    )
    DATA CAPTURE NONE ;

CREATE TABLE A_B (
        AID INTEGER NOT NULL,
        BID INTEGER NOT NULL
    )
    DATA CAPTURE NONE ;

INSERT INTO A (AID) VALUES (1);

INSERT INTO B (BID) VALUES (1);
INSERT INTO B (BID) VALUES (2);

INSERT INTO A_B (AID, BID) VALUES (1, 1);
INSERT INTO A_B (AID, BID) VALUES (1, 2);

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

a.getBs().size();

Примечание: я также добавил строку сверху к исходному сообщению.

1 Ответ

2 голосов
/ 14 октября 2010

Как написано в комментарии, я не мог изначально воспроизвести поведение.Не изменяя коллекцию B, Hibernate просто обновлял A, оставляя таблицу соединений нетронутой.Однако, изменив коллекцию Bs (например, добавив B), я мог получить DELETE, а затем INSERT.Я не знаю, иллюстрирует ли это ваш сценарий, но вот мое объяснение ...

При использовании Collection или List без и @IndexColumn (или @CollectionId), вы получаете семантическую сумку со всеми их недостатками: когда вы удаляете элемент или изменяете коллекцию, Hibernate сначала удаляет все элементы, а затем вставляет (у него нет способа сохранитьпорядок).

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

  1. Установите семантику (т.е. используйте Set, если вы нетребуется List, что справедливо для 95% случаев).

  2. Семантический пакет с первичным ключом (то есть использованиеList с @CollectionId) - Я не проверял это .

  3. true Список семантических (т.е. используйте List с @org.hibernate.annotations.IndexColumn или эквивалентом JPA 2.0 @OrderColumn, если вы используете JPA 2.0)

Вариант 1 является очевидным выбором, если вы не используетене нужно List.Если вы это сделаете, я протестировал только вариант 3 (кажется более естественным), который вы бы реализовали следующим образом (требуется дополнительный столбец и уникальное ограничение для (B_ID, BS_ORDER) в таблице соединений):

@Entity
public class A {

    private int aId;
    private List<B> bs;

    @Id
    public int getAId() { return aId; }

    public void setAId(int aId) { this.aId = aId; }

    @ManyToMany
    @JoinTable(name = "A_B",
            joinColumns = {@JoinColumn(name = "AID")},
            inverseJoinColumns = {@JoinColumn(name = "BID")}
            )
    @org.hibernate.annotations.IndexColumn(name = "BS_ORDER")
    public List<B> getBs() { return bs; }

    public void setBs(List<B> aBs) { bs = aBs; }
}

И Hibernate обновит столбец BS_ORDER в соответствии с требованиями после обновления / удаления Bs.

Ссылки


Я изменил свой тестовый код и переключился на Set в качестве замены для Списка.Из-за этого изменения операторы удаления и обновления больше не генерируются, пока коллекция остается неизменной.К сожалению, у меня есть условия в моем проекте, где коллекция изменена.Поэтому я просил семантику только для чтения, где hibernate загружает сопоставленные данные из БД, но не сохраняет никаких изменений, которые могли бы быть сделаны в коллекции.

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

  1. Я думал, что ваша задача - УДАЛИТЬ, а затем ВСТАВИТЬ, и "только чтение"часть "вашего вопроса выглядела как уродливое решение реальной проблемы.

  2. Требование" extra ", т.е. не сохранять состояние постоянной коллекции, которая была изменена (что необычно, когда у вас есть постоянная коллекция, вы обычно хотите сохранить ее, если измените ее состояние), было непонятно, по крайней мере, не для меня.

В любом случае ... Относительно второгоотметим, что аннотация @Immutable Hibernate здесь не помещается (она запрещает все модификации, исключая исключение, если таковое имеется).Но, возможно, вы могли бы поработать над временной копией коллекции вместо изменения постоянной?

...