OrderBy is not working with the In clause in Spring Data JPA - spring-boot

I have a method whose signature is like this.
public Page<Config> searchByCriteria(Map<String, String> params, Pageable pageable) {
//prework
if (zoneIds.equals(BLANK_STRING)) {
return repository.findByUserIdAndNameContains(userId, name, pageable);
}
List<Integer> zones = Arrays
.stream(zoneIds.split(","))
.distinct()
.map(Integer::parseInt)
.collect(toList());
return repository.findByUserIdEqualsAndNameContainsAndZonesInAndOrderByUpdatedOnDesc(userId, name, zones, pageable);
}
Here the second query is not working when the OrderBy clause is being appended.
However, it works fine when I remove the OrderBy clause.
eg: repository.findByUserIdEqualsAndNameContainsAndZonesIn(...) This works fine.
Please suggest how can I use In and OrderBy clauses together.
here is the error which I am getting here in the stack trace.
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property inAnd found for type Integer! Traversed path:
Config Object
#Entity
public class Config extends AbstractEntity implements Serializable {{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer configId;
private Integer userId;
private String name;
#Enumerated(EnumType.STRING)
private State state;
#ElementCollection
#CollectionTable(name = "zone", joinColumns = #JoinColumn(name = "config_id"))
#Column(name = "zone_id")
private Set<Integer> zones = new HashSet<>();
#Valid
#ElementCollection
#CollectionTable(name = "param", joinColumns = #JoinColumn(name = "config_id"))
private Set<ApParam> apParams = new HashSet<>();
private String remarks;
//getter
//setter
}

I think the problem is you are connecting the method query and the order by with And. This should work: repository.findByUserIdEqualsAndNameContainsAndZonesInOrderByUpdatedOnDesc(userId, name, zones, pageable);
Example: https://www.baeldung.com/spring-data-sorting#1-sorting-with-the-orderby-method-keyword
Document reference: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation

Related

Join Column between entities get NULL value instead of parent entity id number

I am Using Spring Boot on Java to create user's order on his checkout. A new Orders object is created which has a Linked Set of Items. Those items are user's cart contents.
Order is created, but its set of Items is null. The set size is 0. I checked that in JUnit tests. Can you help me to find out what is wrong? Maybe I have defined entities incorrectly? Have a look at the picture of the database:
And check the entities, Orders:
#Entity
public class Orders {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotEmpty
#DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime submitedAt;
#NotEmpty
private String orderName;
#NotEmpty
#Column(name="`User`")
private String username;
#Enumerated(EnumType.STRING)
#Column
private OrderStatus status;
#OneToMany(mappedBy = "orders", cascade = { CascadeType.ALL}, fetch = FetchType.LAZY)
private Set<Item> items;
Item:
#Entity
public class Item {
#Id
private Integer id;
#Column(name="`NAME`")
private String dishName;
#Column(name = "`DESCRIPTION`", length = 2000)
private String dishDescription;
#Column(name = "`QUANTITY`")
private Integer quantityInCart;
#Column(name = "`USER`")
private String username;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
#JoinColumn(name = "ORDERS_ID")
private Orders orders;
How to do entities relation correctly? Should it be one direction or bi-directional relationship?
What are differences of these relations? And what kind of relationship I should use? Why?
I was doing JUnit tests for the Orders service methods. It turns out that it can create orders. And Order items from user's cart.
But when it is time to show order (GetMapping) then it returns Orders entity with empty items set.
I think it happens because JPA cannot find foreign key of items for its designated order. It is null.
Why is it null?
And this is the service method that creates such order by user request:
#Transactional
public ResponseEntity<String> createOrder (String username) {
User user = userService.findByUsername(username);
List<CartItem> items = cartRepo.findByUser(user);
if(items.size() > 0) {
Orders newOrder = new Orders();
Set<Item> orderItems = new LinkedHashSet<>();
for(CartItem item : items) {
// new Item(Integer id, String dishName, String dishDescription, Integer quantityInCart, String username)
Item orderItem = new Item(item.getId(), item.getDish().getName(),
item.getDish().getDescription(), item.getQuantity(), item.getUser().getUsername());
orderItems.add(orderItem);
}
newOrder.setItems(orderItems);
newOrder.setOrderName(user.getUsername()+"'s order");
newOrder.setStatus(OrderStatus.SUBMIT);
newOrder.setSubmitedAt();
newOrder.setUsername(username);
orderDao.save(newOrder);
cartService.removeAllUserProducts(username);
LOG.info("[{}]: A new order is created successfully.", username);
return new ResponseEntity<String>("A new order is created successfully.", HttpStatus.CREATED);
}
//...
}
I tried to do one direction relationship for other entities and it really created foreign keys on joined column fields. But I want to find out why my bidirectional way of joining is wrong. Maybe someone who really knows can explain.
The Order class should be like this:
#Entity
public class Orders {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotEmpty
#DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime submitedAt;
#NotEmpty
private String orderName;
#NotEmpty
#Column(name="`User`")
private String username;
#Enumerated(EnumType.STRING)
#Column
private OrderStatus status;
#OneToMany(cascade = { CascadeType.ALL}, fetch = FetchType.LAZY, orphanRemoval = true)
#JoinColumn(name="ORDERS_ID")
private Set<Item> items;
And Item class without Orders class and its ManyToOne relationship.
Now relationship is unidirectional. Item entity has foreign keys column name ORDERS_ID that has id's of Orders for which Items belong.

ERROR: syntax error at or near "." - JPA Pageable

repository:
#Repository
public interface PostRepository extends PagingAndSortingRepository<Post, Long> {
#Query(value = "SELECT p.postComments FROM Post p WHERE p.webId = ?1")
Page<PostComment> findCommentsByWebId(String webid, Pageable pageable);
}
Post entity:
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "web_id")
private String webId;
#Column(nullable = false, name = "title")
private String title;
#Column(nullable = false, name = "description")
private String description;
#Column(nullable = false, name = "mature")
private boolean mature;
#OneToOne(mappedBy = "post")
private Cover cover;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#OneToMany(mappedBy = "post")
private List<PostView> postViews;
#ManyToMany
#JoinTable(name = "post_tag",
joinColumns = #JoinColumn(name = "post_id"),
inverseJoinColumns = #JoinColumn(name = "tag_id"))
private List<Tag> tags;
#OneToMany(mappedBy = "post")
private List<PostDownvote> postDownvotes;
#OneToMany(mappedBy = "post")
private List<PostUpvote> postUpvotes;
#OneToMany(mappedBy = "post")
private List<PostComment> postComments;
#Column(name = "created_at")
private Timestamp createdAt;
#Column(name = "updated_at")
private Timestamp updatedAt;
}
The problem: When returning plain List<PostComment> from the query method everything works fine. But if I change it to Page<PostComment> (I need total elements count), I get the following error:
2022-08-03 22:29:41.399 ERROR 9192 --- [nio-8080-exec-3] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: syntax error at or near "."
Position: 14
Hibernate: select tags0_.post_id as post_id1_6_0_, tags0_.tag_id as tag_id2_6_0_, tag1_.id as id1_10_1_, tag1_.name as name2_10_1_ from post_tag tags0_ inner join tag tag1_ on tags0_.tag_id=tag1_.id where tags0_.post_id=?
org.springframework.dao.InvalidDataAccessResourceUsageException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
It is very difficult to debug this. Does anyone have any clue on what is wrong?
I need BOTH paging and total amount of elements.
Basically you are not able to fetch the part of the inner collection. But you could reach it from the another side of the bi-directional relationship
#Repository
public interface PostCommentRepository extends PagingAndSortingRepository<PostComment, Long> {
#Query(value = "SELECT pc FROM PostComment pc WHERE pc.post.webId = ?1")
Page<PostComment> findCommentsByWebId(String webid, Pageable pageable);
// or better using Spring Data naming conventions just
Page<PostComment> findAllByPostWebId(String webid, Pageable pageable);
}
If you only need a total count you should avoid querying list of entities which could be very memory intensive.
So in your PostCommentRepository try the following:
long countAllByPost_WebId(String webId);

Pageable not giving expected results with #ManyToMany relationship

We are dealing with #ManyToMany relation with Users and Roles and want to have pagination to get all the Users with associated Roles by using Pageable interface. It is only considering the records count for pagination on the User Table and Roles table record is not considered. But ideally in RDBMS the actual record count would be after flattening the result of join between Users and Roles table.
When working with Pageable in findAll method and passing the page configuration as below :
pageno: 0 and pageSize:1
Pageable paging = PageRequest.of(0, 1);
userRepository.findAll(paging);
It is giving the result as below 
Technically there are 3 records when we flatten the result but pageable is considering this as 1 record which is not correct. Is this intended behavior?
Is there a way where we can get the pagination after flattening the result set of query?
Yes. This is intended. Data is mapped to Java objects as nested objects. Hence, pageable of 5 user records will return 5 users irrespective of number of roles each user has.
To restrict pagination based on record count by combination of user and role, you have to add join between user and role to the query in repository method and fetch columns from both user and role (like we do in SQL).
Below code works for me
User entity
public class User
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
#NonNull
#Column(unique = true, name= "user_name")
private String userName;
#NonNull
private String password;
#NonNull
private boolean status;
#NonNull
private boolean passwordExpired;
#ManyToMany(fetch=FetchType.EAGER,cascade = CascadeType.ALL)
#JoinTable(name = "user_role", joinColumns = {
#JoinColumn(name = "userId", referencedColumnName = "userId") }, inverseJoinColumns = {
#JoinColumn(name = "role_name", referencedColumnName = "name") })
#BatchSize(size = 20)
private Set<Role> roles = new HashSet<>();
//Get and set
}
Role Entity
public class Role {
private static final long serialVersionUID = 1L;
#NotNull
#Size(max = 50)
#Id
#Column(length = 50,unique=true)
private String name;
//get and set
}
Repository
#Repository
public interface UserRepo extends JpaRepository<User, Long>
{
#Query(value="SELECT u.userName,r.name FROM User u left join u.roles r")
public ArrayList<User> findByrole(Pageable paging);
}
Service method
public ArrayList<User> findByrole()
{
// TODO Auto-generated method stub
Pageable paging = PageRequest.of(0, 4);
return uRepo.findByrole(paging);
}

Suggestion for implementation of search filter with many2many relationship between two entites

I want to implement /search rest method that will filter my Product object for the given parameters and return me a pageable set of products that are filtered.
I was reading about Specification interface and Criteria API but i am having difficulties in implementing the solution.
Product entity:
#Entity
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long productId;
#NotEmpty(message = "The product name must not be null.")
private String productName;
private String productDescription;
#Min(value = 0, message = "The product price must no be less then zero.")
private double productPrice;
#Min(value = 0, message = "The product unit must not be less than zero.")
private int unitInStock;
#ManyToMany
#JoinTable(name = "category_product", joinColumns = #JoinColumn(name = "product_id"), inverseJoinColumns = #JoinColumn(name = "category_id"))
private Set<Category> categories = new HashSet<>();
As i want the user to be able to search by category name also,bedsides a price range and unitInStock which is separate entity and it is linked with #ManyToMany relationship ,i want to have a method that would look something like:
#GetMapping("/search")
public ResponseEntity<Set<Product>> advancedSearch(#RequestParam(name="category") String categoryName,
#RequestParam(name="price") double price,
#RequestParam(name="unitInStock") int unitInStock ){
}
Category entity:
#Entity
public class Category implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long categoryId;
#NotEmpty(message = "Can not be null")
private String CategoryName;
#ManyToMany(mappedBy = "categories")
#JsonBackReference
private Set<Product> products = new HashSet<>();
Create spring repository with method with JPQL query:
#Query("select p from Product p left join p.categories c where c.CategoryName like ?1 and p.productPrice=?2 and p.unitInStock=?3")
List<Product> search(String categoryName, double price, int unitInStock)

Can't hibernate search sort in #OneToMany association?

I try to sort a list of Items for a customer by ordered Date. The Date is only avalable through Item.orderPositions.order.orderDate . But #IndexedEmbedded doesn't work. There's no Exeption or Error but the result is only sorted by HS-logic.
#Entity
#Indexed
public class Item{
#Id
private long id;
#Field(index = Index.YES, store = Store.YES, analyse = Analyse.YES, analyser = #Analyzer(definition = Constant.ANALYSER))
private String description;
#OneToMany(mappedBy = "item")
#IndexedEmbedded
private List<OrderPosition> orderPositions;
#ManyToOne
#IndexedEmbedded
private Company company;
//getter&setter
}
#Entity
public class OrderPosition{
#Id
private long id;
#ManyToOne
private Item item;
#ManyToOne
#IndexedEmbedded
private Order order;
//getter&setter
}
#Entity
public class Order{
#Id
private long id;
#ManyToOne
private Customer customer;
#Field(index = Index.NO, store = Store.NO, analyze = Analyze.NO)
#SortableField
private String orderDate;
//getter&setter
}
#Entity
public class Company{
#Id
private long id;
#Field(index = Index.NO, store = Store.NO, analyze = Analyze.NO)
#SortableField
private String name;
//getter&setter
}
If I sort the List by Item.company.name it works fine.
queryService.buildFullTextQuery("searchText", Item.class, "description", "company.name").getResultList();
If I sort the List by Item.orderPosition.order.orderDate it's sorted by default(HS-logic)
queryService.buildFullTextQuery("searchText", Item.class, "description", "orderPositions.order.orderDate").getResultList();
I build the FullTextQuery this way:
public FullTextQuery buildFullTextQuery(#NonNull String searchText, #NonNull Class<?> clazz, #NonNull String... fields) throws Exception {
FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager(getEntityManager());
QueryBuilder qb = fullTextEntityManager.getSearchFactory().buildQueryBuilder().forEntity(clazz).get();
Query query = qb.keyword().onField(fields[0]).matching(searchText).createQuery();
SortField sortField = new SortField(fields[1], SortField.Type.STRING, false);
Sort sort = new Sort(sortField);
return fullTextEntityManager.createFullTextQuery(query, clazz).setSort(sort);
}
I think HS can't find the association for #OneToMany. Is there a way to solve this prob?
Thank you in advance
I can't tell you what's going on exactly without the results of your queries, but you're definitely doing something wrong here: you are trying to sort on a multi-valued field. One item is linked to multiple orders, each having its own date. So there is multiple dates per item.
When you ask to compare two items that each have three dates, what should Hibernate Search do? Compare only the latest dates? Compare only the earliest dates? You didn't say, so your query is bound to return inconsistently ordered results.
Thing is, there is no way to tell Hibernate Search which value to pick in multi-valued fields, so your easiest way out is to explicitly create a single-valued field to sort on.
For instance, you could add a getter on Item to return the latest order, and add the #IndexedEmbedded there:
#Entity
#Indexed
public class Item{
#Id
private long id;
#Field(index = Index.YES, store = Store.YES, analyse = Analyse.YES, analyser = #Analyzer(definition = Constant.ANALYSER))
private String description;
#OneToMany(mappedBy = "item")
#IndexedEmbedded
private List<OrderPosition> orderPositions;
#ManyToOne
#IndexedEmbedded
private Company company;
#javax.persistence.Transient
public Order getLatestOrder() {
Order latestOrder;
// ... compute the latest order ...
return latestOrder;
}
//getter&setter
}
Then sort on latestOrder.orderDate and you should be good.

Resources