Java Hibernate OneToOne双向可选关系:在插入时不使用可选对象,在使用新的可选对象更新时中断

Java Hibernate OneToOne双向可选关系:在插入时不使用可选对象,在使用新的可选对象更新时中断,java,hibernate,spring-boot,one-to-one,Java,Hibernate,Spring Boot,One To One,我在两个对象ChecklistItem和ButtonAction(如下面的代码片段所示)之间有以下OneToOne关系设置。我想这是一种独特的设置。它是双向的,但从ChecklistItem方面来说是可选的,ButtonAction是所有者,它将ChecklistItem的外键存储为主键 检查列表项目类: @Entity @Table(name = "CHECKLIST_ITEM", schema = "CHKL_APP") public class ChecklistItem implemen

我在两个对象ChecklistItem和ButtonAction(如下面的代码片段所示)之间有以下OneToOne关系设置。我想这是一种独特的设置。它是双向的,但从ChecklistItem方面来说是可选的,ButtonAction是所有者,它将ChecklistItem的外键存储为主键

检查列表项目类:

@Entity
@Table(name = "CHECKLIST_ITEM", schema = "CHKL_APP")
public class ChecklistItem implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CHECKLIST_ITEM_ID_SEQ")
    @SequenceGenerator(name = "CHECKLIST_ITEM_ID_SEQ", sequenceName = "CHKL_APP.CHECKLIST_ITEM_ID_SEQ")
    private Long id;

    @OneToOne(optional = true, mappedBy = "checklistItem", cascade = CascadeType.ALL)
    private ButtonAction button;

    //...
}
@Entity
@Table(name = "ACTION_BUTTON", schema = "CHKL_APP")
public class ButtonAction implements Serializable {
    @Id
    @Column(name = "checklist_item_id", unique = true, nullable = false, insertable = true, updatable = false)
    @GenericGenerator(name = "generate-from-checklist-item", strategy = "foreign", parameters = @Parameter(name = "property", value = "checklistItem"))
    @GeneratedValue(generator = "generate-from-checklist-item")
    private Long checklistItemId;

    @OneToOne
    @PrimaryKeyJoinColumn
    @JsonIgnore
    private ChecklistItem checklistItem;

    //...
}
@Entity
@Table(name = "ACTION_BUTTON", schema = "CHKL_APP")
public class ButtonAction implements Serializable {
    @Id
    @Column(name = "checklist_item_id", unique = true, nullable = false, insertable = true, updatable = false)
    private Long checklistItemId;

    @OneToOne
    @MapsId
    @PrimaryKeyJoinColumn
    @JsonIgnore
    private ChecklistItem checklistItem;

    //...
}
按钮操作类:

@Entity
@Table(name = "CHECKLIST_ITEM", schema = "CHKL_APP")
public class ChecklistItem implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CHECKLIST_ITEM_ID_SEQ")
    @SequenceGenerator(name = "CHECKLIST_ITEM_ID_SEQ", sequenceName = "CHKL_APP.CHECKLIST_ITEM_ID_SEQ")
    private Long id;

    @OneToOne(optional = true, mappedBy = "checklistItem", cascade = CascadeType.ALL)
    private ButtonAction button;

    //...
}
@Entity
@Table(name = "ACTION_BUTTON", schema = "CHKL_APP")
public class ButtonAction implements Serializable {
    @Id
    @Column(name = "checklist_item_id", unique = true, nullable = false, insertable = true, updatable = false)
    @GenericGenerator(name = "generate-from-checklist-item", strategy = "foreign", parameters = @Parameter(name = "property", value = "checklistItem"))
    @GeneratedValue(generator = "generate-from-checklist-item")
    private Long checklistItemId;

    @OneToOne
    @PrimaryKeyJoinColumn
    @JsonIgnore
    private ChecklistItem checklistItem;

    //...
}
@Entity
@Table(name = "ACTION_BUTTON", schema = "CHKL_APP")
public class ButtonAction implements Serializable {
    @Id
    @Column(name = "checklist_item_id", unique = true, nullable = false, insertable = true, updatable = false)
    private Long checklistItemId;

    @OneToOne
    @MapsId
    @PrimaryKeyJoinColumn
    @JsonIgnore
    private ChecklistItem checklistItem;

    //...
}
我正在使用SpringBoot,所以我刚刚得到了一个ChecklistItemRepository接口,它扩展了SpringBoot的Crudepository:

public interface ChecklistItemRepository extends CrudRepository<ChecklistItem, Long> {}
因此,每当保存ChecklistItem(通过POST或PUT)时,它都会在调用保存之前使用对ChecklistItem的引用及其ID(如果不是null)更新该ButtonAction(当用户选择包含一个按钮时)

这是我的问题当用户放置带有新按钮操作的ChecklistItem(用户最初发布的ChecklistItem没有按钮操作)时,我得到以下错误

org.springframework.orm.jpa.JpaSystemException: attempted to assign id from null one-to-one property [com.me.chklapp.checklistitem.action.ButtonAction.checklistItem];
nested exception is org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [com.me.chklapp.checklistitem.action.ButtonAction.checklistItem]
我在网上找到的每一个“答案”都在说需要建立关系,但我已经在我的服务中这样做了。我已经通过调试和检查每个对象对另一个对象的引用是否为非空来验证了它的正确性。而且,我找不到任何其他人有和我一样的问题,在某些情况下,它会保存,而在其他情况下,它会中断;在这些情况下,要么全是,要么什么都不是

