Pagination on a LinkedHashSet - java

I have an API controller which allows users return a list of user profiles they have saved to their account. This data is stored in MongoDB and in POJO terms the data I want to paginate on is a LinkedHashSet of strings.
Now, in some other parts of my code I have simple List<String> which I'm able to "paginate" via the code below:
public static <T> List<T> getPage(List<T> sourceList, int page, int pageSize) {
if (pageSize <= 0 || page < 0) {
throw new IllegalArgumentException("invalid page size: " + pageSize);
}
if (sourceList == null || sourceList.size() < (page * pageSize)) {
return Collections.emptyList();
}
return sourceList.subList(page, Math.min(page + pageSize, sourceList.size()));
}
Can anyone help me do apply this same logic to a LinkedHashset? I'm struggling since the collection does not have a sublist method.
Below is my controller:
#GetMapping("/users/savedlist")
public Iterable<User> getUsersListOfSavedProfiles(Authentication authentication,
#PageableDefault(page = 0, size = 10) Pageable pageable) {
return savedProfilesService.getUsersSavedProfiles(authentication.getName(), pageable.getPageNumber(), pageable.getPageSize());
And the Pojo:
#Document(collection = "UsersSavedProfiles")
public class SavedProfiles {
#Id
#JsonIgnore
private String _id;
private String userId;
private LinkedHashSet<String> savedProfileIds;
Access to the DB via DALImpl:
#Override
public SavedProfiles findByUserId(String userId) {
Query query = new Query();
query.addCriteria(Criteria.where("userId").is(userId));
return mongoTemplate.findOne(query, SavedProfiles.class);
}
**** Edit for Clarity ****
I know I can paginate a Mongo query for User Profile documents BUT what I need to do is paginate the ArrayList savedProfiles inside one of the documents.
Thanks again to the people who looked at this

Related

Example of spring using Pageable, Example and Sort accessing a JPA repository

I searched everywhere for an example of a Spring piece of code using simultaneously these 3 JPA concepts, very important when querying :
filtering - using Example, ExampleMatcher
paging - using Pageable (or similar)
sorting - using Sort
So far I only saw examples using only 2 of them at the same time but I need to use all of them at once. Can you show me such an example?
Thank you.
PS: This has examples for Paging and Sorting but not filtering.
Here is an example, searching for news on title attribute, with pagination and sorting :
Entity :
#Getter
#Setter
#Entity
public class News {
#Id
private Long id;
#Column
private String title;
#Column
private String content;
}
Repository :
public interface NewsRepository extends JpaRepository<News, Long> {
}
Service
#Service
public class NewsService {
#Autowired
private NewsRepository newsRepository;
public Iterable<News> getNewsFilteredPaginated(String text, int pageNumber, int pageSize, String sortBy, String sortDirection) {
final News news = new News();
news.setTitle(text);
final ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase()
.withIgnorePaths("content")
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
return newsRepository.findAll(Example.of(news, matcher), PageRequest.of(pageNumber, pageSize, sortDirection.equalsIgnoreCase("asc") ? Sort.by(sortBy).ascending() : Sort.by(sortBy).descending()));
}
}
Call example :
for (News news : newsService.getNewsFilteredPaginated("hello", 0, 10, "title", "asc")) {
log.info(news.getTitle());
}
Found the answer in the end after more research:
public Page<MyEntity> findAll(MyEntity entityFilter, int pageSize, int currentPage){
ExampleMatcher matcher = ExampleMatcher.matchingAll()
.withMatcher("name", exact()); //add filters for other columns here
Example<MyEntity> filter = Example.of(entityFilter, matcher);
Sort sort = Sort.by(Sort.Direction.ASC, "id"); //add other sort columns here
Pageable pageable = PageRequest.of(currentPage, pageSize, sort);
return repository.findAll(filter, pageable);
}

Spring boot: Optional parameter query in Query method

I am new to Spring boot and hibernate. Here I am trying run a search based optional parameter query Where i can search by name, country etc. If I kept this field null then query should all list. But the problem is my method is returning all data ignoring my search parameter. my model class look like
#Entity(name="MLFM_ORDER_OWNER")
public class ModelOrderOwner {
#Id #GenericGenerator(name = "custom_sequence", strategy =
"com.biziitech.mlfm.IdGenerator")
#GeneratedValue(generator = "custom_sequence")
#Column(name="ORDER_OWNER_ID")
private Long orderOwnerId;
#Column(name="OWNER_NAME")
private String ownerName;
#OneToOne
#JoinColumn(name="BUSINESS_TYPE_ID")
private ModelBusinessType businessTypeId;
#Column(name="SHORT_CODE")
private String shortCode;
#ManyToOne
#JoinColumn(name="OWNER_COUNTRY")
private ModelCountry ownerCountry;
// getter setter..
My Repository interface looks like
public interface OrderOwnerRepository extends
JpaRepository<ModelOrderOwner,Long>{
#Query("select a from MLFM_ORDER_OWNER a where a.businessTypeId.typeId=coalsec(:typeId,a.businessTypeId.typeId) and a.ownerCountry.countryId=coalsec(:countryId,a.ownerCountry.countryId) and a.ownerName LIKE %:name and a.shortCode LIKE %:code")
public List <ModelOrderOwner> findOwnerDetails(#Param("typeId")Long typeId,#Param("countryId")Long countryId,#Param("name")String name,#Param("code")String code);
}
And here is my method in controller
#RequestMapping(path="/owners/search")
public String getAllOwner(Model model,#RequestParam("owner_name") String name,#RequestParam("shortCode") String code,
#RequestParam("phoneNumber") String phoneNumber,#RequestParam("countryName") Long countryId,
#RequestParam("businessType") Long typeId
) {
model.addAttribute("ownerList",ownerRepository.findOwnerDetails(typeId, countryId, name, code));
return "data_list";
}
Can Any one help me in this regard? please?
It is too late too answer, but for anyone who looks for a solution yet there is a more simple way as below:
In my case my controller was like:
#RestController
#RequestMapping("/order")
public class OrderController {
private final IOrderService service;
public OrderController(IOrderService service) {
this.service = service;
}
#RequestMapping(value = "/{username}/", method = RequestMethod.GET)
public ResponseEntity<ListResponse<UserOrdersResponse>> getUserOrders(
#RequestHeader Map<String, String> requestHeaders,
#RequestParam(required=false) Long id,
#RequestParam(required=false) Long flags,
#RequestParam(required=true) Long offset,
#RequestParam(required=true) Long length) {
// Return successful response
return new ResponseEntity<>(service.getUserOrders(requestDTO), HttpStatus.OK);
}
}
As you can see, I have Username as #PathVariable and length and offset which are my required parameters, but I accept id and flags for filtering search result, so they are my optional parameters and are not necessary for calling the REST service.
Now in my repository layer I have just created my #Query as below:
#Query("select new com.ada.bourse.wealth.services.models.response.UserOrdersResponse(FIELDS ARE DELETED TO BECOME MORE READABLE)" +
" from User u join Orders o on u.id = o.user.id where u.userName = :username" +
" and (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag)")
Page<UserOrdersResponse> findUsersOrders(String username, Long orderId, Long flag, Pageable page);
And that's it, you can see that I checked my optional arguments with (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag) and I think it needs to be emphasized that I checked my argument with is null condition not my columns data, so if client send Id and flags parameters for me I will filter the Result with them otherwise I just query with username which was my #PathVariable.
Don't know how but below code is working for me:
#Query("select a from MLFM_ORDER_OWNER a
where a.businessTypeId.typeId=COALESCE(:typeId,a.businessTypeId.typeId)
and a.ownerCountry.countryId=COALESCE(:countryId,a.ownerCountry.countryId)
and a.ownerName LIKE %:name and a.shortCode LIKE %:code")
public List <ModelOrderOwner> findOwnerDetails(
#Param("typeId")Long typeId,
#Param("countryId")Long countryId,
#Param("name")String name,
#Param("code")String code);
and in my controller class:
#RequestMapping(path="/owners/search")
public String getAllOwner(Model model,
#RequestParam("owner_name") String name,
#RequestParam("shortCode") String code,
#RequestParam("phoneNumber") String phoneNumber,
#RequestParam("countryName") Long countryId,
#RequestParam(value = "active", required = false) String active, #RequestParam("businessType") Long typeId) {
if(typeId==0)
typeId=null;
if(countryId==0)
countryId=null; model.addAttribute("ownerList",ownerRepository.findOwnerDetails(typeId, countryId, name, code, status));
return "data_list";
}
JPQL doesn't support optional parameters.
There is no easy way of doing this in JPQL. You will have to write multiple WHERE clauses with OR operator.
Refer these answers to similar questions: Answer 1 & Answer 2
PS: You might want to look into Query by Example for your use case. It supports handling of null parameters.
Use JpaSpecificationExecutor //import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
Step 1: Implement JpaSpecificationExecutor in your JPA Repository
Ex:
public interface TicketRepo extends JpaRepository<Ticket, Long>, JpaSpecificationExecutor<Ticket> {
Step 2 Now to fetch tickets based on optional parameters you can build Specification query using CriteriaBuilder
Ex:
public Specification<Ticket> getTicketQuery(Integer domainId, Calendar startDate, Calendar endDate, Integer gameId, Integer drawId) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.equal(root.get("domainId"), domainId));
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), startDate));
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), endDate));
if (gameId != null) {
predicates.add(criteriaBuilder.equal(root.get("gameId"), gameId));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
Step 3: Pass the Specification instance to jpaRepo.findAll(specification), it will return you the list of your entity object (Tickets here in the running example)
ticketRepo.findAll(specification); // Pass output of function in step 2 to findAll

spring-data-mongodb optional query parameter

I am using spring-data-mongodb.
I want to query database by passing some optional parameter in my query.
I have a domain class.
public class Doc {
#Id
private String id;
private String type;
private String name;
private int index;
private String data;
private String description;
private String key;
private String username;
// getter & setter
}
My controller:
#RequestMapping(value = "/getByCategory", method = RequestMethod.GET, consumes = MediaType.APPLICATION_JSON, produces = MediaType.APPLICATION_JSON)
public Iterable<Doc> getByCategory(
#RequestParam(value = "key", required = false) String key,
#RequestParam(value = "username", required = false) String username,
#RequestParam(value = "page", required = false, defaultValue = "0") int page,
#RequestParam(value = "size", required = false, defaultValue = "0") int size,
#RequestParam(value = "categories") List<String> categories)
throws EntityNotFoundException {
Iterable<Doc> nodes = docService.getByCategory(key, username , categories, page, size);
return nodes;
}
Here Key and username are optional query parameters.
If I pass any one of them it should return the matching document with given key or username.
My service method is:
public Iterable<Doc> getByCategory(String key, String username, List<String> categories, int page, int size) {
return repository.findByCategories(key, username, categories, new PageRequest(page, size));
}
Repository:
#Query("{ $or : [ {'key':?0},{'username':?1},{categories:{$in: ?2}}] }")
List<Doc> findByCategories(String key, String username,List<String> categories, Pageable pageable);
But by using above query it does not returns a document with either given key or username.
What is wrong in my query?
This is how I am making request
http://localhost:8080/document/getByCategory?key=key_one&username=ppotdar&categories=category1&categories=category2
Personally, I'd ditch the interface-driven repository pattern at that point, create a DAO that #Autowires a MongoTemplate object, and then query the DB using a Criteria instead. that way, you have clear code that isn't stretching the capabilities of the #Query annotation.
So, something like this (untested, pseudo-code):
#Repository
public class DocDAOImpl implements DocDAO {
#Autowired private MongoTemplate mongoTemplate;
public Page<Doc> findByCategories(UserRequest request, Pageable pageable){
//Go through user request and make a criteria here
Criteria c = Criteria.where("foo").is(bar).and("x").is(y);
Query q = new Query(c);
Long count = mongoTemplate.count(q);
// Following can be refactored into another method, given the Query and the Pageable.
q.with(sort); //Build the sort from the pageable.
q.limit(limit); //Build this from the pageable too
List<Doc> results = mongoTemplate.find(q, Doc.class);
return makePage(results, pageable, count);
}
...
}
I know this flies in the face of the trend towards runtime generation of DB code, but to my mind, it's still the best approach for more challenging DB operations, because it's loads easier to see what's actually going on.
Filtering out parts of the query depending on the input value is not directly supported. Nevertheless it can be done using #Query the $and operator and a bit of SpEL.
interface Repo extends CrudRepository<Doc,...> {
#Query("""
{ $and : [
?#{T(com.example.Repo.QueryUtil).ifPresent([0], 'key')},
?#{T(com.example.Repo.QueryUtil).ifPresent([1], 'username')},
...
]}
""")
List<Doc> findByKeyAndUsername(#Nullable String key, #Nullable String username, ...)
class QueryUtil {
public static Document ifPresent(Object value, String property) {
if(value == null) {
return new Document("$expr", true); // always true
}
return new Document(property, value); // eq match
}
}
// ...
}
Instead of addressing the target function via the T(...) Type expression writing an EvaluationContextExtension (see: json spel for details) allows to get rid of repeating the type name over and over again.

How can I fetch multiple rows from db in Hibernate , Spring, JSP?

I'm designing a hospitality app. and having some problem with fetching multiple rows from database. I'm using Hibernate, Spring Web MVC, mySQL and JSP. I have layers as Controller, Service, Dao, Model. I've designed a search page to see the user profiles according to their city. For example when I write 'NewYork' to the city field on the search screen it will show a list of user profiles who live in NewYork and mark isHosting value as true.
Here is my User class:
public class User{
#Column(unique = true, nullable = false)
private String username;
...
private String city;
private String isHosting;
public boolean isHosting() {
return hosting;
}
public void setHosting(boolean hosting) {
this.hosting = hosting;
}
...
}
Search class:
public class Search {
private String city;
private String sdate;
private String fdate;
private String numOfvisitor;
...
}
This my Dao class:
#Repository
public class SearchDao extends GenericDao<User> {
public User findByUserCity(final String city){
final Criteria c = createCriteria(User.class).add(Restrictions.eq("city", city));
return (User) c.uniqueResult();
}
}
Service class:
#Service
#Transactional
public class SearchService extends GenericService<User>{
#Autowired
public SearchService(SearchDao dao) {
super(dao);
}
...
public User findByUserCity(final String city) {
return ((SearchSurferDao) this.dao).findByUserCity(city);
}
}
And Controller class:
#RequestMapping(value = "/search", method = RequestMethod.GET)
public ModelAndView search(#ModelAttribute Search search) {
User user = SearchService.findByUserCity(search.getCity());
ModelAndView result = new ModelAndView("hello");
...
result.addObject("username", user.getUsername());
return result;
}
I know that I need to write a database query which returns a list and the list is needed to be send to JSP file and with foreach tag I can see profiles on the screen. But how can write a database query to get such a list from db, actually put it in which class? What I need to do in Controller? Where can I check isHosting value?
Criteria c = createCriteria(User.class).add(Restrictions.eq("city", city));
return (User) c.uniqueResult();
The above code does create a query which finds the users with the given city. But it assumes that only one user exists in the given city, which is probably not the case. The method should be
public List<User> findByUserCity(final String city) {
Criteria c = createCriteria(User.class).add(Restrictions.eq("city", city));
return c.list();
}
Also, The Criteria API leads to hard to read code, and is suitable when you have to dynamically compose a query based on several search criteria. For such a static query, you should use HQL:
public List<User> findByUserCity(String city) {
return session.createQuery("select u from User u where u.city = :city")
.setString("city", city)
.list();
}
To see only isHosting=true user you have two ways:
Fetch only isHosting=true users
For this, your query will change to :
session.createQuery("select u from User u where u.city = :city and u.isHosting is true")
.setString("city", city)
.list();
Fetch all users with matching city, then filter them in your java code.
ArrayList<User> fetchedList=session.createQuery("select u from User u where u.city = :city")
.setString("city", city)
.list();
for(User u: fetchedList){
if(u.isHosting()){
display(u);
}
}
Here, I would recommend using first option, as db queries are generally faster than iterating through fetched data on the client side.
However, if you want to have info on all of your users and want to filter isHosting=true users, the 2nd option is better than asking DB again and again.

How to map a class via jdbc template

A table contains three columns: Order, Item and price which i access via Jdbc Template and trying to map with DTO.
Order Item Price
101 "xyz" 100
101 "Pqr" 150
101 "abc" 125
102 "any" 200
102 "one" 101
I can map the above table with my dto with as below,
public class myDTO{
String Order; // Order number
String Item; // item name
String price; // item price
//getter-setter below
}
But i want to map the table in such a way where i would able to link an order against all Items and price which has common Order Number. I am just giving a plain idea of expected DTO class but not able to map.
public class requiredDTO{
String order;
List<String> value;
List<String> price;
//getter setter below
}
Use 'BeanPropertyRowMapper' your columns name must match property names of MyDTO.
getJdbcTemplate().query("SELECT Order, Item, Price FROM your_table", new BeanPropertyRowMapper(MyDTO.class));
Then i recommend you to do your group logic later in java.
Good Luck!
You don't want either of your solutions... What you want (IMHO is the following)
public class Order {
private long id;
private Set<Item> items;
}
public class Item {
private String name;
private long price;
}
Use a ResultSetExtractor to create the List<Order>.
public OrderResultSetExtractor implement ResultSetExtractor<List<Order>> {
public List<Order> extractData(ResultSet rs) throws SQLException, DataAccessException {
List<Order> orders = new ArrayList<Order>();
Order current = null;
while (rs.next()) {
long orderId = rs.getLong(1);
String itemName = rs.getString(2);
long price = rs.getLong(3);
if (current == null || current.getId() != orderId) {
current = new Order();
current.setId(orderId);
orders.add(current);
}
current.getItems().add(new Item(itemName, price));
}
return orders;
}
}
Something along these lines.

Categories