Get page number of specific item in spring data rest - java

I'm building a web application with spring-data-rest.
I want to show data from a big db-table in a paginated table in my frontend. The table loads asynchronically just the current page from the API. That works all fine and dandy out of the box by just having a repository like
public interface KeywordRepository extends JpaRepository<Keyword, String>, QuerydslPredicateExecutor<Keyword> {
}
Now I want to implement a functionality to jump in my paginated table to the correct page where a certain item is.
Problem is that I don't know on what page that specific item is.
I need some kind of endpoint to tell me the page number of a specific item (by id) according to the current filter- and sorting-parameters. Basically findPageOfItemById(Long id, Pageable pageable).
How can I get this?
Since the table is quite big, I don't want to have the whole content in memory.

For the sake of completeness, I'll answer my own question.
It works quite nice and without loading the whole list into memory as reqested.
But I was still hoping to find a little more spring-data-resty answer.
#GetMapping("/getPositionOfItem/{id}")
public long getPositionOfItem(#PathVariable String id, #QuerydslPredicate(root = SomeEntity.class) Predicate predicate, Pageable pageable) {
Iterable<SomeEntity> elements = someEntityRepository.findAll(predicate, pageable.getSort());
return findFirst(elements.iterator(), id);
}
private long findFirst(Iterator<SomeEntity> iterator, String id) {
long index = 0;
while (iterator.hasNext()) {
if (iterator.next().getId().equals(id)) {
return index;
}
index++;
}
return -1;
}
Note that I calculate the position of the item. To get the page of the element, we need to divide by the pagesize.

Related

I want to filter a list of object in groovy

