2017-06-02 55 views
0

我有层叠保存与休眠问题,并没有太多运气跟踪源代码。对象引用一个未保存的瞬态实例 - 麻烦保存新的子/孙引用

简而言之,我有一个三级父母/子女/孙子关系映射,当我保存一个有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(parent)。

在hibernate代码中,我可以看到代码决定父代是否是临时代码并执行相应的保存或合并方法。第一遍执行保存,第二次调用合并。

追踪hibernate代码我看到它要保存新孙子的位置,意识到它需要父项(child3),然后尝试获取子项的id字段。只是因为孩子也是新的,我得到了未保存的瞬态错误。

问题是为什么hibernate无法解析child3的key(通过检查它的父级),因为它在merge过程中解析了grandchild的id,当它明显能够做到这一点时,整个模型是瞬态的保存?

增加可能的问题是中间层(子)实际上是一个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<>(); 
} 

例儿童亚纲:

@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

公共布尔canExtractIdOutOfEntity(){
回报hasIdentifierProperty()|| hasEmbeddedCompositeIdentifier()|| hasIdentifierMapper();
}

这是休眠(1.5.2.RELEASE)已确定被保存的孙子是短暂的,需要它被拉到ID从它的父(child3)。当它搜索id时,它会执行上面的函数,并且所有引用方法都返回null,从而导致调用返回false。在这一点上抛出异常

任何深入了解我可能会做错什么将不胜感激,并且一个解决方案将是非常受欢迎的。

感谢, 杰森

回答

1

为他人谋取利益,我会回答我自己的我是如何设法解决这个问题的问题。

基本的问题是,我依靠@ManyToOne映射关系将父标识提供给子对象。在这样做的时候,我并没有直接将父母身份字段映射到孩子身上。

尽管这可能适用于两级别层次结构,但它表示的任何后续级别都会在第二级别子级上看到对父级ID的依赖关系,但没有看到要映射到的明确列。我觉得这是一个错误,但是我没有足够的信心去理解我的想法。

总之,要解决问题,我进行了以下三个转变:

1)我映射所有父@Id列直接进入每个孩子(每一级随后添加越来越多的键)。只有字段和@Column方面被复制。

2)在每个子的@ManyToOne父映射未标记为ID的

3)I加入@PrePersist映射到每个子拉从@ManyToOne映射父对象的父ID(一个或多个)和将这些值复制到映射字段中。

一直以来,我觉得我需要明确地映射列,但不知道如何从父母填充的ID。只有当我将两个和两个放在一起时,我才意识到,与@PrePersist结合使用的非id @ManyToOne映射可以实现此目的。

我怀疑@MapsId可能与@PrePersist代码块中的逻辑具有相同的影响,因此将不得不进行试验,但现在...它工作。

希望这有助于某人某一天...

相关问题