pagination in spring boot - java

I am beginner with Java and Spring Boot, I use Pagination on Spring Boot, with this code I return the list of users, which I must if I want to also return the number of pages?
I know that with getTotalPages() I can get the page count but how to return it?
#Service
public class UserService{
#Autowired
UserRepository userRepository;
public List<UserDto> findAll(PageRequest pageRequest){
Page<User> userPage = userRepository.findAll(pageRequest);
List<UserDTO> dtos = new ArrayList<UserDTO>();
//return userPage.getContent();
for (User u : userPage.toList() ) {
dtos.add(new UserDTO(u));
}
return dtos;
}
}

The most common implementation of the Page interface is provided by the PageImpl class, you can use like this:
import org.springframework.data.domain.PageImpl;
...
Page<UserDTO> pageResult = new PageImpl<>(dtos,
userPage.getPageable(),
userPage.getTotalElements());
return pageResult;
If you want, you can also use the .map() function of page result, it can be preferred according to the approach. https://stackoverflow.com/a/39037297/2039546

Related

How to limit the size of a Key in a Json response in spring boot?

image
This is my JSON repsonse, there is an attribute (key) in the response named as DATA which is list of lists and have 1000+ list in it.
I want to limit the size of data(attribute inside json) to 30 lists and 90 lists for two different controllers. I am not able to find out how to do it.
Extract business logic to a #Service class, and provide the required limit as a parameter from a Controller:
#Service
public class MyService {
MyDto createResponse(int limit) {
//.... slice list size, e.g.
List limitedData = data.subList(0, limit);
//...
}
}
#RestController
public class MyController1 {
#Autowired
MyService myService;
MyDto createResponse() {
return myService.createResponse(30);
}
}
#Controller
public class MyController2 {
#Autowired
MyService myService;
MyDto createResponse() {
return myService.createResponse(100);
}
}
You could look into Pageable. This interface can be used to paginate data. This way you can return a subset of 30 items, but also display how many other pages there are

Spring Data REST - Sort is always null in Pageable parameter in rest controller

I am using Spring Data REST with Spring Boot (1.5.17) and I have the following controller in my code.
#RestController
public class StudentController {
#RequestMapping(method = GET, value = "students/{id}/notifications")
public #ResponseBody
ResponseEntity<?> getStudentNotifications(#PathVariable Long id, PersistentEntityResourceAssembler resourceAssembler, Pageable page) {
Student student = studentRepo.findOne(id);
Page<Notification> notifications = notificationHandler.getUnreadNotifications(student.getId(),page);
return new ResponseEntity<>(pagedResourcesAssembler.toResource(notifications, resourceAssembler), HttpStatus.OK);
}
}
The controller works correctly except I cannot use the sort parameter like students/1/notifications?sort=createdDate,DESC. It always sorts by created date in ascending order.
I printed the Pageable parameter to console [number: 0, size 20, sort: null] and it shows that the sort attribute is always null.
So what am I doing wrong here?
EDIT
MVC configuration
#Configuration
public class SpringMvcConfig extends WebMvcConfigurerAdapter {
#Autowired
#Qualifier("repositoryExporterHandlerAdapter")
RequestMappingHandlerAdapter repositoryExporterHandlerAdapter;
#Override
public void addArgumentResolvers(
List<HandlerMethodArgumentResolver> argumentResolvers) {
List<HandlerMethodArgumentResolver> customArgumentResolvers = repositoryExporterHandlerAdapter.getCustomArgumentResolvers();
argumentResolvers.addAll(customArgumentResolvers);
}
#Override
public void addCorsMappings(CorsRegistry registry) {
}
}
The controller works correctly except I cannot use the sort parameter
like students/1/notifications?createdDate,DESC. It always sorts by
created date in ascending order.
if you are trying to pass the sort in
students/1/notifications?createdDate,DESC
it will not works because Pageable has sort params and you need to call like below.
students/1/notifications??sort=createdDate,DESC
Add following to addArgumentResolvers method
argumentResolvers.add(new PageableHandlerMethodArgumentResolver());