当我打开更详细的hibernate日志记录时,我能看到的唯一可疑的事情是,就在抛出错误之前,它在buttonaction表上的cheklistitem id匹配的地方进行了选择。我猜Hibernate这样做是为了确定是否需要对buttonaction表进行插入或更新。但也许它会使用那一空行而不是我的ChecklistItem上的ButtonAction对象


谢谢你的帮助

考虑在首次创建所有者ChecklistItem时创建一个新的按钮操作。然后根据需要更新按钮操作

我不太确定,但我认为在以后尝试派生ID时会出现一些Hibernate问题


同时创建ButtonAction和ChecklistItem应该会起作用。

老实说,我仍然不知道为什么会出现我原来的问题,或者为什么这个解决方案会起作用,所以如果有人能够解释这些问题,请发表评论;我希望能更好地理解

感谢@CarlitosWay和@Matthew与我一起努力寻找解决方案。当卡里托斯韦评论说不需要GeneratedValue和GenericGenerator时,他明白了一些事情。我不能就这样把它们拿走;我需要一些方法来告诉Hibernate从哪里获取ButtonAction的ID,但这让我开始寻找替代配置。我发现了一个很有希望的方法:MapsId。我看了一些例子,然后用扇形角度看了看什么是有效的。如果没有别的,这似乎证实了我已经有了某种独特的设置,因为我的解决方案与MapsId的常用示例不同

我将在下面发布我的结果代码,但我仍然不完全确定Hibernate如何处理所有这些,因此这里可能有一些多余的注释。如果可能的话,请告诉我如何清理

按钮操作类:

@Entity
@Table(name = "CHECKLIST_ITEM", schema = "CHKL_APP")
public class ChecklistItem implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CHECKLIST_ITEM_ID_SEQ")
    @SequenceGenerator(name = "CHECKLIST_ITEM_ID_SEQ", sequenceName = "CHKL_APP.CHECKLIST_ITEM_ID_SEQ")
    private Long id;

    @OneToOne(optional = true, mappedBy = "checklistItem", cascade = CascadeType.ALL)
    private ButtonAction button;

    //...
}
@Entity
@Table(name = "ACTION_BUTTON", schema = "CHKL_APP")
public class ButtonAction implements Serializable {
    @Id
    @Column(name = "checklist_item_id", unique = true, nullable = false, insertable = true, updatable = false)
    @GenericGenerator(name = "generate-from-checklist-item", strategy = "foreign", parameters = @Parameter(name = "property", value = "checklistItem"))
    @GeneratedValue(generator = "generate-from-checklist-item")
    private Long checklistItemId;

    @OneToOne
    @PrimaryKeyJoinColumn
    @JsonIgnore
    private ChecklistItem checklistItem;

    //...
}
@Entity
@Table(name = "ACTION_BUTTON", schema = "CHKL_APP")
public class ButtonAction implements Serializable {
    @Id
    @Column(name = "checklist_item_id", unique = true, nullable = false, insertable = true, updatable = false)
    private Long checklistItemId;

    @OneToOne
    @MapsId
    @PrimaryKeyJoinColumn
    @JsonIgnore
    private ChecklistItem checklistItem;

    //...
}
基本上,我将checklistItemId上的GenericGenerator和GeneratedValue注释交换为checklistItem上的MapsId注释。在我看到的大多数其他示例中,MapsId注释位于另一个类上(在本例中为ChecklistItem),但我认为由于ButtonAction是关联的所有者,并且ID来自何处,因此它需要位于本机对象上。ChecklistItem、ChecklistItemRepository和ChecklistItemServiceImpl与我问题中的原始代码相比都没有变化


理论上,我的原始代码是用Hibernate方法来实现这个JPA等价物。但是由于他们的行为不同,我一定是误解了什么,所以如果你知道原因,请回答

为什么需要这些注释:
@GenericGenerator
@GeneratedValue
位于
checklistItemId
属性处。。。当您的按钮ID始终为CheckedItem ID时。。。我相信如果删除这些注释,一切都会正常…@CarlitosWay我需要这些注释来告诉Hibernate该ID是从另一个源生成的,在本例中,是对ChecklistItem的OneToOne引用。所以我不能完全删除它,但我已经做了一些研究,似乎我可以通过使用MapsId注释以更简单的方式完成同样的事情。我们将继续努力,希望这能解决问题。我们拭目以待。对于Hibernate的混乱,我也有同样的想法,但如果它是可选的,则强制执行它不是一个选项。如果用户选择不创建无意义的ButtonAction,则ButtonAction上不存在会抱怨的空约束。如果我要删除那些非空约束,那么我总是会在ChecklistItems上返回一个伪ButtonAction,它不需要ButtonAction,这会导致一些错误的UI显示,如果我选择解决这个问题,UI中会出现复杂的逻辑关于以后获取ID,请发布相关信息。我想我以前遇到过这个问题。我将发布一个答案。我仍然不能完全确定为什么我的原始设置不起作用,但运行@CarlitosWay的建议,即不需要GenericGenerator和GeneratedValue,这让我找到了一个替代的注释设置,似乎已经解决了这个问题。在我的回答中寻找细节我从未使用过那个注释,但根据文档