I am using tje JPA criteria API to create an "IN" query. I want to select Courses that are in certain Categories. The Categories are supposed to end up in the IN part of the query.
This is the Course entity. It has a reference to a Category entity, because each Course is in one Category.
@Entity public class Course implements DomainObject { private Long id; private Integer version; private String name; private Category category; @Override @Id @GeneratedValue public Long getId() { return id; } public void setId(Long id) { this.id = id; } @ManyToOne public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getVersion() { return version; } public void setVersion(Integer version) { this.version = version; } }
In my service I want to select Courses that are belong to certain (a list) of Categories.
public List<Course> findCourses(CourseFilter filter) { CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery<Course> criteriaQuery = criteriaBuilder.createQuery(Course.class); Root<Course> root = criteriaQuery.from(Course.class); List<Predicate> predicateList = new ArrayList<Predicate>(); if (!filter.getCategories().isEmpty()) { Predicate predicate = root.get(Course_.category).in(filter.getCategories()); predicateList.add(predicate); } Predicate[] predicates = new Predicate[predicateList.size()]; predicateList.toArray(predicates); criteriaQuery.where(predicates); TypedQuery<Course> typedQuery = entityManager.createQuery(criteriaQuery); return typedQuery.getResultList(); } When the query executes on the last line of the method it throws an error:
HTTP Status 500 - Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientObjectException: object references an unsaved transient instance save the transient instance before flushing:nl.codebasesoftware.produx.domain.Category; nested exception is java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance save the transient instance before flushing: nl.codebasesoftware.produx.domain.Category I am not even sure I am using the right way to create an IN query. I think the criteria API is terribly complicated. But before I worry about the IN query I would like to know why Hibernate is throwing this TransientObjectException. The filter.getCategories() call results in actual categories, filled with a primary key id, etc.
Added:
Here is how I get the Category instance that I use to later fetch Courses with. This is also a DAO method that is called via a @Service from a @Controller method.
public Category findByName(String name) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<Category> query = builder.createQuery(Category.class); Root<Category> root = query.from(Category.class); Predicate predicate = builder.equal(root.get(Category_.urlTitle), name); query.where(predicate); TypedQuery<Category> typedQuery = entityManager.createQuery(query); return getSingleResult(typedQuery); } So, Hibernate is telling me I am using Category objects that somehow reference an unsaved entity, but I don't see how. The Category that is returned from this method is just a Category that if fetched by Hibernate. I am not doing anything with it before I send it to the method that fetches Courses.
Here is my the controller method:
@RequestMapping(method = RequestMethod.GET, value = "/{categoryUrlName}") public String setup(@PathVariable("categoryUrlName") String categoryUrlName, Model model){ // Fetch the category Category category = categoryService.findByName(categoryUrlName); // if no category found, throw a 404 if(category == null){ throw new ResourceNotFoundException(); } // Fetch courses in this category List<Course> courses = courseService.findCourses(category); model.addAttribute("courses", courses); model.addAttribute("category", category); model.addAttribute("mainContent", "content/category"); return "main"; }