Может ли Hibernate удалять потерянные коллекции при обновлении отдельного объекта? - PullRequest
16 голосов
/ 21 октября 2010

Я знаю, что удаление потерянных дочерних объектов является распространенным вопросом в SO и общей проблемой для людей, впервые знакомых с Hibernate, и что достаточно стандартный ответ заключается в том, чтобы у вас было какое-то изменение cascade=all,delete-orphan или cascade=all-delete-orphan на детская коллекция.

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

Parent parent = session.get(...);
parent.getChildren().clear();
session.update(parent);

Мое текущее отображение для класса Parent выглядит следующим образом:

<bag name="children" cascade="all-delete-orphan">
    <key column="parent_id" foreign-key="fk_parent_id"/>
    <one-to-many class="Child"/>
</bag>

Это прекрасно работает для меня при обновлении прикрепленного объекта, но у меня есть вариант использования, в котором мы хотели бы иметь возможность взять отсоединенный объект (который был отправлен в наш метод API удаленным клиентом через HTTP / JSON) и передать его непосредственно в сеанс Hibernate - чтобы позволить клиентам иметь возможность манипулировать родительским объектом любым удобным для них способом и сохранить изменения.

При вызове session.update(parent) для моего отдельного объекта строки в дочерней таблице теряются (столбец FK имеет значение null), но не удаляются. Обратите внимание, что когда я звоню session.update(), это первый раз, когда Hibernate Session видит этот экземпляр объекта - я не присоединяю и не объединяю объект с Session любым другим способом. Я полагаюсь на клиента для передачи объектов, идентификаторы которых соответствуют фактическим объектам в базе данных. Например, логика в моем методе службы API выглядит примерно так:

String jsonString = request.getParameter(...);
Parent parent = deserialize(jsonString);
session.update(parent);

Возможно ли, чтобы Hibernate обнаруживал коллекции детей-сирот в отсоединенных родительских объектах при передаче в session.update(parent)? Или я каким-то образом неправильно использую отсоединенный объект?

Я надеялся, что смогу избежать каких-либо сложных взаимодействий с Hibernate, чтобы сохранить изменения в отдельном экземпляре. Мой метод API не нуждается в дальнейшей модификации отсоединенного объекта после вызова session.update(parent), этот метод просто отвечает за сохранение изменений, внесенных удаленными клиентскими приложениями.

Ответы [ 2 ]

8 голосов
/ 30 октября 2010

Ваше отображение (упрощенное)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="br.com._3988215.model.domain">
    <class name="Parent" table="PARENT">
        <id name="id">
            <generator class="native"/>
        </id>
        <bag cascade="all,delete-orphan" name="childList">
            <key column="PARENT_ID" not-null="false"/>
            <one-to-many class="Child"/>
        </bag>
    </class>
    <class name="Child" table="CHILD">
        <id name="id" column="CHILD_ID">
            <generator class="native"/>
        </id>
    </class>
</hibernate-mapping>

производит

PARENT
    ID

CHILD
    CHILD_ID
    PARENT_ID

Согласно тому, что вы сказали

Я бы хотел, чтобы Hibernate обнаружил, что дочерняя коллекция была удалена из родительского объекта, и удалил строки в дочерней таблице из базы данных при обновлении родительского объекта

что-то вроде

Parent parent = session.get(...);
parent.getChildren().clear();

session.update(parent);

Вы сказали, что он работает нормально , потому что у вас есть прикрепленный родительский экземпляр

Теперь давайте посмотрим на следующее (Обратите внимание Assert.assertNull (секунда) )

public class WhatYouWantTest {

    private static SessionFactory sessionFactory;

    private Serializable parentId;

    private Serializable firstId;
    private Serializable secondId;

    @BeforeClass
    public static void setUpClass() {
        Configuration c = new Configuration();
        c.addResource("mapping.hbm.3988215.xml");

        sessionFactory = c.configure().buildSessionFactory();
    }

    @Before
    public void setUp() throws Exception {
        Parent parent = new Parent();
        Child first   = new Child();
        Child second  = new Child();

        Session session = sessionFactory.openSession();
        session.beginTransaction();

        parentId = session.save(parent);
        firstId  = session.save(first);
        secondId = session.save(second);

        parent.getChildList().add(first);
        parent.getChildList().add(second);

        session.getTransaction().commit();
        session.close();
    }

    @Test
    public void removed_second_from_parent_remove_second_from_database() {
        Parent parent = new Parent();
        parent.setId((Integer) parentId);

        Child first = new Child();
        first.setId((Integer) firstId);

        /**
          * It simulates the second one has been removed
          */
        parent.getChildList().add(first);

        Session session = sessionFactory.openSession();
        session.beginTransaction();

        session.update(parent);

        session.getTransaction().commit();
        session.close();

        session = sessionFactory.openSession();
        session.beginTransaction();

        Child second = (Child) session.get(Child.class, secondId);
        Assert.assertNull(second);

        session.getTransaction().commit();
        session.close();
    }
}

К сожалению , тест не пройден. Что вы можете сделать ???

  • Включить длительный разговор

Hibernate ссылка говорит

Расширенный (или длинный) сеанс - сеанс Hibernate может быть отключен от базового соединения JDBC после фиксации транзакции базы данных и переподключен, когда происходит новый запрос клиента. Этот шаблон известен как сеанс на разговор и делает ненужным даже присоединение . Автоматическое управление версиями используется для изоляции одновременных изменений, и сеанс обычно не может быть сброшен автоматически, но явно.

Disclaimer: у меня нет сценария, который использует длительный разговор. Java EE Stateful сессионные компоненты поддерживают длительный диалог. Но его поддержка для JPA (не Hibernate)

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

Создайте класс с именем AlternativeParent , который расширяет Parent

public class AlternativeParent extends Parent {}

Теперь его отображение (обратите внимание на Child как составной элемент вместо простого @Entity)

<class name="AlternativeParent" table="PARENT">
    <id name="id">
        <generator class="native"/>
    </id>
    <bag name="childList" table="CHILD">
        <key column="PARENT_ID" not-null="false"/>
        <composite-element class="Child">
            <property column="CHILD_ID" name="id"/>
        </composite-element>
    </bag>
</class>

Теперь реализуйте удобный метод equals в классе Child

public boolean equals(Object o) {
    if (!(o instanceof Child))
        return false;

    Child other = (Child) o;
    // identity equality
    // Used by composite elements
    if(getId() != null) {
        return new EqualsBuilder()
                   .append(getId(), other.getId())
                   .isEquals();
    } else {
        // object equality
     }
}

Если я рефакторинг тестового примера, показанного выше (теперь с использованием AlternativeParent вместо)

@Test
public void removed_second_from_parent_remove_second_from_database() {
    AlternativeParent parent = new AlternativeParent();
    parent.setId((Integer) parentId);

    Child first = new Child();
    first.setId((Integer) firstId);

    /**
      * It simulates the second one has been removed
      */
    parent.getChildList().add(first);

    Session session = sessionFactory.openSession();
    session.beginTransaction();

    session.update(parent);

    session.getTransaction().commit();
    session.close();

    session = sessionFactory.openSession();
    session.beginTransaction();

    Child second = (Child) session.get(Child.class, secondId);
    Assert.assertNull(second);

    session.getTransaction().commit();
    session.close();

}

Я вижу зеленую полосу

1 голос
/ 27 октября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...