2

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

    }
}
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
D-FENS
  • 1,438
  • 8
  • 21

2 Answers2

2

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!

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
D-FENS
  • 1,438
  • 8
  • 21
-1

EclipseLink provides a @CascadeOnDelete annotation that aligns with database "ON DELETE CASCADE" contraint. This annotation tells EclipseLink that the entity will be deleted by the database foriegn key constraint when this entity is deleted, and if using DDL, EclipseLink will generate the table with the proper constraint. see https://wiki.eclipse.org/EclipseLink/Examples/JPA/DeleteCascade for details.

I think though that you can get by with a simple cascade delete on the FriendshipRelation.person mapping:

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

This will force JPA to remove any referenced person when the FriendshipRelation instance is removed.

Chris
  • 20,138
  • 2
  • 29
  • 43
  • 1
    Hi, Chris, thank you for your response. I tried @CascadeOnDelete but it does not work. By the way, I want to do the opposite of what you described. I don't want to delete a referenced person. I want to delete all FriendshipRelations, which are connected to a deleted person through the second reference (one to one reverse) and not through the "friendshipRelations" list. – D-FENS Jan 30 '15 at 06:58
  • The @CascadeOnDelete annotation will not delete the child rows for you. It just tells JPA that the database will delete them. If you did not have setup the on delete cascade in your DB schema, then it won't work. – André Schild Jan 30 '15 at 07:55
  • @mbax, the problem is unclear because you included the OneToMany which is unrelated - try removing it from the problem description. It sounds like the problem is you have a single OneToOne mapping, and you want the referencing side removed when the referenced Person is removed. If so, then you need to add a OneToOne mapping from Person to FriendshipRelation "mappedby" the FriendshipRelation.person relationship, making it a bidirectional relationship. Otherwise JPA has no way of knowing about the FriendshipRelation instance you want removed. You can then mark this with cascade.remove. – Chris Jan 30 '15 at 14:54