外键更新异常:外键在Hibernate一对多关系中设置为null

时间:2014-06-22 16:36:53

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

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

我有两个用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)) 

update中的controller方法是:

@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非常相似,但是如果我将这些问题中指定的类的更改采用到我自己的表中,我的应用程序似乎拒绝编译,因为它有问题在Person表中使用mappedBy

欢迎任何建议。

控制器方法更改为:

// 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";            
    }
}           

它有效。

2 个答案:

答案 0 :(得分:4)

我假设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

答案 1 :(得分:1)

您也可以尝试从@JoinColumn删除@OneToMany,然后使用mappedBy

请参阅:Hibernate Many to one updating foreign key to null