0

I have some serious issues with JPA. We are using Vaadin and JPA (EclipseLink 2.5.0)

In my application i have a treebased datastructure. I get from a rest interface new data, put them into a queue, get them with another thread out of the queue and update the nodes in tree which already are in with the new values, those which are new are getting added into the tree. Then i save the parent tree (called System) and with cascade it should save all child nodes into the database. Works fine. Sometimes.

Here is some code snippet of the thread which is saving and adding to the tree:

public void updateDatastructure(System connectedSystem, System receivedSystem) {
    if (connectedSystem != null) {

        boolean needRMUpdate = false;

        dao.createEntityManager();
        dao.beginTransaction();

        for (Node receivedNode : receivedSystem.getNodes().values()) {
            Node nodeInTree = connectedSystem.getNode(receivedNode.getName());
            if (nodeInTree == null) {
                addStructure(receivedNode, connectedSystem);
                needRMUpdate = true;
            } else if (nodeInTree != null) {
                updateStructure(nodeInTree, receivedNode);
            }
        }

        dao.commitTransaction();
        dao.closeEntityManager();

        triggerChangesOfHistoryDatas(historysizedData);
        historysizedData.clear();

        // RuleManager: Update the RuleManager
        if (needRMUpdate)
            XXX_Initializer.ruleManager.addOrUpdateSystem(connectedSystem);
    }
}

The UpdateDatastructure method is called every 10 seconds.

The structure of my Tree looks like that:

System - Node - Data

Above the System node are another nodes but that doesnt matter at the moment.

System does have 0..* Nodes Node does have 0..* Nodes Node does have 0..* Data Data is leaf

@Entity
@Table(name = "node")
@NamedQuery(name = "node.findAll", query = "SELECT n FROM Node n")
public class Node implements TreeNodeBase, Serializable {

private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column(name = "NAME", nullable = false)
private String name;
private String path;

@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, fetch = FetchType.EAGER)
@MapKey(name = "name")
@JoinColumn(name = "PARENT_ID", referencedColumnName = "id")
private Map<String, Node> nodes = new HashMap<String, Node>();

@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE }, fetch = FetchType.EAGER)
@MapKey(name = "name")
@JoinColumn(name = "NODE_ID", referencedColumnName = "id")
private Map<String, Data> datas = new HashMap<String, Data>();

@ManyToOne
@JoinColumn(name = "SYSTEM_ID", referencedColumnName = "id")
private System system;

@ManyToOne
@JoinColumn(name = "PARENT_ID", referencedColumnName = "id")
private Node parentNode;

The datastructure (tree) is always in memory, because multiple user can login into the application and need that data from the tree.

I guess that means i need to let the whole tree managed right?

So is it wrong to use the entityManager like this?

Main Question: Why duplicates the JPA / EclipseLink sometimes "node" sometimes "data" and sometimes "system", but with different IDs.

Here are some screenshots of the database tables:

System Table

Node Table

Data Table

Edit Node Hash and Equals:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    result = prime * result + ((parentNode == null) ? 0 : parentNode.hashCode());
    result = prime * result + ((system == null) ? 0 : system.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Node other = (Node) obj;
    if (name == null) {
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    if (parentNode == null) {
        if (other.parentNode != null)
            return false;
    } else if (!parentNode.equals(other.parentNode))
        return false;
    if (system == null) {
        if (other.system != null)
            return false;
    } else if (!system.equals(other.system))
        return false;
    return true;
}
Mr. T
  • 21
  • 6
  • At a guess: you're using `HashMap` objects to store references to your objects, but either you haven't provided implementations of the `hashCode` method or your `hashCode` method is inconsistent. – JonK Oct 22 '19 at 11:48
  • thank you for your answer, i added the hash method of nodes System-Hashmethod is using ID Data Hashmethod is just using name and parentNode all generated methods – Mr. T Oct 22 '19 at 12:33
  • The recommendation for JPA entities is that you use only their `@Id` property to determine equality (i.e. do these two objects point to the same row in the database as opposed to do these two objects contain the same information) - similarly for `hashCode`, just hash the `@Id` property. – JonK Oct 22 '19 at 12:45
  • but that concludes that i have to generate the ids by myself and not let the eclipselink generate them right? – Mr. T Oct 22 '19 at 13:01
  • That's certainly a possibility yes. I'm not familiar with EclipseLink specifically so you'll have to check this for yourself, but you *might* find that EL proxies your objects and adds its own IDs to them before they're persisted. In which case (and honestly you might actually want to do this anyway) you would probably be better off **not** overriding `hashCode` and `equals` at all. – JonK Oct 22 '19 at 13:07
  • alright...that means a lot of changes for my project.. but thank you very much for your help! Im still open for other solutions if anyone out there has some ideas! – Mr. T Oct 22 '19 at 13:15
  • EclipseLink can generate IDs - auto uses EclipseLink's table generation which allows to preallocated values, so they will be assigned when you call persist on an entity. Otherwise, the IDs will get assigned on flush or commit. As for duplicates - check your graph as you must have multiple instances of these objects holding the same data. You need to tell JPA what actually is used to tell they are the same if you wish to prevent duplicates from getting in, or ensure you are holding only the instance that has its primary key assigned. – Chris Oct 22 '19 at 14:23
  • Thats the point. I already have equals methods to prevent the duplicates. What i dont understand is, that the ids of the "old" entity in the database are null and there is an new one with an id. Does that mean that my entity in my code, is deleted out of the HashMap, which i use to hold the relation to the childs? Or when does this happen or why? – Mr. T Oct 23 '19 at 13:06
  • That particular problem you should be able to solve by adding `orphanRemoval = true` to your `@OneToMany` annotation. – JonK Oct 23 '19 at 14:15
  • i check my code again and i dont use equals method to check if the objects are the same. I ask the parent node or system if he got already the child. If not ill add the child. i really dont understand the issue... on my HashMap i hold the name of the node as key and the object as the value so it cant be added twice when i ask for the child if its contained by name and add id afterwards?! – Mr. T Oct 25 '19 at 08:56
  • You might not use `hashCode` or `equals` directly, but you're putting these entities inside a *Hash*Map. ***That*** is most definitely using the `hashCode` method to both situate the entity inside the map when it's first added, and to find the entity within the map later on. If your `hashCode` method isn't consistent then the lookup within the map can fail (which might cause a duplicate to be added instead of updated, because it can't find the copy it already has). – JonK Oct 25 '19 at 09:18

0 Answers0