Query in Spring Boot JPA - #OneToMany List relation - jpa

I've got entity like this:
#Entity
#Table(name = "formula")
public class Formula {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
#Column(name = "formula_id")
private Long formulaId;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#Column(name = "time")
private int time;
#OneToMany(mappedBy = "formula",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Product> productList = new ArrayList<>();
And another Entity:
#Entity
#Table(name = "products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long productId;
#Column(name = "product_name")
private String productName;
#Column(name = "amount")
private Double amount;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "formula_id")
private Formula formula;
I want to ask Query to DB which help me get every type of data (by key word). I've got all except List of <Product>. It look like this:
public interface FormulaRepository extends JpaRepository<Formula, Long> {
#Query("SELECT f FROM Formula f WHERE " + "CONCAT(f.name, f.description,
f.time)" + "LIKE %?1%")
List<Formula> findFormulaBy(String word);
How can add productList to Query and acomplished my searching? Is there any possibility to do this in findFormulaBy(String word); method?

Change query to include LEFT JOIN FETCH to eagerly fetch productList. Also include DISTINCT to prevent duplicate Formula objects in List
#Query("SELECT DISTINCT f FROM Formula f " +
"LEFT JOIN FETCH f.productList " +
"WHERE " + "CONCAT(f.name, f.description,f.time)" + "LIKE %?1%")
List<Formula> findFormulaBy(String word);
SQL generated by Hibernate
2022-09-10 10:16:38.287 DEBUG --- [ main] org.hibernate.SQL :
select
distinct formula0_.formula_id as formula_1_9_0_,
productlis1_.product_id as product_1_12_1_,
formula0_.description as descript2_9_0_,
formula0_.name as name3_9_0_,
formula0_.time as time4_9_0_,
productlis1_.amount as amount2_12_1_,
productlis1_.formula_id as formula_4_12_1_,
productlis1_.product_name as product_3_12_1_,
productlis1_.formula_id as formula_4_12_0__,
productlis1_.product_id as product_1_12_0__
from
formula formula0_
left outer join
products productlis1_
on formula0_.formula_id=productlis1_.formula_id
where
(
formula0_.name||formula0_.description||formula0_.time
) like ?
I see in your comment you have added f.productList list to the CONCAT function which is why you are getting a SQL error. If you want to search product fields in CONCAT function you will need to give p.productList an alias and reference the fields in this way
#Query("SELECT DISTINCT f FROM Formula f " +
"LEFT JOIN FETCH f.productList p " +
"WHERE " + "CONCAT(f.name, f.description,f.time,p.productName)" + "LIKE %?1%")
List<Formula> findFormulaBy(String word);
This seems a strange way to search formulae and products and you will be better off adding a second parameter to your SQL
#Query("SELECT DISTINCT f FROM Formula f " +
"LEFT JOIN FETCH f.productList p " +
"WHERE " + "CONCAT(f.name, f.description,f.time)" + "LIKE %?1% " +
"AND p.productName = ?2 ")
List<Formula> findFormulaBy(String word, String productName);

Related

Which return value should I use for the one-to-one table when using spring boot jpa?

