簡體   English   中英

Spring和Hibernate Web應用程序:如何解決'org.hibernate.LazyInitializationException?

[英]Spring and Hibernate web application: how to resolve 'org.hibernate.LazyInitializationException?

我正在使用Spring 4,而Hibernate 4是一個場景,其中我有兩個類Person和Book,它們映射到數據庫中的兩個表。 人與書之間的關系為1:M,即一個人可以擁有許多書。

人員表中的關系定義為:

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

並在書中:

@ManyToOne      
private Person person;

使用適當的getter和setter方法。

但是按照我的方法顯示一個人的書:

// Calls books.jsp for a Person.
@RequestMapping(value = "/books", method = RequestMethod.GET)
public String listBooks(@RequestParam("id") String id, 
                        Model model) {    
    logger.info(PersonController.class.getName() + ".listBooks() method called.");                 

    Person person = personService.get(Integer.parseInt(id));        
    // List<Book> books = bookService.listBooksForPerson(person.getId());

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

我發現Hibernate返回:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: library.model.Person.books, could not initialize proxy - no Session

所以我有兩種選擇:

  • 我可以使用EAGER提取-確實可以,但是忽略了整個LAZY提取原理。
  • 解決問題。 考慮到這是我自己和其他人可以學習的應用程序,因此這是我的偏愛。

如果此問題的答案與在服務或DAO代碼中指定/使用事務有關,通常我的數據庫更新將在服務類中標記為@TRANSACTIONAL。 並實現為:

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

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

我的DAO get方法在其SQL中不使用JOIN,例如:

@Override
public List<Book> listBooksForPerson(Integer id) {
    logger.info(BookDAOImpl.class.getName() + ".listBooksForPerson() method called.");

    Session session = sessionFactory.openSession();          
    try {    
        Query query = session.createQuery("FROM Book WHERE PERSON_ID = " + id);  
        return query.list();                                  
    }        
    finally {
        session.close();
    }         
}

我是Spring的新手,我已經在網站上閱讀了一些有關此問題的問題,並了解了問題的本質,當Hibernate會話關閉時,可以重新EAGER或LAZY獲取相關數據。 但是我不知道如何最好地解決上面給出的代碼。

有人可以建議嗎?

好吧,這樣的方法失敗了:

@Override
public List<Book> listBooksForPerson(Integer id) {
    logger.info(PersonDAOImpl.class.getName() + ".listBooksForPerson() method called.");

    Session session = sessionFactory.openSession();          
    try {    
        Query query = session.createQuery("FROM Person, Book WHERE Person.ID = " + id + " AND Person.ID = Book.PERSON_ID");  
        return query.list();                                  
    }        
    finally {
        session.close();
    }         
}

由於Your page request has caused a QuerySyntaxException: Invalid path: 'null.ID' [FROM library.model.Person, library.model.Book WHERE Person.ID = 1 AND Person.ID = Book.PERSON_ID] error:

我也嘗試了以下方法:

@Override
public Person getPersonAndBooks(Integer id) {
    logger.info(PersonDAOImpl.class.getName() + ".listBooksForPerson() method called.");

    Session session = sessionFactory.openSession();          
    try {    

        SQLQuery query = session.createSQLQuery("SELECT * FROM Person JOIN Book ON Person.ID = book.PERSON_ID AND Person.ID = " + id);  

        // HOW TO GET PERSON OBJECT HERE WITH BOOKS?

    }        
    finally {
        session.close();
    }         
}

但是,如何將查詢的輸出映射到我的Person對象類型,其中Person是:

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

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

有人可以建議嗎?

通過調整DAO方法以使Person達到以下條件,可以解決此查詢:

@Override
public Person get(Integer personId) {
    logger.info(PersonDAOImpl.class.getName() + ".get() method called.");

    Session session = sessionFactory.openSession();

    try {                         
        Query query = session.createQuery(" from Person as p left join fetch p.books where p.personId = :personId").setParameter("personId", personId);                      
        Person person = (Person) query.list().get(0); 
        Hibernate.initialize(person.getBooks());            
        return person;

        // return (Person) session.get(Person.class, personId);
    }        
    finally {
        session.close();            
    }         
}

上面的方法listBooksForPerson已被刪除。

但是可以這么說,我仍然使用表之間關系的一端。 Book表現在不使用@ManyToOne標記或Person實例。 這是最終使LAZY獲取(顯示)工作的唯一方法。

您可能已經知道,問題是一旦將實體發送到視圖后,它就會與EntityManager分離,因此延遲加載在那時將不起作用。 如果嘗試在listBooks使用personService.get ,則在加載books之前,將從EntityManager中分離出檢索到的Person 因此,當您嘗試在視圖中訪問books ,會收到該錯誤。

答案是確保在分離實體之前將視圖所需的數據加載到實體中。 不要使用personService.get ; 而是使用HQL查詢和JOIN FETCH語法加載所需的惰性字段。 嘗試類似:

SELECT p FROM Person p LEFT JOIN FETCH p.books WHERE p.id = ...

在這種情況下,將加載books ,您可以在視圖中使用它。

您也可以級聯聯接提取。 例如,如果Book包含您需要處理的Chapter的惰性集合,則可以使用:

SELECT p FROM Person p LEFT JOIN FETCH p.books book LEFT JOIN FETCH book.chapters WHERE p.id = ...

在“ Person ,所有書籍以及這些書籍中的所有章節將對視圖可用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM