Spring和Hibernate Web应用程序:如何解决&org.hibernate.LazyInitializationException?

时间:2014-06-21 01:00:37

标签: java sql spring hibernate lazy-loading

我正在使用Spring 4,Hibernate 4是一个场景,我有两个类,Person和Book,它们映射到数据库中的两个表。从人到书的关系是1:M,即一个人可以有很多书。

Person表中的关系定义为:

@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获取(出现)最终工作的唯一方法。

1 个答案:

答案 0 :(得分:1)

正如您可能已经意识到的那样,问题在于,一旦将实体发送到您的视图,它就会与EntityManager分离,因此延迟加载在此时无法工作。如果您尝试在personService.get中使用listBooks,则会在加载Person之前将检索到的books与EntityManager分离。因此,当您尝试在视图中访问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中,所有图书以及这些图书中的所有章节都可供视图使用。