0

I have

@Entity
public class Node
{
    @ManyToOne
    private Node parent;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Node> children;

    @ManyToMany
    private List<Owner> ownerList;

    ...
}

and I want to propagate owners to children on persist/merge. The rule is:

Any node must have at least its parent node owners

I tried

@PreUpdate
public void propagateOwnersTopDown(Node node)
{
    for(Node child : node.getChildren())
    {
        for(Owner owner : node.getOwnerList())
        {
            if(!child.getOwnerList().contains(owner))
            {
                child.getOwnerList().add(owner);
            }
        }
    }
}

and either

@PreUpdate
public void propagateOwnersBottomUp(Node node)
{
    if(node.getParent() == null) return;

    for(Owner owner : node.getParent().getOwnerList())
    {
        if(!node.getOwnerList().contains(owner))
        {
            node.getOwnerList().add(owner);
        }
    }
}

even if I know it's not legal, and in fact, these don't work (@PreUpdate is invoked only on the root).

I know I can

  • write a addOwner() method which propagates owner to children
    • but if some other code calls node.getOwnerList().add(owner) propagation is busted

or

  • explicitly call propagateOwnersTopDown() or something like just before em.merge(node) (generally when inside a transaction)
    • but I have to add this to every EJB/transactional method

I wonder if there's some neat way to accomplish this.

I'm no DB expert, but is it possible to achieve the consistency with a stored procedure/trigger/anything?

if true, is it possible/legal to invoke the procedure/trigger/anything in @PrePersist/@PreUpdate?

I'm on EclipseLink 2.5.2 and MySQL 5.6

Thank you.

Michele Mariotti
  • 7,372
  • 5
  • 41
  • 73

1 Answers1

0

I implemented my own Listener subsystem:

protected <T extends AbstractEntity> void invokeListeners(T entity, Class<? extends Annotation> annotation)
{
    ServiceListeners serviceListeners = entity.getClass().getAnnotation(ServiceListeners.class);
    if(serviceListeners != null)
    {
        try
        {
            for(Class<?> listenerClass : serviceListeners.value())
            {
                Object instance = listenerClass.newInstance();

                Set<Method> methods = ReflectionUtils.getAllMethods(listenerClass, ReflectionUtils.withAnnotation(annotation));
                for(Method m : methods)
                {
                    m.invoke(instance, entity);
                }
            }
        }
        catch(Exception e)
        {
            throw new RuntimeException(e.getMessage(), e);
        }
    }
}

public <T extends AbstractEntity> void create(T entity)
{
    invokeListeners(entity, OnCreate.class);

    em.persist(entity);
}

public <T extends AbstractEntity> T update(T entity)
{
    invokeListeners(entity, OnUpdate.class);

    T entity2 = em.merge(entity);
    em.flush();
    em.refresh(entity2);

    return entity2;
}
Michele Mariotti
  • 7,372
  • 5
  • 41
  • 73