Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use the @ManyToMany with two lists on the same table

I have a situation where an entity could use another entity, and it could be used by another, so i have defined a ManyToMany relation that reference the same entity, so i could have listUse and listUsedBy, and both are persisted in the same table entity_usage :

@ManyToMany
@JoinTable(name = "entity_usage",
        joinColumns = {
            @JoinColumn(name = "id_use", referencedColumnName = "id")},
        inverseJoinColumns = {
            @JoinColumn(name = "id_used_by", referencedColumnName = "id")})
private List<Entity> listUse;

@ManyToMany
@JoinTable(name = "entity_usage",
        joinColumns = {
            @JoinColumn(name = "id_use_by", referencedColumnName = "id")},
        inverseJoinColumns = {
            @JoinColumn(name = "id_use", referencedColumnName = "id")})
private List<Entity> listUsedBy;

Exemple : Entity A could use Entity B and C, so Entity B and C are used by A. Now my problem is when i add B and C to listUse, they are persisted in entity_usage, but when try to display listUsedBy i have to redeploy my project, otherwise listUsedBy remains empty, is there a way to refresh listUsedBy when persist my entity without having to redeploy my project.

like image 792
Bilal Dekar Avatar asked Jun 13 '17 09:06

Bilal Dekar


1 Answers

This is the general approach:

@Entity
public class SomeEntity
{
    @ManyToMany
    @JoinTable(name = "entity_usage",
        joinColumns = @JoinColumn(name = "using_id"),
        inverseJoinColumns = @JoinColumn(name = "used_by_id"))
    private Set<SomeEntity> using = new LinkedHashSet<>();

    @ManyToMany(mappedBy = "using")
    private Set<SomeEntity> usedBy = new LinkedHashSet<>();

    public void addUsing(SomeEntity entity)
    {
        this.using.add(entity);
        entity.usedBy.add(this);
    }

    public void addUsedBy(SomeEntity entity)
    {
        this.usedBy.add(entity);
        entity.using.add(this);
    }
}

and it's used:

public void someMethod(long parentEntityId, long childEntityId)
{
    EntityManager em = getSomeEntityManager();

    SomeEntity parentEntity = em.find(SomeEntity.class, parentEntityId);
    SomeEntity childEntity = em.find(SomeEntity.class, childEntityId);

    parentEntity.addUsing(childEntity);
}

typically this is a transactional EJB method.
Note that there's no need to em.merge anything, since entities are already managed by em.find. Anyway, whichever method you'll use to manage your entities (query, find, persist, merge), remember that's important to call addUsing/addUsedBy only when both entities are managed.

This is one of the main incoherences that ORM logic cannot handle by its own: you have to inform both entities (parent and child) of their relation.
It's not sufficient to set the relation only on one side - if you only say that A is parent of B, B still doesn't know who is its parent.

However, there exists alternative approaches, like setting only the owning side of the relation (parent.getChildren().add(child)), flush, and refresh the child.

Nevertheless (as I experienced very well on my skin) the alternatives are very hard to handle in real world complex applications.

As a side note, I'd use Set instead of List for the relation, unless you need some kind of insertion-order.

like image 152
Michele Mariotti Avatar answered Nov 06 '22 15:11

Michele Mariotti