【问题标题】:Foreign key update anomaly: foreign keys set to null in Hibernate one-to-many relationship外键更新异常:Hibernate 一对多关系中外键设置为空
【发布时间】:2014-06-22 16:36:53
【问题描述】:

我发现当一对多关系中的父表更新时,子表上依赖数据的外键被设置为空,从而在子表上留下孤立记录。

我有两个用 Hibernate 标签注释的 Java 类。父表是:

@Entity
@Table(name = "PERSON")
public class Person implements Serializable {

// Attributes.    
@Id
@Column(name="PERSON_ID", unique=true, nullable=false)    
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer personId;

@Column(name="NAME", nullable=false, length=50)      
private String name;

@Column(name="ADDRESS", nullable=false, length=100)
private String address;

@Column(name="TELEPHONE", nullable=false, length=10)
private String telephone;

@Column(name="EMAIL", nullable=false, length=50)
private String email;

@OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="PERSON_ID")   
private List<Book> books;

而子表是:

Entity
@Table(name = "BOOK")
public class Book implements Serializable {

// Attributes.
@Id
@Column(name="BOOK_ID", unique=true, nullable=false)
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer bookId;

@Column(name="AUTHOR", nullable=false, length=50)
private String author;

@Column(name="TITLE", nullable=false, length=50)
private String title;

@Column(name="DESCRIPTION", nullable=false, length=500)
private String description;

@Column(name="ONLOAN", nullable=false, length=5)
private String onLoan;

// @ManyToOne      
// private Person person; 

但是当对 Person 发出更新时,任何与父级相关的 Books 记录都设置为 null。

Book 表是:

CREATE TABLE BOOK (
BOOK_ID INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),    
AUTHOR VARCHAR(50) NOT NULL,  
TITLE VARCHAR(100) NOT NULL, 
DESCRIPTION VARCHAR(500) NOT NULL,
ONLOAN VARCHAR(5) NOT NULL,
PERSON_ID INTEGER,   
CONSTRAINT PRIMARY_KEY_BOOK PRIMARY KEY(BOOK_ID),
CONSTRAINT FOREIGN_KEY_BOOK FOREIGN KEY(PERSON_ID) REFERENCES PERSON(PERSON_ID)) 

而Person controller 中的update 方法是:

@RequestMapping(value = "/profile", method = RequestMethod.POST)
public String postProfile(@ModelAttribute("person") Person person,             
                          BindingResult bindingResult,                              
                          Model model) {                  
    logger.info(PersonController.class.getName() + ".postProfile() method called.");

    personValidator.validate(person, bindingResult);
    if (bindingResult.hasErrors()) {
        return "view/profile";
    }
    else {              
        personService.update(person);                                        
        model.addAttribute("person", person);                                     
        return "view/options";            
    }
}   

而实际DAO级别的方法是:

@Override
public void update(Person person) {
    logger.info(PersonDAOImpl.class.getName() + ".update() method called.");

    Session session = sessionFactory.openSession();
    Transaction transaction = session.getTransaction();        
    try {
        transaction.begin();
        session.update(person);            
        transaction.commit();            
    }
    catch(RuntimeException e) {
        Utils.printStackTrace(e);
        transaction.rollback();
        throw e;
    }
    finally {
        session.close();
    }
}    

所以我假设update 是问题的原因,但为什么呢?

我尝试过mergepersistsaveOrUpdate 方法作为替代方法,但无济于事。

关于我的 Book 表没有 @ManyToOne 的注释这一事实,禁用此标记是我可以让 LAZY 获取工作的唯一方法。

这种情况似乎也与Hibernate one to many:"many" side record's foreign key is updated to null automaticallyHibernate Many to one updating foreign key to null 非常相似,但是如果我将这些问题中对类指定的更改应用到我自己的表中,我的应用程序似乎甚至因为使用@ 的问题而拒绝编译987654337@ 在 Person 表中。

欢迎任何建议。

控制器方法改为:

// Validates and updates changes made by a Person on profile.jap
@RequestMapping(value = "/profile", method = RequestMethod.POST)
public String postProfile(@ModelAttribute("person") Person person,             
                          BindingResult bindingResult,                              
                          Model model) {                  
    logger.info(PersonController.class.getName() + ".postProfile() method called.");

    // Validate Person.        
    personValidator.validate(person, bindingResult);
    if (bindingResult.hasErrors()) {
        return "view/profile";
    }
    else {              

        // Get current Person.
        Person currPerson = personService.get(person.getPersonId()); 

        // Set Books to updated Person.
        person.setBooks(currPerson.getBooks());
        personService.update(person); 

        model.addAttribute("person", person);                                     
        return "view/options";            
    }
}           

而且它有效。

【问题讨论】:

    标签: java sql hibernate one-to-many updates


    【解决方案1】:

    我假设 postProfile() 方法接收一个 Person 实例,该实例仅包含该人的 ID、姓名、地址等,如 Web 表单所发布,但其书籍列表为空或为空。

    你是在告诉 Hibernate 去拯救那个人。因此,您实际上是在告诉 Hibernate,这个由给定 ID 标识的人有一个新名字、一个新地址、一个新电子邮件……以及一个新的书籍列表,而这些列表恰好是空的,并且应该保存这些列表进入数据库。

    因此,Hibernate 会按照您的要求去做:它会保存人的新状态。并且由于新人没有任何书,它之前拥有的所有书都将不归任何人所有。

    您必须从数据库中获取实际持久的 Person 实体,并将实际应该修改的字段从新人员复制到持久实体。

    或者你必须从数据库中预加载持久化的人,并让 Spring 填充这个持久化的人,而不是每次从头开始创建一个新实例。见http://docs.spring.io/spring/docs/4.0.x/spring-framework-reference/htmlsingle/#mvc-ann-modelattrib-methods

    【讨论】:

    • 是的,这是有道理的,我认为类似的事情正在发生。我想我可以在 Person 首次登录时将 Books 持久化到 HTTP 会话存储中,然后仅在数据库中发生状态更改时更新它们。
    • 不,这是一种可怕的做法。请改用我在答案底部给你的两个建议之一。
    • 我稍后会尝试这个,它只是在 Person 实例之间交换 Books。这样做将避免根据多端“拥有”关系的其他问题来更改类定义。
    【解决方案2】:

    您也可以尝试从@OneToMany 中删除@JoinColumn,并改用mappedBy

    见:Hibernate Many to one updating foreign key to null

    【讨论】:

    • 我会试一试的。
    猜你喜欢
    • 1970-01-01
    • 2012-06-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多