I'm trying to use DTOs in a Spring project to decouple business and presentation but I'm having problems while retrieving data from the Spring Data repository. Here I have a sample code:
public Page<UserDto> findAll(int pageIndex) {
return userRepository.findAll(createPageable(pageIndex)); // Page<User>
}
As you can see, I'm trying to return a page of UserDto but I'm getting a page of User.
How can I do this?
You can do it like this :
public Page<UserDto> findAll(Pageable p) {
Page<User> page = userRepository.findAll(p); // Page<User>
return new PageImpl<UserDto>(UserConverter.convert(page.getContent()), p, page.getTotalElements());
}
Since Spring-Data 1.10 you can use the Page#map function:
public Page<UserDto> findAll(Pageable p) {
return userRepository.findAll(p).map(UserConverter::convert);
}
See Spring Data. The UserConverter#convert method takes a Person and converts it to PersonDto.
public PersonDto convert(Person person) {
return new PersonDto(person.getXyz1(), person.getXyz2);
}
public Page<PersonDto> findAllPageableOrderBylastName(Pageable pageable) {
Page<Person> personPage = personRepository.findAllByLastNameIsNotNullOrderByLastName(pageable);
int totalElements = (int) personPage.getTotalElements();
return new PageImpl<PersonDto>(personPage.getContents()
.stream()
.map(person -> new PersonDto(
person.getId(),
person.getFirstName(),
person.getLastName(),
person.getNumberMobil(),
person.getPresentPosition()))
.collect(Collectors.toList()), pageable, totalElements);
}
I just started programming. I realized so. Working. :)
Related
I'm creating Spring Boot HATEOAS REST application. Code below shows how am I adding links, while GET request is send for specific Employee. I'm using RepresentationModelAssembler toModel function. There's also toCollectionModel function to Override, which I would like to use to convert List<Employees> to CollectionModel. -> This will be returned in /Employees/all endpoint.
And I dunno how to do that. So what I need is to pass List<Employees>, then all list elements needs to be processed by toModel functions, and then, like in toModel function I need possibility to add more links to it -> links to entire new collection (not individual items).
Looking forward for your answers!
#Component
public class EmployeeModelAssembler implements RepresentationModelAssembler<Employee, EntityModel<Employee>> {
#Override
public EntityModel<Employee> toModel(Employee employee) {
EntityModel<Employee> employeeEntityModel = EntityModel.of(employee);
Link selfLink = linkTo(methodOn(EmployeeController.class).getEmployeeById(employee.getId())).withSelfRel();
employeeEntityModel.add(selfLink);
return employeeEntityModel;
}
#Override
public CollectionModel<EntityModel<Employee>> toCollectionModel(Iterable<? extends Employee> entities) {
?? ?? ??
}
}
You can use something like this:
#GetMapping(produces = { "application/hal+json" })
public CollectionModel<Customer> getAllCustomers() {
List<Customer> allCustomers = customerService.allCustomers();
for (Customer customer : allCustomers) {
String customerId = customer.getCustomerId();
Link selfLink = linkTo(CustomerController.class).slash(customerId).withSelfRel();
customer.add(selfLink);
if (orderService.getAllOrdersForCustomer(customerId).size() > 0) {
Link ordersLink = linkTo(methodOn(CustomerController.class)
.getOrdersForCustomer(customerId)).withRel("allOrders");
customer.add(ordersLink);
}
}
Link link = linkTo(CustomerController.class).withSelfRel();
CollectionModel<Customer> result = CollectionModel.of(allCustomers, link);
return result;
}
Visit https://www.baeldung.com/spring-hateoas-tutorial#springhateoasinaction for detailed explanation
I have a task to insert entity through r2dbc database client, and convert the result (map) into the entity.
I want to do it this way:
databaseClient.insert().into(ApplicationData.class)
.using(applicationData)
.map(converter.populateIdIfNecessary(applicationData))
.first();
But the problem is converter entity MappingR2dbcConverter isn't created by spring.
So, I decided to create it myself:
#Bean
public MappingR2dbcConverter converter(RelationalMappingContext mappingContext,
R2dbcCustomConversions r2dbcCustomConversions)....
My question, is it correct way to convert result map into entity?
R2dbc DatabaseClient will be part of Spring framework 5.3, see My example for Spring 5.3 M2.
public static final BiFunction<Row, RowMetadata, Post> MAPPING_FUNCTION = (row, rowMetaData) -> Post.builder()
.id(row.get("id", UUID.class))
.title(row.get("title", String.class))
.content(row.get("content", String.class))
.status(row.get("status", Post.Status.class))
.metadata(row.get("metadata", Json.class))
.createdAt(row.get("created_at", LocalDateTime.class))
.build();
public Flux<Post> findAll() {
return this.databaseClient
.sql("SELECT * FROM posts")
.filter((statement, executeFunction) -> statement.fetchSize(10).execute())
.map(MAPPING_FUNCTION)
.all();
}
public Mono<Post> findById(UUID id) {
return this.databaseClient
.sql("SELECT * FROM posts WHERE id=:id")
.bind("id", id)
.map(MAPPING_FUNCTION)
.one();
}
I am trying to create a Spring Boot application, where I need to fetch the records from the database and make a call to the REST API for each record fetched from the database. But instead of fetching all the records at once I want to retrieve in batch sizes, say 10, make the rest call for them and then fetch another 10 and do the same, until last record. I am using spring-data-jpa. How can I achieve that?
P.S.: Its a multi-threaded call and DB is the Amazon DynamoDB
My code till now:
Controller:
//Multi Threaded Call to rest
#GetMapping("/myApp-multithread")
public String getQuoteOnSepThread() throws InterruptedException, ExecutionException {
System.out.println("#################################################Multi Threaded Post Call######################");
ExecutorService executor= Executors.newFixedThreadPool(10);
List<Future<String>> myFutureList= new ArrayList<Future<String>>();
long startTime=System.currentTimeMillis()/1000;
***//Here instead of fetching and calling Mycallable for each one, I want
// to do it in batches of 10***
Iterable<Customer> customerIterable=repo.findAll();
List<Customer> customers=new ArrayList<Customer>();
customerIterable.forEach(customers::add);
for(Customer c:customers) {
MyCallable myCallable= new MyCallable(restTemplate, c);
Future<String> future= executor.submit(myCallable);
myFutureList.add(future);
}
for(Future<String> fut:myFutureList) {
fut.get();
}
executor.shutdown();
long timeElapsed= (System.currentTimeMillis()/1000)-startTime;
System.out.println("->>>>>>>>>>>>>>>Time Elapsed In Multi Threaded Post Call<<<<<<<<<<<<<<<-"+timeElapsed);
return "Success";
}
My Callable:
#Scope("prototype")
#Component
public class MyCallable implements Callable<String>{
private RestTemplate restTemplate;
private Customer c;
public MyCallable(RestTemplate rt, Customer cust) {
this.restTemplate = rt;
this.c = cust;
}
#Override
public String call() throws Exception {
System.out.println("Customer no"+ c.getId() +"On thread Number"+Thread.currentThread().getId());
restTemplate.postForObject("http://localhost:3000/save", c, String.class);
return "Done";
}
}
How can I achieve that?
Spring Data JPA offers Page and Slice to handle this case (see PagingAndSortingRepository and Pageable)
public interface PagingAndSortingRepository<T, ID extends Serializable>
extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
You can create Pageable request as:
Pageable firstPageWithTwoElements = PageRequest.of(0, 2);
and pass it to your custom repository (which should extend PagingAndSortingRepository):
Page<T> pageResult = customRepository.findAll(firstPageWithTwoElements);
Spring Security 5 provides a ReactiveSecurityContextHolder to fetch the SecurityContext from a Reactive context, but when I want to implement AuditorAware and get audition work automatically, but it does not work. Currently I can not find a Reactive variant for AuditorAware.
#Bean
public AuditorAware<Username> auditor() {
return () -> ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.log()
.filter(a -> a != null && a.isAuthenticated())
.map(Authentication::getPrincipal)
.cast(UserDetails.class)
.map(auth -> new Username(auth.getName()))
.switchIfEmpty(Mono.empty())
.blockOptional();
}
I have added #EnableMongoAuduting on my boot Application class.
On the Mongo document class. I added audition related annotations.
#CreatedDate
private LocalDateTime createdDate;
#CreatedBy
private Username author;
When I added a post, the createdDate is filled, but author is null.
{"id":"5a49ccdb9222971f40a4ada1","title":"my first post","content":"content of my first post","createdDate":"2018-01-01T13:53:31.234","author":null}
The complete codes is here, based on Spring Boot 2.0.0.M7.
Update: Spring Boot 2.4.0-M2/Spring Data Common 2.4.0-M2/Spring Data Mongo 3.1.0-M2 includes a ReactiveAuditorAware, Check this new sample, Note: use #EnableReactiveMongoAuditing to activiate it.
I am posting another solution which counts with input id to support update operations:
#Component
#RequiredArgsConstructor
public class AuditCallback implements ReactiveBeforeConvertCallback<AuditableEntity> {
private final ReactiveMongoTemplate mongoTemplate;
private Mono<?> exists(Object id, Class<?> entityClass) {
if (id == null) {
return Mono.empty();
}
return mongoTemplate.findById(id, entityClass);
}
#Override
public Publisher<AuditableEntity> onBeforeConvert(AuditableEntity entity, String collection) {
var securityContext = ReactiveSecurityContextHolder.getContext();
return securityContext
.zipWith(exists(entity.getId(), entity.getClass()))
.map(tuple2 -> {
var auditableEntity = (AuditableEntity) tuple2.getT2();
auditableEntity.setLastModifiedBy(tuple2.getT1().getAuthentication().getName());
auditableEntity.setLastModifiedDate(Instant.now());
return auditableEntity;
})
.switchIfEmpty(Mono.zip(securityContext, Mono.just(entity))
.map(tuple2 -> {
var auditableEntity = (AuditableEntity) tuple2.getT2();
String principal = tuple2.getT1().getAuthentication().getName();
Instant now = Instant.now();
auditableEntity.setLastModifiedBy(principal);
auditableEntity.setCreatedBy(principal);
auditableEntity.setLastModifiedDate(now);
auditableEntity.setCreatedDate(now);
return auditableEntity;
}));
}
}
Deprecated: see the update solution in the original post
Before the official reactive AuditAware is provided, there is an alternative to implement these via Spring Data Mongo specific ReactiveBeforeConvertCallback.
Do not use #EnableMongoAuditing
Implement your own ReactiveBeforeConvertCallback, here I use a PersistentEntity interface for those entities that need to be audited.
public class PersistentEntityCallback implements ReactiveBeforeConvertCallback<PersistentEntity> {
#Override
public Publisher<PersistentEntity> onBeforeConvert(PersistentEntity entity, String collection) {
var user = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(it -> it != null && it.isAuthenticated())
.map(Authentication::getPrincipal)
.cast(UserDetails.class)
.map(userDetails -> new Username(userDetails.getUsername()))
.switchIfEmpty(Mono.empty());
var currentTime = LocalDateTime.now();
if (entity.getId() == null) {
entity.setCreatedDate(currentTime);
}
entity.setLastModifiedDate(currentTime);
return user
.map(u -> {
if (entity.getId() == null) {
entity.setCreatedBy(u);
}
entity.setLastModifiedBy(u);
return entity;
}
)
.defaultIfEmpty(entity);
}
}
Check the complete codes here.
To have createdBy attribute filled, you need to link your auditorAware bean with the annotation #EnableMongoAuditing
In your MongoConfig class, define your bean :
#Bean(name = "auditorAware")
public AuditorAware<String> auditor() {
....
}
and use it in the annotation :
#Configuration
#EnableMongoAuditing(auditorAwareRef="auditorAware")
class MongoConfig {
....
}
We are building an API for our service and we would like to leverage Spring Data Rest as much as possible.
This API and the new model underneath will substitute a legacy API (and it's old model) that we still need to support.
Our idea is to build an "adapter" web app that replicates the structure of the old api and serve the old model using some internal transformations.
Also the old api is using Spring Data Rest, so here the idea:
build a repository implementation that instead of querying a database will query our brand new API, retrieve the new model, apply some transformations, and return the old model.
Unfortunately, even if I'm annotating the repository implementation with the #Repository annotation, Spring is not exposing the repository in the API.
I'm not sure if this is actually something possible to do or is just a matter of me not implementing some core functionalities.
What I would like to avoid is reimplement all spring data rest methods manually in a controller.
Here my Repository class
// Method are not implemented, this is just the backbone
#Repository
public class SampleRespositoryImpl implements ReadOnlyRepository<OldSample, String> {
NewApiClient client;
public SampleRespositoryImpl(NewApiClient client) {
this.client = client;
}
#Override
public OldSample findOne(String accession) {
NewSample newSample = client.fetch(accession)
OldSample oldSample = //apply transformation to newSample
return oldSample;
}
#Override
public boolean exists(String accession) {
return client.fetch(accession) != null;
}
#Override
public Iterable<OldSample> findAll() {
return new ArrayList<>();
}
#Override
public Iterable<OldSample> findAll(Iterable<String> var1) {
return new ArrayList<>();
}
#Override
public long count() {
return 0;
}
#Override
public Iterable<OldSample> findAll(Sort var1) {
return new ArrayList<>();
}
#Override
public Page<OldSample> findAll(Pageable var1) {
List<OldSample> OldSampleList = new ArrayList<>();
Page<OldSample> page = new PageImpl<>(OldSampleList);
return page;
}
}
Here what I would like to get back when I hit the api root (http://localhost:8080/)
{
"_links": {
"samples": {
"href": "http://localhost:8080/samples{?page,size,sort}
}
}
}
Someone else linked me to another answer in StackOverflow available here as possible duplication.
Reading through that answer, I decided that is too much effort to follow this path for our needs, so I'm more oriented to create a custom controller to expose necessary methods.
This solution was reported by Kevin as answer to Implementing methods of Spring Data repository and exposing them through REST