Каскадирование jpa удаляет обратную связь

Мой вопрос касается каскадного удаления с помощью JPA и Eclipselink.

Я хотел бы смоделировать простую связь между двумя объектами: A и B. B ссылается на A через свойство ref2a (в терминах БД B.ref2a подключен к A.id через внешний ключ с параметром «ON DELETE CASCADE»). Моя цель - когда объект A удаляется, чтобы каскадировать удаление для всех объектов B, которые на него ссылаются.

Я много искал, но я не могу заставить его работать. Большинство решений, которые я нашел, предназначены для противоположной ситуации: A содержит набор ссылок на B. Это работает как шарм. Но если ссылка находится на стороне Б, я не знаю, как это сделать.

Вот пример кода:

@Entity
public class A 
{
    @Id
    @GeneratedValue
    private Integer id;

    private String name;
    // ...
}

@Entity
public class B 
{
    @Id
    @GeneratedValue
    private Integer id;

    private String name;

    @OneToOne
    @JoinColumn(
            foreignKey=@ForeignKey(
                    foreignKeyDefinition="FOREIGN KEY ref2a REFERENCES A id ON DELETE CASCADE"
                    )
            )
    private A ref2a;
    // ...
}

И тестовый код:

public class CascadeTest extends TestCase
{
    private EntityManagerFactory emf;
    private EntityManager em;

    @Override
    protected void setUp() throws Exception {
        emf = Persistence.createEntityManagerFactory("myDB");
        em = emf.createEntityManager();
    }

    @Override
    protected void tearDown() throws Exception {
        em.close();
        emf.close();
    }

    public void testApp()
    {
        Integer aid = -1, bid = -1;

        try {
            em.getTransaction().begin();

            A a = new A();
            a.setName("My name is A");

            B b = new B();
            b.setRef2a(a);
            b.setName("My name is B, please delete me when A is gone.");

            em.persist(a);
            em.persist(b);

            em.getTransaction().commit();

            aid = a.getId();
            bid = b.getId();

        } finally {
            if (em.getTransaction().isActive())
                em.getTransaction().rollback();
        }

        try {
            em.getTransaction().begin();

            B b = em.find(B.class, bid);
            assertNotNull(b);
            assertEquals("My name is B, please delete me when A is gone.", b.getName());
            assertEquals("My name is A", b.getRef2a().getName());
            assertEquals(aid, b.getRef2a().getId());

            A a = em.find(A.class, aid);
            assertEquals("My name is A", a.getName());

            em.remove(a);
            em.getTransaction().commit();

            em.getTransaction().begin();

            // a should have been removed.
            // This passes OK.
            a = em.find(A.class, aid);
            assertNull(a);

            // Cascading deletes should have deleted also b.
            b = em.find(B.class, bid);

            // PROBLEM: This fails - b is still here.
            assertNull(b);
            em.getTransaction().commit();

        } finally {
            if (em.getTransaction().isActive())
                em.getTransaction().rollback();
        }

    }
}

person mbax    schedule 28.01.2015    source источник


Ответы (2)


Я решил свою проблему. Действительно очень просто - мой первоначальный код был почти правильным. У меня только что была проблема с синтаксисом в каскаде внешнего ключа. Атрибуты должны быть в скобках "()", я пропустил это в документации. Итак, изменение, которое мне нужно было сделать, это:

@OneToOne
    @JoinColumn(
            foreignKey=@ForeignKey(
                    foreignKeyDefinition="FOREIGN KEY (ref2a) REFERENCES A (id) ON DELETE CASCADE"
                    )
            )
    private A ref2a;

Обратите внимание на скобки вокруг двух атрибутов.

Это работает, удаление объекта A также каскадирует связанные с ним объекты B.

Спасибо всем за вашу помощь!

person mbax    schedule 18.02.2015

EclipseLink предоставляет аннотацию @CascadeOnDelete, которая согласуется с ограничением базы данных «ON DELETE CASCADE». Эта аннотация сообщает EclipseLink, что объект будет удален ограничением внешнего ключа базы данных, когда этот объект будет удален, и при использовании DDL EclipseLink сгенерирует таблицу с соответствующим ограничением. см. https://wiki.eclipse.org/EclipseLink/Examples/JPA/DeleteCascade для подробностей.

Однако я думаю, что вы можете обойтись простым каскадным удалением сопоставления FriendshipRelation.person:

@Entity
public class FriendshipRelation {
..
  @OneToOne(cascade=CascadeType.REMOVE)
  private Person person;

Это заставит JPA удалить любое указанное лицо при удалении экземпляра FriendshipRelation.

person Chris    schedule 29.01.2015
comment
Привет, Крис, спасибо за ваш ответ. Я пробовал @CascadeOnDelete, но это не работает. Кстати, я хочу сделать обратное тому, что вы описали. Я не хочу удалять упомянутого человека. Я хочу удалить все Дружеские Отношения, которые связаны с удаляемым человеком через вторую ссылку (один к одному в обратном порядке), а не через список Дружеских Отношений. - person mbax; 30.01.2015
comment
Аннотация @CascadeOnDelete не удалит для вас дочерние строки. Он просто сообщает JPA, что база данных удалит их. Если вы не настроили каскад при удалении в своей схеме БД, он не будет работать. - person André Schild; 30.01.2015
comment
@mbax, проблема неясна, потому что вы включили не связанный с ней OneToMany - попробуйте удалить его из описания проблемы. Похоже, проблема в том, что у вас есть одно сопоставление OneToOne, и вы хотите, чтобы ссылочная сторона удалялась при удалении указанного человека. Если это так, вам нужно добавить сопоставление OneToOne из Person в FriendshipRelation, сопоставленное отношением FriendshipRelation.person, сделав его двунаправленным отношением. В противном случае JPA не сможет узнать об экземпляре FriendshipRelation, который вы хотите удалить. Затем вы можете отметить это с помощью cascade.remove. - person Chris; 30.01.2015