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

Mr *_*gan 4 java sql hibernate updates one-to-many

我发现当一对多关系中的父表被更新时,子表上从属数据的外键被设置为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;
Run Code Online (Sandbox Code Playgroud)

子表是:

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; 
Run Code Online (Sandbox Code Playgroud)

但是,当在人员上发布更新时,与父级相关的所有“书籍”记录都将设置为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)) 
Run Code Online (Sandbox Code Playgroud)

updatePerson中的方法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";            
    }
}   
Run Code Online (Sandbox Code Playgroud)

实际的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();
    }
}    
Run Code Online (Sandbox Code Playgroud)

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

我都试过mergepersistsaveOrUpdate方法的替代品,但无济于事。

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

这种情况也似乎与Hibernate一对多非常相似:“许多”边记录的外键自动更新为null,Hibernate Many到一个将外键更新为null,但是如果我自己采用这些问题中对类指定的更改表,似乎是由于mappedByPerson表中的使用问题,我的应用程序甚至拒绝编译。

欢迎任何建议。

控制器方法更改为:

// 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";            
    }
}           
Run Code Online (Sandbox Code Playgroud)

而且有效。

JB *_*zet 5

我假定该postProfile()方法接收一个Person实例,该实例仅包含通过Web表单发布的人员的ID,名称,地址等,但是其书籍列表为null或为空。

您要告诉Hibernate拯救那个人。因此,您实际上是在告诉Hibernate,该人(由给定ID标识)具有一个新名称,一个新地址,一个新电子邮件,...以及一个新书列表,这些书碰巧是空的,应该将其保存进入数据库。

因此,Hibernate会执行您要执行的操作:它可以保存人员的新状态。而且由于新人没有任何书籍,因此以前拥有的所有书籍都将归任何人所有。

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

否则,您必须从数据库中预加载持久性人员,然后让Spring填充该持久性人员,而不是从头开始创建一个新实例。参见http://docs.spring.io/spring/docs/4.0.x/spring-framework-reference/htmlsingle/#mvc-ann-modelattrib-methods