Search code examples
javajpaeclipselinkcascading

jpa cascading deletes reverse relationship


My question is about cascading deletes with JPA and Eclipselink.

I would like to model a simple relationship between two entities: A and B. B references A through a property ref2a (in DB terms B.ref2a is connected to A.id through a foreign key with "ON DELETE CASCADE"). My goal is when an A object is deleted to cascade the delete to all B objects that reference it.

I searched a lot, but I cannot make it work. Most solutions I have found are for the opposite situation: A contains a collection of references to B. This works like a charm. But if the reference is on the B side, I don't know how to do it.

Here is the Code sample:

@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;
    // ...
}

And the test code:

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();
        }

    }
}

Solution

  • I have solved my problem. Really really simple - my initial code was almost right. I just had a syntax problem in the foreign key cascade. The attributes needed to be in brackets "()", I had overlooked that in the documentation. So the change I needed to do is:

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

    Please notice the brackets around the two attributes.

    This works, deleting an A object also cascades its linked B objects.

    Thanks to everybody for your help!