Может ли Hibernate удалять потерянные коллекции при обновлении отдельного объекта?

Я знаю, что удаление потерянных дочерних объектов - это общий вопрос о 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 видит этот экземпляр объекта - я не присоединяю и не объединяю объект каким-либо другим способом. Я полагаюсь на клиента для передачи объектов, идентификаторы которых соответствуют фактическим объектам в базе данных. Например, логика в моем методе службы API выглядит примерно так:

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

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

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

2 ответа

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

<?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 (second))

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 после фиксации транзакции с базой данных и повторно подключен при возникновении нового запроса клиента. Этот шаблон известен как сеанс на разговор и делает ненужным даже присоединение. Автоматическое управление версиями используется для изоляции одновременных изменений, и сеанс обычно не может быть сброшен автоматически, но явно.

Отказ от ответственности: у меня нет сценария, который использует длительный разговор. 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();

}

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

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

Другие вопросы по тегам