Jpa Hibernate #Query annotation with filters

I have an Angular based Application for Front End and Spring Boot for Back End.
My componaent had a variable that contains filters like that
search:any={
name:'',
surname:'',
address:'',
phone:'',
city:''
}
i pass it to my Api with Post Method like that:
getPeoplesByFilters(filter){
return this.http.post("http://localhost:8080/search", filter)
.map (resp => resp.json())
}
I receive this in my Spring Boot Controller by this method:
#RequestMapping(value="/search", method= RequestMethod.POST )
public Page<People> searchFilters(#RequestBody() People p,
#RequestParam(value="page", defaultValue="0") int page ,
#RequestParam(value="size", defaultValue="5") int size){
p.setName("%"+p.getName()+"%");
p.setSurname("%"+p.getSurname()+"%");
p.setAddress("%"+p.getAddress()+"%");
p.setPhone("%"+p.getPhone()+"%");
System.out.println(p.getName());
return poepleRepository.searchFilters( p , new PageRequest(page, size));
}
And in my Jpa Repository interface I have this query:
#Query("select p from poeple p where p.name like :#{#x.name} and p.surname like :#{#x.surname} and p.address like :#{#x.address} and p.phone like :#{#x.phone} and p.mobile like :#{#x.phone}")
public Page<People> searchFilters(#Param("x") People x, Pageable pageable);
But I don't have any result
I tried just with one field and i fill it in my front end App and it work
But when I fill just one field and let another empty i have zero result.
What's Wrong!
Thanks
My previous experience shows that using specifications is best for your case. Because it is more extendable and understandable. You can write specification like below.
public class Spec{
public static Specification<Person> filter(FilterParams filterParams) {
return (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if(filterParams.getName()!=null){
predicates.add(cb.equal(root.get("name"),filterParams.getName()));
}
if(filterParams.getSurname()!=null){
predicates.add(cb.equal(root.get("surname"),filterParams.getSurname()));
}
....
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
}
}
}
Then you can use like below:
#Service
public TestService{
#Autowired
PersonRepository personRepository;
void test(){
FilterParam filterParam = new FilterParam();
filterParam.setName("test);
...
personRepository.findAll(Spec.filter(filterParam));
}
}

Adding REST Web Service annotation in API class

I am using Spring Boot (1.5.3) to create a Spring REST Web Service. I have added spring-boot-starter-web as the only dependency (as per spring guide). Next I have created UserManagementService interface for my service class.
#RequestMapping("/usermanagement/v1")
public interface UserManagementService {
#RequestMapping(value = "/user/{id}/", method=RequestMethod.GET)
public UserTo getUserById(#PathVariable("id") long id);
#RequestMapping(value = "/users/", method=RequestMethod.GET)
public List<UserTo> getAllUsers();
}
And its implementation UserManagementServiceImpl
#RestController
public class UserManagementServiceImpl implements UserManagementService {
private Map<Integer, UserTo> users;
public UserManagementServiceImpl() {
users = new HashMap<>();
users.put(1, new UserTo(1, "Smantha Barnes"));
users.put(2, new UserTo(2, "Adam Bukowski"));
users.put(3, new UserTo(3, "Meera Nair"));
}
public UserTo getUserById(long id) {
return users.get(id);
}
public List<UserTo> getAllUsers() {
List<UserTo> usersList = new ArrayList<UserTo>(users.values());
return usersList;
}
}
I wanted to created a REST Web Service using Spring Boot with minimum configuration and thought this would work. But on accessing my the Web Service I am getting No Response. What I am missing?
Also, I have seen many projects where annotations are added to the interface rather than the implementation class. Which I think is better than annotating classes. It should work here, right?
As mentioned in the comments, not all annotations are supported on interfaces. The #PathVariable annotation for example won't work, so you'll have to put that on the implementation itself:
public UserTo getUserById(#PathVariable("id") long id) {
return users.get(id);
}
Additionally to that, you have a Map<Integer, UserTo>, but you're retrieving the users using a #PathVariable of type long. This won't work either, so either change the key of users to Long or the id parameter to int:
public UserTo getUserById(#PathVariable("id") int id) {
return users.get(id);
}
The reason for this is that 1L (long) is not the same as 1 (int). So retrieving a map entry wouldn't return any result for a long value.

Implementing custom methods of Spring Data repository and exposing them through REST

I'm trying to add custom methods to my Spring Data repository PersonRepository as described in 1.3 Custom implementations for Spring Data repositories and exposing these method through REST. The initial code is from Accessing JPA Data with REST sample, here is the code for added/modified classes:
interface PersonRepositoryCustom {
List<Person> findByFistName(String name);
}
class PersonRepositoryImpl implements PersonRepositoryCustom, InitializingBean {
#Override
public void afterPropertiesSet() throws Exception {
// initialization here
}
#Override
public List<Person> findByFistName(String name) {
// find the list of persons with the given firstname
}
}
#RepositoryRestResource(collectionResourceRel = "people", path = "people")
public interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
List<Person> findByLastName(#Param("name") String name);
}
When I run the application and visit http://localhost:8080/portfolio/search/, I get the following response body:
{
"_links" : {
"findByLastName" : {
"href" : "http://localhost:8080/people/search/findByLastName{?name}",
"templated" : true
}
}
}
Why findByFirstName is not exposed even if it is available in the PersonRepository interface?
Also, is there a way to dynamically/programmatically add respositories to be exposed via REST?
After two days, I have solved in this way.
Custom Repository Interface:
public interface PersonRepositoryCustom {
Page<Person> customFind(String param1, String param2, Pageable pageable);
}
Custom Repository Implementation
public class PersonRepositoryImpl implements PersonRepositoryCustom{
#Override
public Page<Person> customFind(String param1, String param2, Pageable pageable) {
// custom query by mongo template, entity manager...
}
}
Spring Data Repository:
#RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepository extends MongoRepository<Person, String>, PersonRepositoryCustom {
Page<Person> findByName(#Param("name") String name, Pageable pageable);
}
Bean Resource representation
public class PersonResource extends org.springframework.hateoas.Resource<Person>{
public PersonResource(Person content, Iterable<Link> links) {
super(content, links);
}
}
Resource Assembler
#Component
public class PersonResourceAssembler extends ResourceAssemblerSupport<Person, PersonResource> {
#Autowired
RepositoryEntityLinks repositoryEntityLinks;
public PersonResourceAssembler() {
super(PersonCustomSearchController.class, PersonResource.class);
}
#Override
public PersonResource toResource(Person person) {
Link personLink = repositoryEntityLinks.linkToSingleResource(Person.class, person.getId());
Link selfLink = new Link(personLink.getHref(), Link.REL_SELF);
return new PersonResource(person, Arrays.asList(selfLink, personLink));
}
}
Custom Spring MVC Controller
#BasePathAwareController
#RequestMapping("person/search")
public class PersonCustomSearchController implements ResourceProcessor<RepositorySearchesResource> {
#Autowired
PersonRepository personRepository;
#Autowired
PersonResourceAssembler personResourceAssembler;
#Autowired
private PagedResourcesAssembler<Person> pagedResourcesAssembler;
#RequestMapping(value="customFind", method=RequestMethod.GET)
public ResponseEntity<PagedResources> customFind(#RequestParam String param1, #RequestParam String param2, #PageableDefault Pageable pageable) {
Page personPage = personRepository.customFind(param1, param2, pageable);
PagedResources adminPagedResources = pagedResourcesAssembler.toResource(personPage, personResourceAssembler);
if (personPage.getContent()==null || personPage.getContent().isEmpty()){
EmbeddedWrappers wrappers = new EmbeddedWrappers(false);
EmbeddedWrapper wrapper = wrappers.emptyCollectionOf(Person.class);
List<EmbeddedWrapper> embedded = Collections.singletonList(wrapper);
adminPagedResources = new PagedResources(embedded, adminPagedResources.getMetadata(), adminPagedResources.getLinks());
}
return new ResponseEntity<PagedResources>(adminPagedResources, HttpStatus.OK);
}
#Override
public RepositorySearchesResource process(RepositorySearchesResource repositorySearchesResource) {
final String search = repositorySearchesResource.getId().getHref();
final Link customLink = new Link(search + "/customFind{?param1,param2,page,size,sort}").withRel("customFind");
repositorySearchesResource.add(customLink);
return repositorySearchesResource;
}
}
The reason these methods are not exposed is that you're basically free to implement whatever you want in custom repository methods and thus it's impossible to reason about the correct HTTP method to support for that particular resource.
In your case it might be fine to use a plain GET, in other cases it might have to be a POST as the execution of the method has side effects.
The current solution for this is to craft a custom controller to invoke the repository method.
For GET methods I have used the following approach:
create a dummy #Query method in the Repository (LogRepository.java)
create a custom interface with the same method declared (LogRepositoryCustom.java)
create an implementation of the custom interface (LogRepositoryImpl.java)
Using this approach I don't have to manage projections and resource assembling.
#RepositoryRestResource(collectionResourceRel = "log", path = "log")
public interface LogRepository extends PagingAndSortingRepository<Log, Long>,
LogRepositoryCustom {
//NOTE: This query is just a dummy query
#Query("select l from Log l where l.id=-1")
Page<Log> findAllFilter(#Param("options") String options,
#Param("eid") Long[] entityIds,
#Param("class") String cls,
Pageable pageable);
}
public interface LogRepositoryCustom {
Page<Log> findAllFilter(#Param("options") String options,
#Param("eid") Long[] entityIds,
#Param("class") String cls,
Pageable pageable);
}
In the implementation you are free to use the repository methods or going directly to the persistence layer:
public class LogRepositoryImpl implements LogRepositoryCustom{
#Autowired
EntityManager entityManager;
#Autowired
LogRepository logRepository;
#Override
public Page<Log> findAllFilter(
#Param("options") String options,
#Param( "eid") Long[] entityIds,
#Param( "class" ) String cls,
Pageable pageable) {
//Transform kendoui json options to java object
DataSourceRequest dataSourceRequest=null;
try {
dataSourceRequest = new ObjectMapper().readValue(options, DataSourceRequest.class);
} catch (IOException ex) {
throw new RuntimeException(ex);
}
Session s = entityManager.unwrap(Session.class);
Junction junction = null;
if (entityIds != null || cls != null) {
junction = Restrictions.conjunction();
if (entityIds != null && entityIds.length > 0) {
junction.add(Restrictions.in("entityId", entityIds));
}
if (cls != null) {
junction.add(Restrictions.eq("cls", cls));
}
}
return dataSourceRequest.toDataSourceResult(s, Log.class, junction);
}
The answer is that you haven't followed instructions. Your PersonRepository has to extend both PagingAndSortingRepository<Person, Long> AND PersonRepositoryCustomin order to achieve what you're after. See https://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/#repositories.custom-implementations
Another option we used as well is to implement a custom repository factory for your specific storage type.
You can extend from RepositoryFactoryBeanSupport, build your own PersistentEntityInformation and take care of CRUD ops in a default repo impl for your custom data storage type. See JpaRepositoryFactoryBean for example. You maybe need to implement about 10 classes in total but then it gets reusable.
Try using
class PersonRepositoryCustomImpl implements PersonRepositoryCustom, InitializingBean {
...
}
The implementing class name should be PersonRepositoryCustomImpl instead of PersonRepositoryImpl.

Categories

Resources