I am using Spring 4 and Hibernate 4 is a scenario where I have two classes, Person and Book, which map to two tables in the database. The relationship is 1:M from Person to Book, i.e. a Person can have many Books.
The relationship in the Person table is defined as:
@OneToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name="PERSON_ID")
private List<Book> books;
And in Book:
@ManyToOne
private Person person;
With appropriate getter and setter methods.
But in my method to display the Books for a Person:
// 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";
}
I find that Hibernate returns:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: library.model.Person.books, could not initialize proxy - no Session
So I have two alternatives:
- I could use EAGER fetching - this does work but ignores the whole LAZY fetching principle.
- Resolve the issue. This is my preference given that this is an application for myself and others to learn from.
If the answer to this issue is to do with specifying / using transactions in the service or DAO code, typically my database updates are annotated @TRANSACTIONAL in the service classes. and implemented as:
@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();
}
}
My DAO get methods don't use JOINs in their SQL, e.g.:
@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();
}
}
I'm a bit of a Spring newbie and I've read a few questions about this problem on the site and understand the nature of the problem, re EAGER or LAZY fetching of dependent data when a Hibernate session has closed. But I do not know how best to solve it in the code given above.
Can anyone advise?
Well, a method like this fails:
@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();
}
}
Because of 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:
I've also tried the following:
@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();
}
}
But how do I map the output of the query to my Person object type where Person is:
// 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;
Can anyone advise?
This query was resolved by adjusting the DAO method to get a Person to:
@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();
}
}
Where method listBooksForPerson
above has been deleted.
But I am still using the one end of the relationship between the tables as the owner, so to speak. The Book table now has no use of a @ManyToOne
tag or instance of Person. This was the only way to get LAZY
fetching (appearing) to work in the end.