Hibernate OneToOne BiDirectional可选关系:在没有可选对象的情况下插入时工作,使用新的可选对象更新时中断

时间:2016-09-15 17:37:42

标签: java hibernate spring-boot one-to-one

我在两个对象ChecklistItem和ButtonAction之间进行了以下OneToOne关系设置(如下面的代码片段所示)。我想这是一种独特的设置。它是双向的,但在ChecklistItem方面是可选的,而ButtonAction是所有者,将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;

    //...
}

ButtonAction类:

@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;

    //...
}

我正在使用SpringBoot,所以我只有一个扩展SpringBoot的CrudRepository的ChecklistItemRepository接口:

public interface ChecklistItemRepository extends CrudRepository<ChecklistItem, Long> {}

在我的ChecklistItem服务中,我已将save方法配置为如下:

@Service
@Transactional
public class ChecklistItemServiceImpl implements ChecklistItemService {

    @Override
    public ChecklistItem saveChecklistItem(ChecklistItem checklistItem) {
        processButtonAction(checklistItem);
        return checklistItemRepository.save(checklistItem);
    }

    private void processButtonAction(ChecklistItem checklistItem,String username) {
        ButtonAction button = checklistItem.getButton();

        if(button != null) {
            button.setChecklistItem(checklistItem);

            if(checklistItem.getId() != null){
                button.setChecklistItemId(checklistItem.getId());
            }
        }
    }

    //...
}

因此,无论何时保存ChecklistItem(通过POST或PUT),它都会在调用保存之前更新ButtonAction(当用户选择包含一个)时引用ChecklistItem及其ID(如果不为null)

这是我的问题... 当用户 PUTs 一个带有新ButtonAction的ChecklistItem时(用户最初在没有ButtonAction的情况下发布了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日志记录时,我能够看到的唯一可疑的事情就是在抛出错误之前,它在按钮操作表上选择cheklistitem id匹配。我猜Hibernate这样做是为了确定它是否需要在buttonaction表上进行插入或更新。但也许它会使用那个空行而不是我的ChecklistItem上的ButtonAction对象?

感谢您的帮助!

2 个答案:

答案 0 :(得分:2)

说实话,我仍然不确定为什么我的原始问题或为什么这个解决方案有效,所以如果有人可以对这些事情有所了解,请评论;我希望能更好地理解。

感谢@CarlitosWay和@Matthew与我一起努力寻找解决方案。当CarlitosWay评论说不需要GeneratedValue和GenericGenerator时,CarlitosWay正在做些什么。我无法将它们移除; 我需要一些方法来告诉Hibernate从获取ButtonAction的ID,但它让我开始寻找替代配置。我发现了一个很有前途的: MapsId 。我查看了一些例子,然后四处讨论,看看会有什么用。如果不出意外,这似乎证实了我有一些独特的设置,因为我的解决方案与通常使用MapsId的例子不同。

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

ButtonAction类:

@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;

    //...
}

基本上,我在checklistItem上的MapsId注释的checklistItemId上交换了GenericGenerator和GeneratedValue注释。在我看到的大多数其他示例中,MapsId注释位于另一个类(在本例中为ChecklistItem),但我在想,因为ButtonAction是关联的所有者以及ID即将到来的地方从,它需要在本机对象上。 ChecklistItem,ChecklistItemRepository和ChecklistItemServiceImpl在我的问题中与原始代码相同。

从理论上讲,我的原始代码是Hibernate做这个JPA等效的方法。但由于他们的行为不同,我必须误解一些事情,所以如果你知道原因,请回复!

答案 1 :(得分:0)

每当首次创建所有者ChecklistItem时,请考虑创建一个新的ButtonAction。然后根据需要更新ButtonAction。

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

同时创建ButtonAction和ChecklistItem应该可以正常工作。