首页 > 解决方案 > 在 save() 操作期间,休眠调用子实体的更新,而插入父实体

问题描述

TL;博士

对于第三张表OneToMany之间的映射Note和使用,无法保存实体。TagNote_tagsave()Note

背景:

所以我在这个NoteAppGithub repo location)上工作,它保存了一个带有标题、描述、状态和标签(标签是一个字符串值)的注释。作为功​​能更新,我想为一个注释添加多个标签。为此,我使用 faily straigt forward @JoinTableannotation 创建了一个 Tag 表和第三个 tag 和 note 关联表。此功能导致我在保存 Note 实体时遇到上述问题。

我在屏幕后面使用的内容:

Java 1.8, Hiberanate, SpringBoot

有关技术堆栈的更多详细信息,请点击此处

我已经有的工作:

Save() Note没有Tags。

我的 Note.java:

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table(name = "T_NOTE")
public class Note implements Serializable {

    private static final long serialVersionUID = -9196483832589749249L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="ID")
    private Integer id;

    @Column(name="TITLE")
    private String title;

    @Column(name="DESCRIPTION")
    private String description;

    @Column(name = "LAST_UPDATED_DATE")
    private Date lastUpdatedDate;

    @Column(name="STATUS")
    private String status;

    @OneToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "T_NOTE_TAG", joinColumns = { @JoinColumn(name="NOTE_ID", referencedColumnName = "ID") }, inverseJoinColumns = {
            @JoinColumn(name = "TAG_ID", referencedColumnName = "TAG_ID") })
    private List<Tag> tags = new ArrayList<Tag>();
    /** getters setters omitted for brevity**/
}

我的标签.java:

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "T_TAG")
public class Tag implements Serializable {

    private static final long serialVersionUID = -2685158076345675196L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="TAG_ID")
    private Integer tagId;

    @Column(name="TAG_NAME")
    private String tagName;
}

我的 NoteDto.java:

public class NoteDto {

    private int id;
    private String title;
    private String description;
    private List<TagDto> tags = new ArrayList<TagDto>();
    private Date lastUpdatedDate;
    private String status;
}

我的 TagDto.java:

public class TagDto {

    private int tagId;
    private String tagName;
}

我的表创建查询:

CREATE database if NOT EXISTS noteApp;

CREATE TABLE `T_NOTE` (
    `id` int(11) unsigned NOT NULL auto_increment,
    `description` varchar(20) NOT NULL DEFAULT '',
    `header` varchar(20) NOT NULL,
    `status` varchar(20) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `T_TAG` (
    `tag_id` int unsigned not null auto_increment,
    `tag_name` varchar(30) not null,
    PRIMARY KEY(TAG_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `T_NOTE_TAG` (
    `note_tag_id` int unsigned not null auto_increment,
    `note_id` int unsigned not null,
    `tag_id` int unsigned not null,
    CONSTRAINT note_tag_note foreign key (`note_id`) references T_NOTE(`id`),
    CONSTRAINT note_tag_tag foreign key (`tag_id`) references T_TAG(`tag_id`),
    CONSTRAINT note_tag_unique UNIQUE (`tag_id`, `note_id`),
    PRIMARY KEY (`note_tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

use noteApp;
ALTER TABLE T_NOTE
Change header title varchar(1000) NOT NULL;

ALTER TABLE T_NOTE
ADD COLUMN last_updated_date timestamp AFTER status;

错误日志:

Hibernate: select next_val as id_val from hibernate_sequence for update
Hibernate: update hibernate_sequence set next_val= ? where next_val=?
test
Hibernate: insert into T_NOTE (DESCRIPTION, LAST_UPDATED_DATE, STATUS, TITLE, ID) values (?, ?, ?, ?, ?)
Hibernate: update T_TAG set TAG_NAME=? where TAG_ID=?
2020-04-11 18:02:40.647 ERROR 3614 --- [nio-8080-exec-1] o.h.i.ExceptionMapperStandardImpl        : HHH000346: Error during managed flush [Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]]
org.springframework.orm.jpa.JpaOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]; nested exception is javax.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.sandeep.SpringBootDemo.model.Tag#0]
    at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:396)
    at org.springframework.orm.jpa.DefaultJpaDialect.translateExceptionIfPossible(DefaultJpaDialect.java:127)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:545)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.

异常提示我 Tag 被错误地更新,这引发了一个问题,为什么它首先调用 update forTag而 insert 被正确调用Note?在发布此问题之前,我尝试寻找解决方案,但找不到任何解决方案。提前致谢!

标签: javahibernatespring-bootjoinjpa-2.0

解决方案


Hibernate 在父插入时为子级调用更新,因为您使用了单向@OneToMany而不是@ManyToOne或双向。无论您做什么,Hibernate 都将其建模为 OneToMany。因此,Hibernate 插入您的父项和子项,然后对子项调用 update 以将父外键添加到子表。现在关于为什么你得到异常,正如我所说,Hibernate 在你的关系的许多方面调用更新,但是由于默认行为是可空的设置为 true,它会导致你的错误。请记住,您正在进行单向映射,因此只有关系的一侧知道它。请避免使用 OneToMany 的单向关系。您可以在 Vlad MihalCea 网站上找到许多关于此问题的精彩文章。他是冬眠大师。将 nullable 设置为 false ,它应该可以解决您的问题。


推荐阅读