我在使用hibernate时遇到级联保存问题,并且没有太多运气跟踪源代码。
简而言之,我有一个三级父/子/孙关系映射,当我保存对1父母的引用时,有2个孩子,每个孩子都有少数孙子,那么第一次坚持成功,每个人都得到适当的ID:
Parent (id:p1)
child1 (id:parent,c1)
grandchild (id:child1,g1)
grandchild (id:child1,g2)
child2 (id:parent,c2)
grandchild (id:child2,g1)
grandchild (id:child2,g2)
如果我然后加载父(急切地)并添加一个新的孩子,其中包含几个自己的新孩子,当我尝试通过父对象持久保存更改时,我得到未保存的瞬态错误:
Parent (id:p1)
child1 (id:parent,c1)
grandchild1 (id:child1,g1)
grandchild2 (id:child1,g2)
child2 (id:parent,c2)
grandchild3 (id:child2,g1)
grandchild4 (id:child2,g2)
child3 (id:<new>)
grandchild5 (id:child3,<new>)
grandchild6 (id:child3,<new>)
这是我用来保存对象的基本JPA样式语法:
rulesRepository.save(父)。
在hibernate代码中,我可以看到代码决定父项是否是瞬态的并执行适当的保存或合并方法。第一遍执行保存,第二遍调用merge。
跟踪hibernate代码我看到它要保存新孙子的位置,意识到它需要父母的密钥(child3),然后尝试获取孩子的id字段。只因为孩子也是新的,我得到未保存的瞬态错误。
问题是为什么hibernate无法解析child3的密钥(通过检查它的父母),因为它在合并期间解析了孙子的密钥,当它解决了。当整个模型是瞬态的时,它显然能够做到这一点吗?
增加可能的问题是中间层(子)实际上是JOINED子类型。孙子对象与所有子类型相关联,因此映射到鉴别器类。
这是我绘制的内容:
父:
@Entity
@Transactional
@EntityListeners(AuditingEntityListener.class)
@Table(name="DS_EXTENDED_DATA_SOURCE")
public final class Rule {
@Id
@GeneratedValue(generator="IdentityProvider")
@GenericGenerator(name="IdentityProvider", strategy="com.teradata.tac.domain.common.IdentityProvider")
@Column(name="Extended_Data_Source_Id", nullable=false, length=MAX_ID_LENGTH, updatable=false, insertable=true)
private String extendedDataSourceId;
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true, fetch=FetchType.EAGER, mappedBy="rule")
@Fetch(FetchMode.SELECT)
@OrderBy("Display_X_Position_Num,Display_Y_Position_Num,Extended_Data_Source_Id")
private List<Node<?>> nodes = new ArrayList<>();
}
儿童 - 辨别者:
@Entity
@IdClass(NodeId.class)
@Table(name="DS_XDS_NODE")
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn(name="Xds_Node_Type_Cd")
public class Node<T extends Node<T>> extends BaseDomain<Node<T>> {
@Id
@GeneratedValue(generator="IdentityProvider")
@GenericGenerator(name="IdentityProvider", strategy="com.teradata.tac.domain.common.IdentityProvider")
@Column(name="Xds_Node_Id", nullable=false, length=MAX_ID_LENGTH, updatable=false, insertable=false)
protected String xdsNodeId;
@Column(name="Xds_Node_Type_Cd", nullable=false, insertable=false, updatable=false)
protected short xdsNodeTypeCd;
@Id
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="Extended_Data_Source_Id", insertable=false, updatable=false, nullable=false)
@ApiModelProperty(hidden=true)
@JsonBackReference(value="rule")
protected Rule rule;
// Selected Columns
@OneToMany(cascade=CascadeType.ALL, orphanRemoval=true, fetch=FetchType.EAGER, mappedBy="node")
@Fetch(FetchMode.SELECT)
@OrderBy("Display_Ord")
protected List<SelectedColumn> selectedColumns = new ArrayList<>();
}
示例Child-Subclass:
@Entity
@Table(name="DS_XDS_JOIN_NODE")
@DiscriminatorValue("60")
public class JoinNode extends Node<JoinNode> {
}
孙:
@Entity
@IdClass(SelectedColumnId.class)
@Table(name = "DS_XDS_SELECTED_COLUMN")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class SelectedColumn {
@Id
@GeneratedValue(generator = "IdentityProvider")
@GenericGenerator(name = "IdentityProvider", strategy = "com.teradata.tac.domain.common.IdentityProvider")
@Column(name = "Xds_Selected_Column_Id", nullable = false, length = MAX_ID_LENGTH, updatable = false, insertable = false)
protected String xdsSelectedColumnId;
@Id
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumns({
@JoinColumn(name = "Extended_Data_Source_Id", insertable = false, updatable = false, nullable = false),
@JoinColumn(name = "Xds_Node_Id", insertable = false, updatable = false, nullable = false) })
protected Node<?> node;
}
所有使用的IdClasses都有类似的布局:
public class NodeId {
private Rule rule; // Parent object reference
private String xdsNodeId; // local instance id (not guaranteed to be unique)
public NodeId() {}
public NodeId(Rule rule, String xdsNodeId) {
this.rule = rule;
this.xdsNodeId = xdsNodeId;
}
public String getId() {
return this.xdsNodeId;
}
public Rule getRule() {
return this.rule;
}
}
我省略了重载的equals和hashcode方法,但基本上它们都与关键字段匹配。
我设法追踪根本原因的最好方法是这个方法:AbstractEntityPersister:4480
public boolean canExtractIdOutOfEntity(){
return hasIdentifierProperty()|| hasEmbeddedCompositeIdentifier()|| hasIdentifierMapper();
}
这就是hibernate(1.5.2.RELEASE)确定被保存的孙子是短暂的并且需要从它的父母(child3)中提取它的id。当它搜索id时,它执行上面的函数,并且所有引用方法都返回null,导致调用返回false。此时抛出异常
对我可能做错的任何见解都将不胜感激,并且非常欢迎解决方案。
谢谢, 杰森
答案 0 :(得分:1)
为了别人的利益,我会回答我自己的问题,我是如何设法解决问题的。
基本问题是我依靠@ManyToOne映射关系将父ID提供给子对象。这样做我没有直接将父ID字段映射到子节点。
虽然这可能适用于两级层次结构,但它看起来意味着下面的任何后续级别都会看到第二级子级中父级ID的依赖关系,但是看不到要映射到的显式列。我觉得这是一个错误,但我对自己的理解并不足以做出这样的假设。
无论如何,为解决这个问题,我进行了以下三项修改:
1)我将所有父级@Id列直接映射到每个子级(每个级别随后添加越来越多的键)。只复制了字段和@Column方面。
2)每个孩子的@ManyToOne父映射都没有标记为ID
3)我为每个子节点添加了@PrePersist映射,以从@ManyToOne映射的父对象中提取父ID,并将值复制到映射的字段中。
一直以来,我觉得我需要明确地映射列,但不知道如何从填充的父项中获取ID。只有当我把两个和两个放在一起时,我才意识到一个非id的@ManyToOne映射与@PrePersist相结合可以实现这一点。
我怀疑@MapsId可能与@PrePersist代码块中的逻辑具有相同的影响,因此必须进行实验,但是现在......它可以工作。
希望有一天能帮到某人......