How to map embeddable that contains many-to-many

Viewed 115

I have the following DB schema:

enter image description here

And the following model:

enter image description here

I'm struggling to map the RootChildren as an @Embedded within Root. I've tried using a @ManyToMany within RootChildren to map the set of Child, but I couldn't make it work. It doesn't seem like there's a lot of documentation online on the subject or at least I couldn't find anything that works so hopefully someone will be able to help.

Note that I do not want to model bi-directional relationships if possible. Thanks!

Erratum: RootChildren#children should be a SortedSet.

2 Answers

I did the following mapping in a scratch project I have (running on embedded Derby, but that should not matter) and it seems to work. I did have problem defining the collection as SortedSet, hibernate complained that "A sorted collection must define and ordering or sorting"; it would go away if I added an ordering property (visible to the state of the entity), which is not what you want.

@Entity
@Table(name="SO_ROOT")
public class Root {
    @Id
    @GeneratedValue(strategy=SEQUENCE)
    private long id;

    @Embedded
    private RootChildren children;

    // getters and setters
}
@Embeddable
public class RootChildren {
    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(
            name = "SO_ROOT_CHILDREN",
            joinColumns = @JoinColumn(name = "root_id"),
            inverseJoinColumns = @JoinColumn(name = "child_id")
    )
    @OrderColumn(name = "xorder")
    private List<Child> children;

    // getters and setters
}
@Entity
@Table(name="SO_CHILD")
public class Child {
    @Id
    @GeneratedValue(strategy=SEQUENCE)
    private long id;

    // getters and setters
}

Test code:

        EntityManager em = null;
        try {
            em = ...
            em.getTransaction().begin();

            Root root1 = new Root();
            Root root2 = new Root();
            Child r1c1 = new Child();
            Child r1c2 = new Child();
            Child r2c1 = new Child();
            Child r2c2 = new Child();
            RootChildren rc1 = new RootChildren();
            rc1.setChildren(Arrays.asList(r1c1,r1c2));
            RootChildren rc2 = new RootChildren();
            rc2.setChildren(Arrays.asList(r2c1,r2c2));
            root1.setChildren(rc1);
            root2.setChildren(rc2);

            em.persist(root1);
            em.persist(root2);

            em.getTransaction().commit();
        }
        finally {
            EntityManagerUtil.safeClose(em);
        }

Child cannot be an entity if you want to use embeddables.

You can map RootChildren as separate entity with a composite key containing Root and Child as a @ManyToOne association:

@Entity
@IdClass( PK.class )
public static class RootChildren {

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    Root root;

    @Id
    @ManyToOne(fetch = FetchType.LAZY)
    Child child;

    int ordering;

}

public class PK implements Serializable {

    private Root root;

    private Child child;

    public PK(Root root, Child child) {
        this.root = root;
        this.child = child;
    }

    private PK() {
    }

    //Getters, setters, hashcode and equals are omitted for brevity
}

Alternatively, if you create a separate class like EmbeddedChild, you could use this mapping:

@Entity
class Root {

  @Embedded
  RootChildren children;
}


@Embeddable
class RootChildren {

   @ElementCollection
   Set<EmbeddedChild> children;
}

@Embeddable
class EmbeddedChild {

}
Related