We are working on a club management project.
There is a club president on the club table and the user ID is received as a foreign key.
I want to join two tables while using jpa and get the results
If you specify the mapped club table as a type, an error appears (image 1)
If you interface the resulting field, only null values are returned. (Image 2)
How shall I do it?
(Image1)
org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.Object[]] to type [#org.springframework.data.jpa.repository.Query com.wodongso.wodongso.entity.Society] for value '{호남대학교, 왕유저13, 두 발의 자유1, 스포츠, 두 바퀴만 있다면 지원가능!!1, 허벅지 터지도록 활동합니다1, true}'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [#org.springframework.data.jpa.repository.Query com.wodongso.wodongso.entity.Society]
(Image2)
society entity
user entity
#Entity
#Data
public class User {
#Id
private String id;
private String name;
private String nickname;
private String password;
private String role;
private String contact;
#Column(name = "profile_url")
private String profileUrl;
private String region;
private String university;
private String major;
#Column(name = "class_of")
private Integer classOf;
private boolean enabled;
#Column(name = "created_at")
private Date createdAt;
}
SocietyRepository
#Repository
public interface SocietyRepository extends JpaRepository<Society, Integer> {
Page<Society> findByNameContaining(String searchKeyword, Pageable pageable);
#Query("SELECT s FROM Society s WHERE s.enabled = :isEnable")
Page<Society> findByEnabledPage(#Param("isEnable") boolean isEnable, Pageable pageable);
#Query("SELECT u.university, u.name, s.name, s.category, s.simpleDesc, s.detailDesc, s.enabled " +
"FROM Society s " +
"INNER join User u " +
"ON u.id = s.officerId " +
"WHERE u.university = :university")
List<Society> findAllByUniversity(#Param("university") String university);
}
Create a class which contains all the fields and add an all args constructor and than use the query:
#Query("SELECT new SocietyWithUser(u.university, u.name, s.name, s.category, s.simpleDesc, s.detailDesc, s.enabled) " +
"FROM Society s " +
"INNER join User u " +
"ON u.id = s.officerId " +
"WHERE u.university = :university")
List<SocietyWithUser> findAllByUniversity(#Param("university") String university);

How to return a count column not exists in table by JPA

I want find a way to get extra column that count my records and return it in 1 mapping entity with extra filed.
I tried #transient on field but it will not return value when query.
Then I remove #transient but get an exception when save.
Also I tried #Formula but received null pointer exception.
Here's my repository code:
#Query(value = "select id,account,session_id,create_time,count from query_history a join " +
"(select session_id sessionId,max(create_time) createTime,count(*) count from query_history group by session_id) b " +
"on a.session_id = b.sessionId and a.create_time = b.createTime where account = ?1 order by create_time desc",
countQuery = "select count(distinct(session_id)) from query_history where account = ?1",
nativeQuery = true)
Page<QueryHistory> findByNtAndGroupBySessionAndAction(String account, Pageable pageable);
entity code:
#Entity
#Table(name = "query_history")
#Data
public class QueryHistory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String account;
#Column
private Long sessionId;
#Column
private long createTime;
#Transient
private Integer count;
}
Sorry about my English and thanks a lot for any advice.
I solved the problem by projections spring-data-projections, in fact I tried this before but in my sql:
select id,account,session_id,create_time,count
which should be:
select id,account,session_id sessionId,create_time createTime,count
PS:
projection interface:
public interface QueryHistoryWithCountProjection {
Long getId();
String getAccount();
Long getSessionId();
long getCreateTime();
Integer getCount();
}

JPA Create Criteria with GROUP COUNT and HAVING on nested objects

Given this SQL query
SELECT
ug.lookup_key,
count(ug.id) as count
FROM user u
INNER JOIN user_group ug on ug.id = u.id
WHERE
u.age >= 11 AND
u.age <= 20 AND
ug.lookup_key in('12345')
GROUP BY ug.lookup_key
HAVING count(ug.id) < 7
I have written this
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = criteriaBuilder.createQuery(Object[].class);
Root<UserGroup> d = query.from(UserGroup.class);
Join<UserGroup, User> join = d.join("users");
Predicate pred1 = criteriaBuilder.between(join.get("age"), ageFrom, ageTo);
Expression<String> exp = d.get("lookupKey");
Predicate pred2 = exp.in(lookupKeys);
query.where(pred1, pred2);
query.multiselect(d.get("lookupKey"), criteriaBuilder.count(d.get("id"))).groupBy(d.get("lookupKey"));
List<Object[]> results = entityManager.createQuery(query).getResultList();
for(Object[] object : results){
System.out.println(object[0] + " " + object[1]);
}
The SQL returns {"12345",4} whereas the code returns {"12345", 37}. The SQL is the correct result. There are 37 users in the database for groups with that lookup key, so I understand where the numbers are coming from but I do not understand how to do the JOIN, GROUP BY, HAVING with the CreateCriteria query so that I get the results. I don't want to use JPQL.
The entities...
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private int age;
private double salary;
#ManyToOne(optional=false,cascade=CascadeType.ALL, targetEntity=UserGroup.class)
#JsonBackReference
private UserGroup group;
// Getters and Setters //
}
#Entity
public class UserGroup {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String lookupKey;
#OneToMany(mappedBy="group",targetEntity=User.class, fetch=FetchType.EAGER)
#JsonManagedReference
private Collection users;
// Getters and Setters //
}
And also, here is the method in which it is implemented
public void summarizeGroupsByLookupKey(long ageFrom, long ageTo, List<String> lookupKeys, long numUsers){
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Object[]> query = criteriaBuilder.createQuery(Object[].class);
Root<UserGroup> d = query.from(UserGroup.class);
Join<UserGroup, User> join = d.join("users");
Predicate pred1 = criteriaBuilder.between(join.get("age"), ageFrom, ageTo);
Expression<String> exp = d.get("lookupKey");
Predicate pred2 = exp.in(lookupKeys);
query.where(pred1, pred2);
query.multiselect(d.get("lookupKey"), criteriaBuilder.count(d.get("id")));
query.groupBy(d.get("lookupKey"));
query.having(criteriaBuilder.<Long>lessThan(criteriaBuilder.count(d.get("id")), numUsers));
List<Object[]> results = entityManager.createQuery(query).getResultList();
for(Object[] object : results){
System.out.println(object[0] + " " + object[1]);
}
}
By way of info...using Spring Boot 1.5.1 and all the default JPA, Hibernate, etc. from there.
Can a JPA expert offer some help? Thanks!
Change the multiselect part to use countDistinct(..)
query.multiselect(d.get("lookupKey")
,criteriaBuilder.countDistinct(d.get("id")));
and also having(..)
query.having(criteriaBuilder.<Long>lessThan(
criteriaBuilder.countDistinct(d.get("id")), numUsers)
);
Original query returned row per matching user in which rows userGroup.id was then multiplied.