This is my groovy class
Asset {
ObjectState objectState = ObjectState.CURRENT
String description
#NotEmpty(message = "*Please provide a asset name")
#Length(min = 2, max = 50, message = "*Asset name must have characters between 2 and 50")
String assetName
#DBRef
Company company
}
I want to find those assets of a particular company which contains "test" in assetName and description
Now i implemented the business logic like this
#Override
Page<Asset> fetchAssetsBySearchStringAndObjectStateAndCompany(Company company, Pageable pageable, String searchQuery) {
ObjectState objectState = ObjectState.CURRENT
if (!pageable) {
pageable = PageRequest.of(0, 10, Sort.Direction.DESC, "lastUpdated")
}
if (searchQuery) {
Page<Asset> assets = assetRepository.findAllByCompanyAndObjectState(company, pageable, objectState)
List<Asset> filteredAssets = []
assets.each {
if (it.assetName.contains(searchQuery) || it.description.contains(searchQuery)) {
filteredAssets.add(it)
}
}
return filteredAssets // i want this list in pagination object
} else {
return assetRepository.findAllByCompanyAndObjectState(company, pageable, objectState)
}
}
I find all the assets of a company -
Filter out the "test" string using groovy closure - assets.each { }
Now my filteredAssets contains required result but i want this in pagination object
Now my question is
1- Is this approach is efficient
2- How to convert filteredAssets in Page
I also tried to use mongo native query but i am unable to convert it to spring boot
#Query('''{
'company': ?0,
$or :
[
{'assetName' : { $regex: ?1, $options:'i' }},
{'description' : { $regex: ?1, $options:'i' }},
]
}
''')
Page<Asset> findAllByCompanyAndAssetNameOrDescription(Company company, String assetName, Pageable pageable)
I don't have a specific answer but my suggestion is that your first approach is not going to work at a higher level because you are filtering the results after the pagination has been performed by the initial query. So you will potentially end up with less than the desired page size (or even an empty result) even though there are valid results that could have been returned by the query.
In other words, to achieve this you really do need to use the second approach of constructing a native query that incorporates the filtering. To resolve why that is not working, you would need to post more information about the kind of errors you are seeing (or possibly put it as a separate question and close this one out).
EDIT: to answer the question more specifically - if you choose to persist with the approach, it looks to me like you can construct your own Page object by harnessing the Spring data PageImpl object which has a usable constructor from a list of elements. You can simply construct this object from your filtered list of elements - ie: instead of this:
...
return filteredAssets
Do this:
return new PageImpl(filteredAssets)
If you want to be more idiomatic with your groovy code I would also suggest to change the filtering operation to use findAll. In that case the code gets more compact:
return new PageImpl(assets.findAll { it.assetName.contains(searchQuery) })
Once again though I would caution that from looking at your problem I don't think it's going to have the result you actually want.

How to use multiple query constraints in Parse

I'm trying to sort through ParseObjects created by different users (clubadmins) and presenting the results based on "clubAdmin" in a recyclerview. For some reason I can't put my finger on, the query is not yielding any results to populate my view.
The line that seems to be the problem is where I query to sort using whereEqualTo().
Any help with what might be the problem is much appreciated.
The .toString() was the latest addition which also did not resolve the issue.
ParseQuery<Club> query = ParseQuery.getQuery(Club.class);
query.whereEqualTo("clubAdmin",ParseUser.getCurrentUser().toString());
query.orderByAscending("createdAt");
query.setLimit(MAX_CLUBS_TO_SHOW);
query.findInBackground(new FindCallback<Group>() {
#Override
public void done(List<Club> objects, ParseException e) {
if(e == null) {
for (Club c : objects) {
Club createdClub = new Club();
createdClub.setClubName(c.getClubName());
createdClub.setObjectId(c.getObjectId());
listItems.add(createdClub);
I was aiming for a list with the clubs created by the logged in user barring all others. Now I do not see anything in my view. If I comment out the line containing whereEqualTo(), I get all the clubs created within the app populating my view set to the limit.
What is the type of clubAdmin field? Is it a pointer to user class or a String holding the objectId of a user? If it is the second case, you have to get the objectId of the user like ParseUser.getCurrentUser().getObjectId();

Vaadin pass parameter to view

I am currently making a form for film database, using Vaadin. My problem is the whole situation with deep linking. I want to have a url with an id of movie in it, to make possible to get to the form of concrete movie.
localhost:port/filmView/idOfSelectedMovie
I am currently using pushState but there are couple of problems.
1) When I add string "Film/" before Id, the first selection works fine, however with following selections the url just keeps adding this.
http://localhost:8081/Film/Film/Film/Film/Film/4
2) The second option I tried was using just an id. The effect was that the url just keeps the host with port, and gets rid of ViewName
http://localhost:8801/4
I had already tried to use replaceState and Urifragment methods, the effect wasn't better at all.
the function handling selection of movie on the list
this.itemsList.addSelectionListener(selectionEvent -> {
if (selectionEvent.getFirstSelectedItem().isPresent()) {
Film selectedFilm = selectionEvent.getFirstSelectedItem().get();
this.setupForm(selectedFilm);
Page.getCurrent().pushState("Film/" + selectedFilm.getFilmId());
}
});
What you have to do while navigating between views this: let's say you have a View with a list of films, from you can select a film. When you select a film from that list, you move to the film View
getUI().getNavigator().navigateTo(FilmView + "/" + filmId); //let's say id 88
in this way, you will navigate to http://localhost:8081/Film/88
now, in your FilmView you can get and use this id, something like:
public class FilmView extends VerticalLayout implements View{
#Override
public void enter(ViewChangeEvent event) {
String yourPassedId = event.getParameters();
//do stuff with your id, for example loading from DB
}
}
In this way, you can reach every film you want with hard link, like http://localhost:8081/Film/88

Teleport to next player

I am working on a Spigot 1.8.9 plugin and am trying to add a feature when a staff right-clicks an item it teleports them to the next player that isn't in vanish and not themselves and if there aren't any it should return null.
On click I attempted to add all possible users to a list using
public static List<User> getPossibleUsers(User user){
List<User> result = new ArrayList<>();
for(User target : users)
if(!target.isVanished() && !user.getUUID().equals(target.getUUID()))
result.add(target);
return result;
}
The staff is also assigned an int called nextPlayer which is set to 0 when they login. Then when they click I add one to the int so next time they click it can get the next user.
private User getNextPlayer(User user) {
int next = user.nextPlayer;
List<User> users = getPossibleUsers(user);
if(users.size() == 0)
return null;
int current = 0;
for(User target : users) {
if(current == next){
return target;
}
current++;
}
user.nextPlayer = next;
}
The problem is I don't know how to make the getNextPlayer method correctly and make it efficient. I also would like to also to make it so once it hits the last player it loops back to the first player.
I'd suggest thinking about your problem entirely differently if you want it to be efficient, but efficiency really isn't a concern in this situation, so I'm opting to not pre-maturely optimize and instead work with the code you already have.
public static List<User> getPossibleUsers(User user){
List<User> result = new ArrayList<>();
for(User target : users)
if(!target.isVanished() && !user.getUUID().equals(target.getUUID()))
result.add(target);
return result;
}
This currently returns the Users in the same order, as they are defined on users.
This better have a natural sort order, otherwise you are going to have issues when people join / leave the server, as it will cause people to change their ordering in the list.
Now let's get back to first principals.
int next = user.nextPlayer;
Looks like you are storing the index of the player in the list you have already been in on the 'user'.
Once you have this, you can access that index directly from the list.
https://docs.oracle.com/javase/8/docs/api/java/util/List.html#get-int-
E get(int index)
So, doing users.get(next++); is all you need to do to 'fix' the code you have above. it increments next, and gets the user at that position (assuming the ordering is consistent, and hasn't changed) However, it may throw an exception if it's out of range of the list, so we wrap it in
if(next <= users.length) {
users.get(next++);
} else return null;
This will change it to returning null, if it would otherwise throw an exception.
BUT all of this still has a fatal flaw, that if the list is mutated between calls, that you could be potentially skipping or changing the order around.
A far better solution to this, is to instead cache the visited users, as well as the last visited user.
If the users are ordered, and you store the last visited user, instead of the index, you are storing data that is much more resilient to change, and more closely matches the behavior you want.
To more closely match your needs, you are asking that.
Generate a predictable, ordered list of users that don't include the admin, or anyone else that is vanished, to aid the admin in predicting where they are going.
Rotate through this list, by right clicking with a tool, (Note this is async, so all the state needs to be saved)
Ensure that all visited users are visited before repeating the sequence.
public class TeleportTooldata {
private ListIterator<UUID> cursor;
private List<UUID> cachedOrder;
public TeleportTooldata(List<UUID> applicableUsers) {
cachedOrder = applicableUsers;
}
#Nullable
public UUID next() {
if (!cursor.hasNext()) return null;
UUID next = cursor.next();
if (!cachedOrder.contains(next)) {
cachedOrder.add(next);
}
return next;
}
public void Update(List<UUID> applicableUsers) {
applicableUsers.removeAll(cachedOrder);
cachedOrder.addAll(applicableUsers);
}
}
public class TeleportToolUtil {
YourPluginUserRepo repo;
Map<User, TeleportTooldata> storage; //This could be a cache, make sure to remove if they log out, or maybe timed as well.
public List<UUID> getApplicableUsers() {
return repo.getOnlineUsers().stream()
.filter(User::isVanish)
.sorted(Comparator.comparing(User::getId)) // You can change the sort order
.map(User::getId)
.collect(Collectors.toList());
}
public void onToolUse(User user) {
TeleportTooldata data = storage.computeIfAbsent(user, x -> new TeleportTooldata(getApplicableUsers()));
UUID next = data.next();
if (next == null) {
data.Update(getApplicableUsers());
next = data.next();
if(next == null) {
storage.put(user, new TeleportTooldata(getApplicableUsers()));
next = data.next();
}
}
user.teleportTo(next);
}
}
A few changes.
We are now caching the ordering, so that you could conceptually also let the user go backwards through the list.
We are using ListIterator. ListIterator is an object that loops through lists, and stores the current position for you! Much like you were doing before, but without indexes.
We now have the possibility to update the data, in case a player joins late, or someone unvanishes they will be put at the back of the list if they are not already inside it.
when we run out of users, we attempt an update, if we are really out, we start again with a brand new list. (note this won't guarantee the same order every time (people will be 'properly' sorted when it updates if they were previously appended, but it's close enough for this usecase)
However! We still need to be mindful of memory leaks. using UUID's rather then players or users, means this class is very light weight, we should be pretty safe from memory leaks in the list of UUID AS LONG as the TeleportTooldata doesn't live too long.
You can replace the Map of TeleportTooldata with a cache (maybe from Guava?) to remove the data some time after the admin leaves the game.
If TeleportTooldata was expected to be long-lived, we would want to seriously consider removing UUID's from the history.
Also, not handled in my example, is the possibility of the users going offline after the order is cached.
To handle this, before teleporting the player, check if the uuid is online, otherwise go to the 'next' and follow all the same logic again.

Page<> vs Slice<> when to use which?

I've read in Spring Jpa Data documentation about two different types of objects when you 'page' your dynamic queries made out of repositories.
Page and Slice
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
So, I've tried to find some articles or anything talking about main difference and different usages of both, how performance changes and how sorting affercts both type of queries.
Does anyone has this type of knowledge, articles or some good source of information?
Page extends Slice and knows the total number of elements and pages available by triggering a count query. From the Spring Data JPA documentation:
A Page knows about the total number of elements and pages available. It does so by the infrastructure triggering a count query to calculate the overall number. As this might be expensive depending on the store used, Slice can be used as return instead. A Slice only knows about whether there’s a next Slice available which might be just sufficient when walking through a larger result set.
The main difference between Slice and Page is the latter provides non-trivial pagination details such as total number of records(getTotalElements()), total number of pages(getTotalPages()), and next-page availability status(hasNext()) that satisfies the query conditions, on the other hand, the former only provides pagination details such as next-page availability status(hasNext()) compared to its counterpart Page. Slice gives significant performance benefits when you deal with a colossal table with burgeoning records.
Let's dig deeper into its technical implementation of both variants.
Page
static class PagedExecution extends JpaQueryExecution {
#Override
protected Object doExecute(final AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) {
Query query = repositoryQuery.createQuery(accessor);
return PageableExecutionUtils.getPage(query.getResultList(), accessor.getPageable(),
() -> count(repositoryQuery, accessor));
}
private long count(AbstractJpaQuery repositoryQuery, JpaParametersParameterAccessor accessor) {
List<?> totals = repositoryQuery.createCountQuery(accessor).getResultList();
return (totals.size() == 1 ? CONVERSION_SERVICE.convert(totals.get(0), Long.class) : totals.size());
}
}
If you observe the above code snippet, PagedExecution#doExecute method underlyingly calls PagedExecution#count method to get the total number of records satisfying the condition.
Slice
static class SlicedExecution extends JpaQueryExecution {
#Override
protected Object doExecute(AbstractJpaQuery query, JpaParametersParameterAccessor accessor) {
Pageable pageable = accessor.getPageable();
Query createQuery = query.createQuery(accessor);
int pageSize = 0;
if (pageable.isPaged()) {
pageSize = pageable.getPageSize();
createQuery.setMaxResults(pageSize + 1);
}
List<Object> resultList = createQuery.getResultList();
boolean hasNext = pageable.isPaged() && resultList.size() > pageSize;
return new SliceImpl<>(hasNext ? resultList.subList(0, pageSize) : resultList, pageable, hasNext);
}
}
If you observe the above code snippet, to findout whether next set of results present or not (for hasNext()) the SlicedExecution#doExecute method always fetch extra one element(createQuery.setMaxResults(pageSize + 1)) and skip it based on the pageSize condition(hasNext ? resultList.subList(0, pageSize) : resultList).
Application:
Page
Use when UI/GUI expects to displays all the results at the initial stage of the search/query itself, with page numbers to traverse(ex., bankStatement with pagenumbers)
Slice
Use when UI/GUI expects to doesnot interested to show all the results at the initial stage of the search/query itself, but intent to show the records to traverse based on scrolling or next button click event (ex., facebook feed search)

Categories

Resources