how to get specific column in Spring JPA? - spring

I can GET below data by findAll() from db
[
{ id : 1,
name : "John",
age : "23",
sex : "male"
},
{ id : 2
...
]
And what I want to do is getting specific data from db like below.
[
{ id : 1,
name : "John"
},
{ id : : 1,
name : "Peter"
}
...
]
so I tried like this.
Repository
#Repository
public interface PersonDAO extends JpaRepository<Person, Integer> {
#Query(value ="select p.id , p.name from person p", nativeQuery = true)
Collection<Object> getPersonIdAndName();
}
Controller
#GetMapping ("/getPersonIdAndName")
public Collection<Object> getPerson() {
return personDAO.getPersonIdAndName();
}
but the result was like this (by postman)
[
[
1,
"John"
],
[
2,
"Peter"
]
....
]
I want a type key : value, not just value,
because I want to fetch this to React.js state.
how can I fix?
I'd appreciate for your help

You need to Use a Projection. Create a new Interface
public interface PersonProjection {
Integer getId();
String getName();
}
After that in you repository use the projection in the return type
#Repository
public interface PersonDAO extends JpaRepository<Person, Integer> {
#RestResource(exported = false)
Query("select p.id, p.name from Person as p")
Collection<PersonProjection> getPersonIdAndName();
}
Alternately, if you want to #Override the findAll() method you can create a customized repository. Here there is a GitHub repo example https://github.com/federicogatti/Spring-Custom-Repository-Example

Related

Update a list of objects in spring mongodb

In the below code I want to generate addrId automatically and show it in the Person document, but the addrId is not showing up in the document.
#Document
public class Person {
#Id
String id;
List<Address> addresses;
}
public class Address {
#Id
String addrId;
String street;
}
public class Example {
public Person createAddress(Person person, Address addr) {
Set<Address> addresses = new HashSet<>();
addresses.add(addr);
Query query = new Query();
query.addCriteria(Criteria.where("id").is(id));
Person person = mongoTemplate.findOne(query, Person.class);
person.setAddresses(addresses);
return mongoTemplate.save(person);
}
}
Expected document with addrId:
{
"_id" : ObjectId("592c7029aafef820f432c5f3"),
"_class" : "tutorial.mongodb.documents.Person",
"addresses" : [{
"addrId" : ObjectId("321c7029aafed220f432d321"),
"street" : "London street"
}]
}
but addrId is not getting displayed as seen in the below document:
{
"_id" : ObjectId("592c7029aafef820f432c5f3"),
"_class" : "tutorial.mongodb.documents.Person",
"addresses" : [{
"street" : "London street"
}]
}
This works for me as expected:
public String createAddress(Person person, Address addr) {
addr.setAddrId(String.valueOf(UUID.randomUUID()));
List<Address > addrs = person.getAddresses();
addrs.add(addr);
person.setAddresses(addrs);
mongoTemplate.save(person);
return addr.getAddrId();
}

Spring RepositoryRestController with excerptProjection

I have defined a #Projection for my Spring Data entity as described here
For the same reasons as described there. When I do GET request, everything is returned as expected. But when I do a POST request, the projection won't work. Following the example provided above, "Address" is shown as a URL under Links and is not exposed the way it is with GET request.
How to get it exposed the same way?
I created a class with #RepositoryRestController where I can catch the POST method. If I simply return the entity, it is without links. If I return it as a resource, the links are there, but "Address" is also a link. If I remove the GET method from my controller, the default behavior is as described above.
UPDATE
My entities are same as described here A, B and SuperClass except I don't have fetch defined in my #ManyToOne
My controller looks like this:
#RepositoryRestController
public class BRepositoryRestController {
private final BRepository bRepository;
public BRepositoryRestController(BRepository bRepository) {
this.bRepository = bRepository;
}
#RequestMapping(method = RequestMethod.POST, value = "/bs")
public
ResponseEntity<?> post(#RequestBody Resource<B> bResource) {
B b= bRepository.save(bResource.getContent());
BProjection result = bRepository.findById(b.getId());
return ResponseEntity.ok(new Resource<>(result));
}
}
And my repository looks like this:
#RepositoryRestResource(excerptProjection = BProjection.class)
public interface BRepository extends BaseRepository<B, Long> {
#EntityGraph(attributePaths = {"a"})
BProjection findById(Long id);
}
And my projection looks like this:
#Projection(types = B.class)
public interface BProjection extends SuperClassProjection {
A getA();
String getSomeData();
String getOtherData();
}
And SuperClassProjection looks like this:
#Projection(types = SuperClass.class)
public interface SuperClassProjection {
Long getId();
}
In the custom #RepositoryRestController POST method you should also return the projection. For example:
#Projection(name = "inlineAddress", types = { Person.class })
public interface InlineAddress {
String getFirstName();
String getLastName();
#Value("#{target.address}")
Address getAddress();
}
public interface PersonRepo extends JpaRepository<Person, Long> {
InlineAddress findById(Long personId);
}
#PostMapping
public ResponseEntity<?> post(...) {
//... posting a person
InlineAddress inlineAddress = bookRepo.findById(person.getId());
return ResponseEntity.ok(new Resource<>(inlineAddress));
}
UPDATE
I've corrected my code above and the code from the question:
#RepositoryRestResource(excerptProjection = BProjection.class)
public interface BRepository extends CrudRepository<B, Long> {
BProjection findById(Long id);
}
#Projection(types = B.class)
public interface BProjection {
#Value("#{target.a}")
A getA();
String getSomeData();
String getOtherData();
}
Then all works fine.
POST request body:
{
"name": "b1",
"someData": "someData1",
"otherData": "otherData",
"a": {
"name": "a1"
}
}
Response body:
{
"a": {
"name": "a1"
},
"someData": "someData1",
"otherData": "otherData",
"_links": {
"self": {
"href": "http://localhost:8080/api/bs/1{?projection}",
"templated": true
}
}
}
See working example

Adding more information to the HATEOAS response in Spring Boot Data Rest

I have the following REST controller.
#RepositoryRestController
#RequestMapping(value = "/booksCustom")
public class BooksController extends ResourceSupport {
#Autowired
public BooksService booksService;
#Autowired
private PagedResourcesAssembler<Books> booksAssembler;
#RequestMapping("/search")
public HttpEntity<PagedResources<Resource<Books>>> search(#RequestParam(value = "q", required = false) String query, #PageableDefault(page = 0, size = 20) Pageable pageable) {
pageable = new PageRequest(0, 20);
Page<Books> booksResult = BooksService.findBookText(query, pageable);
return new ResponseEntity<PagedResources<Resource<Books>>>(BooksAssembler.toResource(BooksResult), HttpStatus.OK);
}
My Page<Books> BooksResult = BooksService.findBookText(query, pageable); is backed by SolrCrudRepository. When it is run BookResult has several fields in it, the content field and several other fields, one being highlighted. Unfortunately the only thing I get back from the REST response is the data in the content field and the metadata information in the HATEOAS response (e.g. page information, links, etc.). What would be the proper way of adding the highlighted field to the response? I'm assuming I would need to modify the ResponseEntity, but unsure of the proper way.
Edit:
Model:
#SolrDocument(solrCoreName = "Books_Core")
public class Books {
#Field
private String id;
#Field
private String filename;
#Field("full_text")
private String fullText;
//Getters and setters omitted
...
}
When a search and the SolrRepository is called (e.g. BooksService.findBookText(query, pageable);) I get back these objects.
However, in my REST response I only see the "content". I would like to be able to add the "highlighted" object to the REST response. It just appears that HATEOAS is only sending the information in the "content" object (see below for the object).
{
"_embedded" : {
"solrBooks" : [ {
"filename" : "ABookName",
"fullText" : "ABook Text"
} ]
},
"_links" : {
"first" : {
"href" : "http://localhost:8080/booksCustom/search?q=ABook&page=0&size=20"
},
"self" : {
"href" : "http://localhost:8080/booksCustom/search?q=ABook"
},
"next" : {
"href" : "http://localhost:8080/booksCustom/search?q=ABook&page=0&size=20"
},
"last" : {
"href" : "http://localhost:8080/booksCustom/search?q=ABook&page=0&size=20"
}
},
"page" : {
"size" : 1,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
Just so you can get a full picture, this is the repository that is backing the BooksService. All the service does is call this SolrCrudRepository method.
public interface SolrBooksRepository extends SolrCrudRepository<Books, String> {
#Highlight(prefix = "<highlight>", postfix = "</highlight>", fragsize = 20, snipplets = 3)
HighlightPage<SolrTestDocuments> findBookText(#Param("fullText") String fullText, Pageable pageable);
}
Ok, here is how I did it:
I wrote mine HighlightPagedResources
public class HighlightPagedResources<R,T> extends PagedResources<R> {
private List<HighlightEntry<T>> phrases;
public HighlightPagedResources(Collection<R> content, PageMetadata metadata, List<HighlightEntry<T>> highlightPhrases, Link... links) {
super(content, metadata, links);
this.phrases = highlightPhrases;
}
#JsonProperty("highlighting")
public List<HighlightEntry<T>> getHighlightedPhrases() {
return phrases;
}
}
and HighlightPagedResourcesAssembler:
public class HighlightPagedResourcesAssembler<T> extends PagedResourcesAssembler<T> {
public HighlightPagedResourcesAssembler(HateoasPageableHandlerMethodArgumentResolver resolver, UriComponents baseUri) {
super(resolver, baseUri);
}
public <R extends ResourceSupport> HighlightPagedResources<R,T> toResource(HighlightPage<T> page, ResourceAssembler<T, R> assembler) {
final PagedResources<R> rs = super.toResource(page, assembler);
final Link[] links = new Link[rs.getLinks().size()];
return new HighlightPagedResources<R, T>(rs.getContent(), rs.getMetadata(), page.getHighlighted(), rs.getLinks().toArray(links));
}
}
I had to add to my spring RepositoryRestMvcConfiguration.java:
#Primary
#Bean
public HighlightPagedResourcesAssembler solrPagedResourcesAssembler() {
return new HighlightPagedResourcesAssembler<Object>(pageableResolver(), null);
}
In cotroller I had to change PagedResourcesAssembler for newly implemented one and also use new HighlightPagedResources in request method:
#Autowired
private HighlightPagedResourcesAssembler<Object> highlightPagedResourcesAssembler;
#RequestMapping(value = "/conversations/search", method = POST)
public HighlightPagedResources<PersistentEntityResource, Object> findAll(
#RequestBody ConversationSearch search,
#SortDefault(sort = FIELD_LATEST_SEGMENT_START_DATE_TIME, direction = DESC) Pageable pageable,
PersistentEntityResourceAssembler assembler) {
HighlightPage page = conversationRepository.findByConversationSearch(search, pageable);
return highlightPagedResourcesAssembler.toResource(page, assembler);
}
RESULT:
{
"_embedded": {
"conversations": [
..our stuff..
]
},
"_links": {
...as you know them...
},
"page": {
"size": 1,
"totalElements": 25,
"totalPages": 25,
"number": 0
},
"highlighting": [
{
"entity": {
"conversationId": "a2127d01-747e-4312-b230-01c63dacac5a",
...
},
"highlights": [
{
"field": {
"name": "textBody"
},
"snipplets": [
"Additional XXX License for YYY Servers DCL-2016-PO0422 \n  \n<em>hi</em> bodgan \n  \nwe urgently need the",
"Additional XXX License for YYY Servers DCL-2016-PO0422\n \n<em>hi</em> bodgan\n \nwe urgently need the permanent"
]
}
]
}
]
}
I was using Page<Books> instead of HighlightPage to create the response page. Page obviously doesn't contain content which was causing the highlighted portion to be truncated. I ended up creating a new page based off of HighlightPage and returning that as my result instead of Page.
#RepositoryRestController
#RequestMapping(value = "/booksCustom")
public class BooksController extends ResourceSupport {
#Autowired
public BooksService booksService;
#Autowired
private PagedResourcesAssembler<Books> booksAssembler;
#RequestMapping("/search")
public HttpEntity<PagedResources<Resource<HighlightPage>>> search(#RequestParam(value = "q", required = false) String query, #PageableDefault(page = 0, size = 20) Pageable pageable) {
HighlightPage solrBookResult = booksService.findBookText(query, pageable);
Page<Books> highlightedPages = new PageImpl(solrBookResult.getHighlighted(), pageable, solrBookResult.getTotalElements());
return new ResponseEntity<PagedResources<Resource<HighlightPage>>>(booksAssembler.toResource(highlightedPages), HttpStatus.OK);
}
Probably a better way of doing this, but I couldn't find anything that would do what I wanted it to do without having a change a ton of code. Hope this helps!

effective way to loopoever three array lists to compare values

I have two object of arraylist orderList, productList and one String arraylist customerIdList.I have ProductInfo POJO to be mapped with orderList and productList where cuustomerId should match.If I don't have order or productlist for given ProdId I should add standard Error and map to the ProductInfo Error.Here is what I am doing ...
public class ProductInfo {
private List<ProductDetails> products;
private List<Error> errors;
private String customerId;
}
public class ProductDetails {
private String customerId;
private Order order;
private Product product;
private List<Error> errors;
}
Sample result ...
{
"productInfo": {
"customer_id": "123",
"product_details": [
{
"customer_id": "123",
"order_details": null,
"product_details": {
"customer_id": "123"
"product_id" : "2343"
"product_name": "XYZ",
"product_type": "PQR"
...
},
"errors": [
"error_code":"6001",
"error_desc":"Failure in getting Order information from Order Service"
]
},
{
"order_details": {
"customer_id":"123"
"order_id": "3543454",
"order_date":"2016-10-12",
"order_status":"ordered"
},
"product_details": null,
"errors": [
"error_code":"6001",
"error_desc":"Failure in getting Product information from Product Service"
]
}
],
"system_errors":[]
}
}
Looping over ArrayList and Mapping
for(String customerId : customerIdList) {
for(Product product: productList) {
for(SOrder ordr: orderList) {
if(customerId.equals(product.getCustomerId()) && customerId.equals(Order.getCustomerId()) ) {
ModelMapper mapper = new ModelMapper();
Order order = mapper.map(ordr, Order.class));
productDetails.setOrder(order);
//mapping to ProductInfo
productDetailsList.add(productDetails);
}
}
}
}
I want to know if there is any better way of doing this and also I am using ModelMapper to map SOrder to Order POJO andother POJOs would like to know if there is any other efficient model mapper available.Thanks.
you can create maps from productList and orderList with customerId as key
Map<String, Product> productMap = productList.stream().collect(Collectors.toMap(p -> p.getCustomerId(), p -> p));
Map<String, Product> orderMap = orderList.stream().collect(Collectors.toMap(o -> o.getCustomerId(), o -> o));
then with just one loop you can check if there is product and order for that customer id
for(String customerId : customerIdList) {
if (productMap.containsKey(customerId) && orderMap.containsKey(customerId)) {
//do your mapping stuff here
}
}

Spring: one JPA model, many JSON respresentations

I'm writing a RESTful web service using Spring/JPA. There's a JPA model which is exposed through the web service. The 'Course' model is quite spacious - it actually is composed of several sets of data: general information, pricing details and some caches.
The issue I encounter is the inability to issue different JSON representations using the same JPA model.
The in first case I only need to return general_info set of data for courses:
GET /api/courses/general_info
in the second case I would like to return pricing set of data only:
GET /api/courses/pricing
I see the following ways to solve this, not in particular order:
To create CourseGeneralInfo and CoursePricing JPA models using
the origin database table as a source. CourseGeneralInfo model
would have its own set of fields and CoursePricing would have its
own ones. This way I would have the JSON I need.
To refactor the stuff out of the Course model/table to have
GeneralInfo and PricingDetails to be separate JPA entities. Ok, this sounds like the best one (imo) though the database is legacy and it is not something I can change easily...
Leverage some sort of DTO and Spring Mappers to convert the JPA model to representation needed in any particular case.
What approach would you recommend?
I was just reading about some really nifty features in Spring 4.1, which allow you to use different views via annotations.
from: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring
public class View {
interface Summary {}
}
public class User {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private String firstname;
#JsonView(View.Summary.class)
private String lastname;
private String email;
private String address;
private String postalCode;
private String city;
private String country;
}
public class Message {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private LocalDate created;
#JsonView(View.Summary.class)
private String title;
#JsonView(View.Summary.class)
private User author;
private List<User> recipients;
private String body;
}
Thanks to Spring MVC #JsonView support, it is possible to choose, on a per handler method basis, which field should be serialized:
#RestController
public class MessageController {
#Autowired
private MessageService messageService;
#JsonView(View.Summary.class)
#RequestMapping("/")
public List<Message> getAllMessages() {
return messageService.getAll();
}
#RequestMapping("/{id}")
public Message getMessage(#PathVariable Long id) {
return messageService.get(id);
}
}
In this example, if all messages are retrieved, only the most important fields are serialized thanks to the getAllMessages() method annotated with #JsonView(View.Summary.class):
[ {
"id" : 1,
"created" : "2014-11-14",
"title" : "Info",
"author" : {
"id" : 1,
"firstname" : "Brian",
"lastname" : "Clozel"
}
}, {
"id" : 2,
"created" : "2014-11-14",
"title" : "Warning",
"author" : {
"id" : 2,
"firstname" : "Stéphane",
"lastname" : "Nicoll"
}
}, {
"id" : 3,
"created" : "2014-11-14",
"title" : "Alert",
"author" : {
"id" : 3,
"firstname" : "Rossen",
"lastname" : "Stoyanchev"
}
} ]
In Spring MVC default configuration, MapperFeature.DEFAULT_VIEW_INCLUSION is set to false. That means that when enabling a JSON View, non annotated fields or properties like body or recipients are not serialized.
When a specific Message is retrieved using the getMessage() handler method (no JSON View specified), all fields are serialized as expected:
{
"id" : 1,
"created" : "2014-11-14",
"title" : "Info",
"body" : "This is an information message",
"author" : {
"id" : 1,
"firstname" : "Brian",
"lastname" : "Clozel",
"email" : "bclozel#pivotal.io",
"address" : "1 Jaures street",
"postalCode" : "69003",
"city" : "Lyon",
"country" : "France"
},
"recipients" : [ {
"id" : 2,
"firstname" : "Stéphane",
"lastname" : "Nicoll",
"email" : "snicoll#pivotal.io",
"address" : "42 Obama street",
"postalCode" : "1000",
"city" : "Brussel",
"country" : "Belgium"
}, {
"id" : 3,
"firstname" : "Rossen",
"lastname" : "Stoyanchev",
"email" : "rstoyanchev#pivotal.io",
"address" : "3 Warren street",
"postalCode" : "10011",
"city" : "New York",
"country" : "USA"
} ]
}
Only one class or interface can be specified with the #JsonView annotation, but you can use inheritance to represent JSON View hierarchies (if a field is part of a JSON View, it will be also part of parent view). For example, this handler method will serialize fields annotated with #JsonView(View.Summary.class) and #JsonView(View.SummaryWithRecipients.class):
public class View {
interface Summary {}
interface SummaryWithRecipients extends Summary {}
}
public class Message {
#JsonView(View.Summary.class)
private Long id;
#JsonView(View.Summary.class)
private LocalDate created;
#JsonView(View.Summary.class)
private String title;
#JsonView(View.Summary.class)
private User author;
#JsonView(View.SummaryWithRecipients.class)
private List<User> recipients;
private String body;
}
#RestController
public class MessageController {
#Autowired
private MessageService messageService;
#JsonView(View.SummaryWithRecipients.class)
#RequestMapping("/with-recipients")
public List<Message> getAllMessagesWithRecipients() {
return messageService.getAll();
}
}
In Spring Data REST 2.1 there is a new mechanism for this purpose - Projections (It's now part of spring-data-commons).
You'll need to define interface, containing exactly exposed fields:
#Projection(name = "summary", types = Course.class)
interface CourseGeneralInfo {
GeneralInfo getInfo();
}
After that Spring will be able to find it automagically in your source, and you could make requests to your existing endpoints, like this:
GET /api/courses?projection=general_info
Based on
https://spring.io/blog/2014/05/21/what-s-new-in-spring-data-dijkstra
Spring sample project with projections:
https://github.com/spring-projects/spring-data-examples/tree/master/rest/projections

Resources