My JPQL query with Outer Join returns empty list while using GroupBy, OrderBy and Sum Function

This is my Product entity:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotBlank
private String name;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Category category;
...
}
And this is the productOrder junction table for the relation product-order:
#Entity
public class ProductOrder {
#EmbeddedId
private ProductOrderId pk;
#Min(value = 1)
private int quantity;
...
}
And this is the embeddedId class:
#Embeddable
public class ProductOrderId {
#ManyToOne
#JoinColumn(name = "product_id")
private Product product;
#ManyToOne
#JoinColumn(name = "orderr_id")
private Order order;
...
}
I have this JPQL Query works perfectly which finds all the products from a category, sorting them based on their total sold quantity. Since it is a outer join, i get also the ones which haven't been sold yet:
#Query(value = "select p, sum(po.quantity) as total_quantity " +
"from ProductOrder po " +
"right join po.pk.product p where p.category = (?1) " +
"group by p.id, p.name " +
"order by total_quantity desc nulls last")
Page<Object[]> findBestSellerProductsByCategory(Category category, Pageable pageable);
The problem is, when there is not any sold product in a category, i get an empty page. So in order to use this query, my category has to have at least 1 sold product, then i get also the others which haven't been sold yet. What is the problem here?
Since i make a "select from junctionTable (ProductOrder)", when there is not any element from the category in this table, i get an empty list as i understand. But i could not make it the other way around "select from product", since product has no reference of productOrder table, i cannot make a join relationship in that case..
INFO: When i remove the groupBy, it works. I get my products from a category even if none of them is sold yet.
#Query(value = "select p " +
"from ProductOrder po " +
"right join po.pk.product p where p.category = (?1)")
Page<Object[]> findProductsByCategory(Category category, Pageable pageable);

Filtering by map's key & value using JPA2

For the entity defined as follows:
#Entity
public class Acl implements Serializable {
#Id
#GeneratedValue
#Column(name = "acl_id")
private Long id;
#ElementCollection(fetch=FetchType.EAGER)
#JoinTable(name = "acl_permits")
private Map<String, Integer> permits = new HashMap<String, Integer>();
Query:
query = em.createQuery("select a FROM Acl a " +
"JOIN a.permits p WITH KEY(p) = '" + user + "' and VALUE(p) = " + permit + "");
is converted to:
select acl0_.acl_id as acl1_1_ from Acl acl0_
inner join acl_permits permits1_ on acl0_.acl_id=permits1_.Acl_acl_id
and (permits1_.permits_KEY='user1'
and (select permits1_.permits from acl_permits permits1_ where acl0_.acl_id=permits1_.Acl_acl_id)=1)
What would be the query in order to get it converted into?:
select acl0_.acl_id as acl1_1_ from Acl acl0_
inner join acl_permits permits1_ on acl0_.acl_id=permits1_.Acl_acl_id
and (permits1_.permits_KEY='user1' and acl0_.acl_id=permits1_.Acl_acl_id=1)
You can use native SQL in JPA. So you can do this instead.
query = em.createNativeQuery("select acl0_.acl_id as acl1_1_ from Acl acl0_
inner join acl_permits permits1_ on acl0_.acl_id=permits1_.Acl_acl_id
and (permits1_.permits_KEY=?user and acl0_.acl_id=permits1_.Acl_acl_id=?permit_id)");