Michele Mariotti Michele Mariotti - 5 months ago 21
Java Question

Hibernate: merge two related entities

I have this entity:

@Entity
public class Node
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column
protected Long id;

@Column
private String name;

@ManyToOne
@JoinColumn(name = "NODE_ID")
private Node parent;

@OneToMany(mappedBy = "parent")
private List<Node> children = new ArrayList();
}


Suppose there is an external source that generates a tree, and that I want to import (read make persistent) this whole tree:

public void importTree()
{
Node root = someExternalSource.receiveTree();

// service.preOrderSave(root); ERROR - Not-null property references a transient value
// service.postOrderSave(root); ERROR - Not-null property references a transient value

// no way out :(
}


Note that I absolutely want to avoid cascade because this is an exceptional case, and it would interfere with the normal application logic.

What I don't understand is why TransientPropertyValueException is thrown when calling
em.persist
/
em.merge
instead on transaction commit.

However, is there any way out?

Thanks




this is the rest of the code:

public Node receiveTree()
{
// dummy code

Node root = new Node();
root.setName("root");

Node child = new Node()
child.setName("child");

root.getChildren().add(child);
child.setParent(root);

return root;
}

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void preOrderSave(Node node)
{
em.persist(node);

for(Node child : node.getChildren())
{
preOrderSave(child);
}
}

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void postOrderSave(Node node)
{
for(Node child : node.getChildren())
{
postOrderSave(child);
}

em.persist(node);
}

Answer

It seems this problem is caused by @GeneratedValue(strategy = GenerationType.IDENTITY), which forces hibernate to flush immediately the insert statement (or something like this, I haven't done enough code-digging).

Switching to another strategy, solves it:

/** The id. */
@XmlTransient
@Id
// NOK, forces immediate flush
// @GeneratedValue(strategy = GenerationType.IDENTITY)
// NOK, in my case defaults to IDENTITY
// @GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence_generator")
// NOK, MySQL doesn't support sequences...
// @SequenceGenerator(name = "sequence_generator", sequenceName = "GLOBAL_SEQUENCE", allocationSize = 100, initialValue = 1000) 
// OK, but ACID is lost
@GeneratedValue(strategy = GenerationType.TABLE, generator = "table_generator")
@TableGenerator(name = "table_generator", table = "SEQUENCE_GENERATOR", pkColumnName = "NAME", valueColumnName = "NEXT_VAL", allocationSize = 100)
@Column(name = "ID")
protected Long id;