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.
Related
I've been hitting my head in the wall for couple of days to figure out how to get partial result from #ManyToMany realtion from Criteria query. There are two entities:
public class UserEntity {
#Id
private Integer userId;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.DETACH)
#JoinTable(
name = "user2brand",
joinColumns = {#JoinColumn(name = "user_id", nullable = true)},
inverseJoinColumns = {#JoinColumn(name = "brand_id", nullable = true)}
)
private List<BrandEntity> brands;
....
}
public class BrandDTO {
#Id
#Column(name = "brand_id", unique = true, nullable = false)
private Integer brandId;
#Column(name = "brand_name", length = 128)
private String brandName;
...
}
public List<UserEntity> listUsersByBrand(List<Integer> brandIds) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<UserEntity> query = cb.createQuery(UserEntity.class);
Root<UserEntity> root = query.from(UserEntity.class);
final ListJoin<BrandEntity, UserEntity> brands = root.joinList("brands", JoinType.INNER);
CriteriaBuilder.In<Integer> inClauseBrand = cb.in(brands.get("brandId"));
inClauseBrand.value(brandIds);
query.select(root).distinct(true).where(inClauseBrand);
return result.getResultList();
}
And here is my problem - when using criteria query on the UserEntity i can add where clause to get the users which are related to a specific brand. However when getting the resultList from the query each UserEntity has all the brands that it relates to but not only the ones that are in the where clause. C
Can this be achieved using criteria query or I should go with native query ?
Hey so I have a many to many relationship between two tables, Knights and Quests.
I want to write JPQL query where I get quests with id of 1 from knights of id 1 as well, later on I will change it to quest status, but for now I want to make it work:
#Query("select k.quests from Knight k join k.quests q where k.id=1 and q.id=1")
Collection<Quest> findDoneStories();
It kinda works, because it gives me quests from knight with id 1, but it gives me all quests rather than only one quest with id 1.
#Entity
#Table(name = "knights")
public class Knight {
#Id
#Column(name = "id")
int id;
#Column
String name;
#Column(name = "status")
#Enumerated(EnumType.STRING)
private KnightEnum status;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "knights_quests", joinColumns = #JoinColumn(name = "id_knights"),
inverseJoinColumns = #JoinColumn(name = "id_quest"))
List < Quest > stories;
}
#Entity
#Table(name = "quests")
public class Quest {
#Id
#Column(name = "id")
int id;
#Column
String name;
#Column(name = "description")
String description;
#Column(name = "status")
#Enumerated(EnumType.STRING)
QuestEnum status;
#Column(name = "story_points")
int storyPoints;
#ManyToMany(mappedBy = "stories", fetch = FetchType.EAGER)
List < Sprint > knights;
}
If you want to get only one Quest, your query and method should be like this:
#Query("SELECT DISTINCT q FROM Knight k JOIN k.stories q WHERE k.id = 1 AND q.id = 1")
Quest findDoneStories();
Since it's many to many relation you have to add one more table knights_quests with columns: knighs_id and quests_id where you can store 1 and 1 for example.
Your Entity will be like this:
#ManyToMany() #JoinTable(name = "knights_quests", joinColumns = #JoinColumn(name = "knighs_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "quests_id",referencedColumnName = "name") )
And after you can execute query with simple join, JPQL will handle it under the hood
I am implementing a user management system which has the following entities :
public class UserEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
Long id;
#Column(unique = true, name = "EMAIL")
private String email;
#Column(name = "NAME")
private String name;
#Column(name = "PASSWORD")
private String password;
#Column(name = "MOBILE")
private String mobile;
#Column(name = "OWNER_ID")
private String ownerId;
#Column(name = "TRAINER_ID")
private String trainerId;
#Column(name = "ADDED_ON")
private Timestamp addedOn;
#Column(name = "MODIFIED_ON")
private Timestamp modifiedOn;
#Column(name = "ADDED_BY")
private String addedBy;
#Column(name = "MODIFIED_BY")
private String modifiedBy;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "USER_ROLES", joinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "ID"),
inverseJoinColumns =
#JoinColumn(name =
"ROLE_ID", referencedColumnName = "ROLE_ID"))
List<RoleEntity> roles;
#OneToOne(
mappedBy = "user",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private UserStatisticsEntity userStatisticsEntity;
here is the RoleClass :
public class RoleEntity implements GrantedAuthority {
#Id
#Column(name="ROLE_ID")
private String roleId;
#Column(name="ROLE_NAME")
private String roleName;
#ManyToMany(mappedBy = "roles")
private List<UserEntity> users;
#Override
public String getAuthority() {
return this.roleId;
}
}
I would like to fetch all users belonging to a particular role and also be able to add dynamic where clauses on name, mobile, email along with paging etc.
My current code looks like this to fetch selected fields of Users with dynamic where clauses and pagination :
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<TrainerDTO> criteriaQuery = criteriaBuilder.createQuery(TrainerDTO.class);
Root<UserEntity> main = criteriaQuery.from(UserEntity.class);
criteriaQuery.multiselect(main.get("id"), main.get("name"), main.get("email"), main.get("ownerId"), main.get(
"mobile"),
main.get("addedBy"), main.get("modifiedBy"), main.get("addedOn"), main.get("modifiedOn"))
.orderBy(criteriaBuilder.desc(main.get("addedOn")))
.distinct(true);
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNotBlank(queryParams.get("mobile"))) {
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(main.get("mobile"), queryParams.get("mobile"))));
}
if (StringUtils.isNotBlank(queryParams.get("name"))) {
predicates.add(criteriaBuilder.and(criteriaBuilder.like(main.get("name"),
"%" + queryParams.get("name") + "%")));
}
if (StringUtils.isNotBlank(queryParams.get("email"))) {
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(main.get("email"), queryParams.get("email"))));
}
criteriaQuery.where(predicates.toArray(new Predicate[predicates.size()]));
log.info("TrainerDAO::getAllTrainersPaginatedForOwner Query created...");
TypedQuery<TrainerDTO> query = entityManager.createQuery(criteriaQuery);
query.setFirstResult(pageNumber - 1);
query.setMaxResults(pageSize);
return query.getResultList();
I am having two issues here :
How do I get all users which have a certain role? Suppose I need to find all users which have a Role with ROLE_ID = "ROLE_ADMIN".
In my pagination implementation, the last item in repeated on the next page. Suppose User1 was the last item on page 1, he is coming as first item on page 2 as well.
Please suggest on how to proceed further. All help would be appreciated.
Here is my way would be like this:
Issue 1:
You need to reach RoleEntity to check if the role_id is equal to "ROLE_ADMIN", so you need to fetch roles from RoleEntity first and get all the information there.
After you created main object:
Fetch<UserEntity, RoleEntity> fetchedRoles = main.fetch("roles", JoinType.LEFT);
You will append your condition to your predicates list;
predicates.add(criteriaBuilder.equal( fetchedRoles.get( "roleId" ), "ROLE_ADMIN"));
Issue 2:
I will try to share what I would do in this case to help you solve the issue.
Let's say you create the query here, in this method with pageable object, you want to return Page
private Page<Books> getUsersWithAdminRole(String... parameters, Pageable pageable){
//...
List<UserEntity> result = entityManager.createQuery(criteria).setFirstResult((int) pageable.getOffset()).setMaxResults(pageable.getPageSize()).getResultList();
CriteriaQuery<Long> countQuery = criteriaBuilder.createQuery(Long.class);
Root<UserEntity> userCount = countQuery.from(UserEntity.class);
countQuery.select(criteriaBuilder.count(userCount)).where(criteriaBuilder.and(predicates.toArray(newPredicate[predicates.size()])));
Long count = entityManager.createQuery(countQuery).getSingleResult();
Page<UserEntity> userPage = new PageImpl<>(result, pageable, count);
return userPage;
}
I hope, it is helpful
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.
Brand
public class Brand implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "BrandID", nullable = false)
private Integer brandID;
#Basic(optional = false)
#Column(name = "BrandName", nullable = false, length = 100)
private String brandName;
#Basic(optional = false)
#Column(name = "Description", nullable = false, length = 1000)
private String description;
#Column(name = "Is_Visible")
private Boolean isVisible;
#JoinTable(name = "brandcategory", joinColumns = {
#JoinColumn(name = "BrandID", referencedColumnName = "BrandID")}, inverseJoinColumns = {
#JoinColumn(name = "CategoryID", referencedColumnName = "CategoryID")})
#ManyToMany(fetch = FetchType.EAGER)
private Collection<Category> categoryCollection;
#OneToMany(mappedBy = "brand", fetch = FetchType.EAGER)
private Collection<Product> productCollection;
I want to retrive the Brand IDs from table brandcategory whoes categoryID = :categoryID
how can i createnamed query for it in entity brand?
this does not work:
#NamedQuery(name = "Brand.getBrandListByCategory",
query = "SELECT b FROM Brand b WHERE b.brandID =
(SELECT bc.brandID
FROM b.brandctegory bc
WHERE bc.category.categoryID = :categoryID)")
If I understand correctly, you want all the brands belonging to a category. Why don't you simply make the association bidirectional. You could then just do:
Category category = em.find(Category.class, categoryId);
return category.getBrands();
If it's unidirectional, then you'll need a query, but it's much simpler that the one you tried:
select b from Brand b inner join b.categoryCollection category
where category.id = :categoryId;
Your query doesn't make sense: it uses a non-existing association (b.brandcategory). Remember that JPQL uses entities, their persistent fields and associations to other entities. And nothing else. Tables don't exist in JPQL.
AFAIK, you cant go out of a entity boundary, when creating queries in entity class.
Instead use .createNativeQuery() method of the entity manager, to create complex and mixed queries.