Reputation: 2535
I am having trouble using Hibernate to insert a bunch of transient child entities which may have composite keys composed of other transient child entities by saving the detached parent entity. I am pretty sure I have my composite key class set up properly, but every time I try to save the parent entity which has transient entities (no generated ID yet), I get this error:
org.hibernate.id.IdentifierGenerationException: null id generated for:class org._.website.data.entity.Mean
So Hibernate never generates the Composite key which I figured it should be able to given the properties being referenced. However, since the properties that the composite key is referencing are also transient, there is no ID to manually set the composite key with. So I was hoping that Hibernate would be smart enough to do the generation itself.
Is there a way to get Hibernate to handle saving/inserting of transient child entities with composite keys which reference other transient child entities?
Here is the code I am working with. If fails on projectDao.save(project);
Variable variable = new Variable();
variable.setProject(project);
variable.setName("x");
Belief belief = new Belief();
belief.setProject(project);
belief.setName("model-1");
Mean mean = new Mean();
mean.setVariable(variable);
mean.setBelief(belief);
// I can't do this because variable and belief are transient and have no ID yet
//MeanPK meanPk = new MeanPK(variableId, beliefId);
//mean.setPk(meanPk);
belief.getMeans().add(mean);
project.getVariables().add(variable);
project.getBeliefs().add(belief);
projectDao.save(project);
If it helps, here is the Embeddable MeanPK class
@Embeddable
public static class MeanPK implements Serializable {
private static final long serialVersionUID = 341373316515655834L;
@GeneratedValue
@Column(name = "belief_id", nullable = false, updatable = false)
protected Integer beliefId;
@GeneratedValue
@Column(name = "variable_id", nullable = false, updatable = false)
protected Integer variableId;
// getters/setters excluded for brevity
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof MeanPK)) {
return false;
}
MeanPK other = (MeanPK) obj;
return beliefId.equals(other.beliefId) && variableId.equals(other.variableId);
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(beliefId).append(variableId).toHashCode();
}
}
If I absolutely have to, I can save the transient entities referenced by the composite key first to get the IDs and manually construct the MeanPK composite key, but I was hoping that Hibernate was able to handle that on its own with a single call to projectDao.save(...);
Thanks for your help!
Upvotes: 2
Views: 2068
Reputation: 1
Yeah its @Transient
so your making your call like so?
MeanPK meanPk = new MeanPK(variableId, beliefId);
does this work for you when making this call
Mean mean = new Mean(variableId, beliefId);
Upvotes: 0
Reputation: 2535
I figured out the answer to my question and I thought I would post it in case anyone found it useful.
What I did was store the referenced Variable and Belief entities in the MeanPK class itself when they are set to the Mean entity. I added some logic to the ID getters in the MeanPk class so that when they are called by hibernate, it will first check to set the ids from the objects stored in the MeanPK class. This works because hibernate will insert and persist the transient Variable and Belief entities before it gets to the Mean entity since it is the bottom-most child. And I have CascadeType.ALL for all my collections, so I don't need to worry about manually saving each entity, and Hibernate will cascade the save operation from parent to child.
Here is the updated MeanPK class and Mean entity class:
@Entity
@Table(name = "mean")
public class Mean implements Serializable {
private static final long serialVersionUID = -5732898358425089380L;
// composite key
@EmbeddedId
private MeanPK pk = new MeanPK();
@ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
@JoinColumn(name = "belief_id", insertable = false, nullable = false, updatable = false)
private Belief belief;
@ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
@JoinColumn(name = "variable_id", insertable = false, nullable = false, updatable = false)
private Variable variable;
// more attributes excluded
public MeanPK getPk() {
return pk;
}
protected void setPk(MeanPK pk) {
this.pk = pk;
}
public Belief getBelief() {
return belief;
}
public void setBelief(Belief belief) {
pk.setBelief(this.belief = belief);
}
@XmlTransient
public Variable getVariable() {
return variable;
}
public void setVariable(Variable variable) {
pk.setVariable(this.variable = variable);
}
@Embeddable
public static class MeanPK implements Serializable {
private static final long serialVersionUID = 341373316515655834L;
@Access(AccessType.PROPERTY)
@Column(name = "belief_id", nullable = false, updatable = false)
protected Integer beliefId;
@Access(AccessType.PROPERTY)
@Column(name = "variable_id", nullable = false, updatable = false)
protected Integer variableId;
@Transient
private Belief belief;
@Transient
private Variable variable;
public Integer getBeliefId() {
if (beliefId == null && belief != null) {
beliefId = belief.getId();
}
return beliefId;
}
protected void setBeliefId(Integer beliefId) {
this.beliefId = beliefId;
}
public Belief getBelief() {
return belief;
}
void setBelief(Belief belief) {
this.belief = belief;
if (belief != null) {
beliefId = belief.getId();
}
}
public Integer getVariableId() {
if (variableId == null && variable != null) {
variableId = variable.getId();
}
return variableId;
}
protected void setVariableId(Integer variableId) {
this.variableId = variableId;
}
public Variable getVariable() {
return variable;
}
void setVariable(Variable variable) {
this.variable = variable;
if (variable != null) {
variableId = variable.getId();
}
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof MeanPK)) {
return false;
}
MeanPK other = (MeanPK) obj;
return getBeliefId().equals(other.getBeliefId()) && getVariableId().equals(other.getVariableId());
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(getBeliefId()).append(getVariableId()).toHashCode();
}
}
}
Upvotes: 1
Reputation: 1
look into using cascading annotation on your entity classes... The save operation will try to save project before Mean has an ID. Try doing projectDao.save(mean); first or use cascade annotation on a Mean collection on the Project class....
like so...
Mean mean = new Mean();
mean.setVariable(variable);
mean.setBelief(belief);
**projectDao.save(mean);** //first option
// I can't do this because variable and belief are transient and have no ID yet
//MeanPK meanPk = new MeanPK(variableId, beliefId);
//mean.setPk(meanPk);
belief.getMeans().add(mean);
project.getVariables().add(variable);
project.getBeliefs().add(belief);
projectDao.save(project);
//second option
//or in your method declaration section in your Project class remove
getVariables().add(variable) &
getBeliefs().add(belief)
//as well as their associated variable declarations and add
// mappedBy foreign key constraint meanId
@OneToMany(cascade = CascadeType.ALL, mappedBy = "meanId")
//add to variable declarations section
private Collection<Means> meansCollection;
//and add the following method under getter/setter section
@XmlTransient
public Collection<Mean> getMeansCollection() {
return meansCollection;
}
//In the Project class constructor do the following initialization of the MeanCollection
meansCollection = new ArrayList();
//now your call simply becomes
Mean mean = new Mean();
mean.setVariable(variable);
mean.setBelief(belief);
Project project = new Project();
project.getMeansCollection().add(means);
projectDao.save(project);
// Also it looks like you should be using @JoinColumns for the variable_id &
// belief_id fields where each variable is actually a class variable
// representation and not an Integer. In this case you will have mean_id as
// the single primary key and class Variable & Belief each as a @JoinColumn
// foreign key constraint
//4 spaces
Upvotes: 0