I have two tables with many to many relations.
products(id, description, price,image)----> products_category(idProducts, category _id)----> category(id, category_name).
Here is my enteties:
1. Products
#Entity
#Table(name = "products")
public class Products implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idProducts")
private long id;
#Column(name = "description")
private String description;
#Column(name = "price")
private String price;
#Column(name = "image")
private byte [] image;
public Products() {
}
public Products(String description, String price, byte[] image) {}
#ManyToMany
#JoinTable(
name = "category_products",
joinColumns ={#JoinColumn (name = "Products_idProducts", referencedColumnName = "idProducts")},
inverseJoinColumns = {#JoinColumn(name = "category_id", referencedColumnName = "id")}
)
List<Category> categories = new ArrayList<>();
#ManyToMany
#JoinTable(
name = "users_product",
joinColumns ={#JoinColumn (name = "Products_idProducts", referencedColumnName = "idProducts")},
inverseJoinColumns = {#JoinColumn(name = "users_id", referencedColumnName = "id")}
)
List<Users> usersList = new ArrayList<>();
2.Category
#Entity
#Table(name = "category")
public class Category {
#Id
#Column(name = "id")
private long id;
public Category() {
}
public Category(String category_name) {
this.category_name = category_name;
}
#Column (name = "category_name")
private String category_name;
#ManyToMany(mappedBy = "categories")
private List<Products> products = new ArrayList<>();
I'm try to write query for controller, which return all the products by previosly selected category object with id? i tried many query, but all throws exceptions.
public List<Products> list (Category category) {
//category - object with needed id
Query query;
query = entityManager.createQuery("SELECT c FROM Category c left join c.categories WHERE c.category = :category", Products.class);
query.setParameter("category", category);
return (List<Products>) query.getResultList();
}
java.lang.IllegalArgumentException: org.hibernate.QueryException: could not resolve property: categories of: classes.Category [SELECT c FROM classes.Category c join c.categories WHERE c.category = :category]
org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1750)
org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1683)
If you need to retrieve products, you need to do a query that select Product entity, not Category.
So:
return all the products by previosly selected category object with
id
You need to do:
Query query = entityManager.createQuery("SELECT p FROM Product p
JOIN p.categories c
WHERE c.id = :idCategory");
query.setParameter("idCategory", category.getId());
You use LEFT JOIN but this is not necessary in your case, because the unique condition of your query is find a category with a specific ID. This condition will ignore the LEFT part of the JOIN, forcing always a JOIN.
I think there are some errors in your code:
You SELECT c which mean Category but you cast the result list to List<Product>, should be SELECT c.products
Your WHERE c.category = :category clause is not correct because you don't have any category attribute in your Category class, should be WHERE c.id = :id and query.setParameter("id", category.getId());
Hope that help.
Related
I am using Spring Boot with Spring-data-jpa
I have two entities Category and Course. They are associated by #ManyToMany.
public class Course {
#Id
#GeneratedValue(generator = "inc")
#GenericGenerator(name = "inc", strategy = "increment")
private int courseId;
#NotBlank(message = "Add the course's title!")
private String title;
#ManyToMany(mappedBy = "courses", fetch = FetchType.EAGER)
private Set<Category> categories;
}
public class Category {
#Id
#GeneratedValue(generator = "inc")
#GenericGenerator(name = "inc", strategy = "increment")
private int categoryId;
#NotBlank(message = "category's name must be not empty!")
private String name;
#ManyToMany(cascade = CascadeType.PERSIST)
#JoinTable(
name="course_category",
joinColumns = #JoinColumn(name = "category_id"),
inverseJoinColumns = #JoinColumn(name = "course_id")
)
private Set<Course> courses;
When I download one category I want to download all its courses as well ( without fetchType.EAGER because I don't want to get courses while downloading all categories ). This is my overriden query in CategoryRepository (JpaRepository):
#Override
#Query("SELECT cat FROM Category cat JOIN FETCH cat.courses")
Optional<Category> findById(Integer id);
Since I want to send my client data from two tables, I have created a DTO:
public class CategoryAndCourses {
private Category category;
private List<Course> courses;
public CategoryAndCourses(Category category, List<Course> courses) {
this.category = category;
this.courses = courses;
}
And there is a place where I actually send both the courses and the category by DTO:
public CategoryAndCourses getCourses(int id) {
var category = repository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("No category with given id"));
var result = new CategoryAndCourses(category, new ArrayList<>(category.getCourses()));
return result;
}
With such a code I get this error:
javax.persistence.NonUniqueResultException: query did not return a unique result: 8
How to solve it ?
The error is pretty clear: you have a query returnin more than 1 result and your code is defined as expecting a single result.
Aren't you missing a WHERE on the id on the following query?
#Query("SELECT cat FROM Category cat JOIN FETCH cat.courses")
Optional<Category> findById(Integer id);
I'm struggling to understand how can I query all products that have specified category association.
I might need something like this
select p.* from product p
inner join product_category pc on p.product_id = pc.product_id
inner join category c on pc.category_id = c.category_id
where c.name = :categoryName;
but in HQL.
Assuming that you use the following mapping:
#Entity
#Table(name = "product")
class Product
{
#Id
#Column(name = "product_id")
private Long id;
#ManyToMany
#JoinTable(name = "product_category",
joinColumns = #JoinColumn(name = "category_id"),
inverseJoinColumns = #JoinColumn(name = "product_id")
)
private List<Category> categories;
// getters/ setters
}
#Entity
#Table(name = "category")
class Category
{
#Id
#Column(name = "category_id")
private Long id;
#Column(name = "name")
private String name;
// getters/ setters
}
you can use the following HQL:
select p from Product
join fetch p.categories c
where c.name = :categoryName
Have 3 entities
Group
#Entity
#Table(name = "`group`")
public class Group implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_group")
private Long id;
#OneToMany(mappedBy = "group",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#OrderColumn(name = "id_student")
private List<Student> students = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "id_faculty")
private Faculty faculty;
.....getters/setters
}
Student
#Entity
#Table(name = "student")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id", scope = Long.class)
public class Student implements Serializable {
#Id
#Column(name = "id_student")
private Long id;
......
#OneToMany(mappedBy = "student",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private List<Rating> ratings = new ArrayList<>();
#ManyToOne
#JoinColumn(name = "id_okr")
private OKR okr;
#ManyToOne
#JoinColumn(name = "id_group")
private Group group;
.....getters/setters
}
Rating
#Entity
#Table(name = "rating")
public class Rating implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_rating")
private Long id;
#Temporal(TemporalType.DATE)
#Column
private Date date;
#ManyToOne
#JoinColumn(name = "id_student")
private Student student;
#ManyToOne
#JoinColumn(name = "id_paragraph")
private Paragraph paragraph;
.....getters/setters
}
JPA Query
#Query(value = "SELECT g FROM Group g INNER JOIN FETCH g.students s LEFT JOIN FETCH s.ratings r WHERE g.id = :id AND s.group.id = g.id AND (r.student.id = s.id AND r.date BETWEEN :startMonth and :endMonth OR r IS NULL) GROUP BY s.id")
Group findGroupByStudentGroupId(#Param("id") Long id ,#Param("startMonth") Date startMonth, #Param("endMonth") Date endMonth );
I have the student with id 8000 and after extracting query the result list contains 8001 elements which contain 8 students that I have and 7993 null values. If I remove annotation #OrderColumn I have MultiBagException(cannot simultaneously fetch). If add #OrderColumn to rating #OneToMany association in the entity Student I have null values in Rating collection and in Student collection.
For me, the logic of #OrderColumn which return null values as biggest id in collection seems very strange. Is any way how to solve it?
Hibernate version 5.1.0 Final
Spring Data JPA 1.8.2
As you are trying to fetch students with particular group and ratings, you can have your query fetch from student entity and have join on Ratings entity. You don't have to explicitly add join for group entity as it is already in many to one mapping with student.
You can Change your query as follows:
#Query(value = "SELECT s FROM Student s LEFT JOIN s.ratings r WHERE s.group.id = :id AND (r.date BETWEEN :startMonth and :endMonth OR r IS NULL) GROUP BY s.id")
List<Student> findGroupByStudentGroupId(#Param("id") Long id ,#Param("startMonth") Date startMonth, #Param("endMonth") Date endMonth );
My entity of product looks like below:
#Entity
#Table(name = "order")
public class OrderEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "order_id")
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "order_products",
joinColumns = #JoinColumn(name = "order_id", referencedColumnName = "order_id"),
inverseJoinColumns = #JoinColumn(name = "product_id", referencedColumnName = "id")
)
private Set<ProductEntity> products = new HashSet<>();
}
ProductEntity:
#Entity
#Table(name = "product")
public class ProductEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(unique = true)
private String name;
#ManyToMany(mappedBy = "products")
private Set<OrderEntity> orders = new HashSet<>();
}
I want to get all orders where product name is equal to wanted value. And I write sql query to get result from database, but I cannot write hibernate query for Spring Data JPA.
My query for postgreSQL looks like this:
SELECT o.order_id, op.product_id, p.name
FROM public.order o
INNER JOIN public.order_products op
ON p.order_id = op.product_id
INNER JOIN public.product p
ON op.product_id = p.id
WHERE p.name = 'Foo';
And this query return me an id of order, product_id and name of product. And this works. But I didn't know how to write this question as spring query using #Query.
I need a metohod in my repository:
#Repository
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
#Query("") <- place for my query in Hibernate sql
List<OrderEntity> findAllByProductName(#Param("name") String name);
}
try this: (it returns full OrderEntity objects )
#Query("select o from OrderEntity o join o.products prod where prod.name = :name")
List<OrderEntity> findAllByProductName(#Param("name") String name);
if you need fetch eager all data for products use : ....OrderEntity o join o.products... in query instead of OrderEntity o join o.products
This is a projection consisting of columns from many entties, so you would have to go for the Result Class strategy.
Basically, you create a POJO class with expected result fields an an equivalent constructor:
public class ResultClass{
private Integer orderId;
private Integer productId;
private String name;
public ResultClass(Integer orderId, Integer productId, String name){
// set the fields
}
}
Then you alter the query a bit:
SELECT new com.mypkg.ResultClass(o.order_id, op.product_id, p.name)
FROM public.order o
INNER JOIN public.order_products op
ON p.order_id = op.product_id
INNER JOIN public.product p
ON op.product_id = p.id
WHERE p.name = 'Foo';
And change the return type on the interface method:
#Repository
public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
#Query("...")
List<ResultClass> findAllByProductName(#Param("name") String name);
}
I'm sorry for my maybe foolish question. I have products and orders tables (with many-to -many relationship), also i have an user table. And now I want to get the product count by user_id and by special field "order_satus". I can make two queries get order by special criteria and then get size of product in order. But this is not optimal at all. When i use JDBCTemplate I did a lot of joins and get only one query.
Here are my entities:
#Entity
#Table(name = "shop.order")
public class Order {
#Id
#Column(name = "order_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private long orderId;
private long user_id;
#Column(name = "databegin")
private Date dateBegin;
#Column(name = "dataend")
private Date dateEnd;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", insertable = false, updatable = false)
private User user;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "order_product", joinColumns = { #JoinColumn(name = "order_id") }, inverseJoinColumns = { #JoinColumn(name = "product_id") })
private List<Product> products;
}
Product Entity
#Entity
#Table(name = "product")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int product_id;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "order_product", joinColumns = { #JoinColumn(name = "product_id") }, inverseJoinColumns = { #JoinColumn(name = "order_id") })
private List<Order> order;
public List<Order> getOrder() {
return order;
}
public void setOrder(List<Order> order) {
this.order = order;
}
#Column
#NotBlank
private String name;
#Column
#Max(value = 250)
private String descr;
#Column
#Max(value = 250)
private String manufacturer;
#Column
private Double price;
#Column
private Byte[] barcode;
#Column
private Byte[] picture;
#ForeignKey(name = "category_id")
private int category_id;
As you gave sql query..
'select count(*) from produtc p join order ord on ord.id = ? and
ord.status = ?' – Igor Masternoy
And according to the Entity structure you gave, the HQL will be..
select ord.products productList from Order ord where ord.id=? and ord.status=?
This query will return you list of products (List<Product> products) and then you can get the count by java code i.e. productList.size(); This size is the product count you need based on order id and order status you will pass as parameter and also you can append user.id in where cause to filter your result as per user.
This is productList as per your need..
Query query = getSession().createQuery("select ord.products productList from Order ord where ord.id=:orderID and ord.status=:orderStatus");
query.setInteger("orderID", orderIDParameter);
query.setString("orderStatus", orderStatusParameter);
List<Product> productList = (List<Product>) query.list();
Integer productCount = productList.size();
This productCount is your product count you need.
If I get it right, you can have many orders for one user, and many products for one order.
I think a good option is to use a DetachedCriteria and to build your query with it.
Should look like (not tested):
DetachedCriteria userCriteria = DetachedCriteria.forClass(User.class);
userCriteria.add(Projections.groupProperty("user_id"));
userCriteria.add(Projections.count("product_id");
DetachedCriteria orderCriteria = userCriteria.createCriteria("order.user_id","order",CriteriaSpecification.LEFT_JOIN);
DetachedCriteria orderCriteria = orderCriteria.createCriteria("order_product.order_id","product",CriteriaSpecification.LEFT_JOIN);
//orderCriteria.add(Restrictions.eq(...); // I can't see a "status" field in your model
List results = userCriteria.list();
select size(ord.products) from Order ord where ord.id = :orderId and ord.status = :orderStatus
or
select count(*) from Order ord join ord.products where ord.id = :orderId and ord.status = :orderStatus
The first form will result in a subquery to get the count. The second relies on the join to create the sql product over which to apply the count. The first form is more intuitive, in my opinion, while the second query will perform better in most cases.