resource link rel with id spring data rest - spring

Having following Entities and Repositories. I could'nt manage to put the id on my relationship. Thanks in advance for your help
Related artifacts from my build.gradle (using Spring Boot version 1.5.4.RELEASE)
compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('org.springframework.boot:spring-boot-starter-data-rest')
Entities
Store
#Entity
#Table(name = "store")
class Store {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
long id
String description
#ManyToOne(optional=false, fetch = FetchType.EAGER)
Province province
}
Province
#Entity
#Table(name = "province")
class Province {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
long id
#NotNull
String name
}
Repositories
Store
#RepositoryRestResource(collectionResourceRel = "stores", path = "stores")
interface StoreRepository extends PagingAndSortingRepository<Store, Long> {
}
Province
#RepositoryRestResource(collectionResourceRel = "provinces", path = "provinces")
interface ProvinceRepository extends PagingAndSortingRepository<Province, Long> {
}
Expected Result
I'm expecting this result, please note link on Province
{
"stores": [
{
"id": 1,
"description": "desc1",
"_links": {
"self": {
"href": "http://localhost:8080/stores/1"
},
"store": {
"href": "http://localhost:8080/stores/1"
},
"province": {
"href": "http://localhost:8080/stores/1/province/1" ==> Expecting the url with provinceID since its many to one
}
}
}
]
//Simplified for simplicity sake
}
Actual Result
Not having the Province Id in href
{
"stores": [
{
"id": 1,
"description": "desc1",
"_links": {
"self": {
"href": "http://localhost:8080/stores/1"
},
"store": {
"href": "http://localhost:8080/stores/1"
},
"province": {
"href": "http://localhost:8080/stores/1/province" ==> //NO ID!
}
}
}
]
//Simplified for simplicity sake
}
Basically im expecting
this
"province": {
"href": "http://localhost:8080/stores/1/province/1" ==> Expecting the url with provinceID since its many to one
}
instead of this
"province": {
"href": "http://localhost:8080/stores/1/province" //NO province ID
}
Edit Jun 21 at 11:54
I've changed FetchType.LAZY to EAGER on Store due to an error when trying to do
"http://localhost:8080/stores/1/province/1"
GOT
"org.springframework.http.converter.HttpMessageNotWritableException",
"message": "Could not write JSON: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer

Finally i've found two possibles ways to solve this one with Projections and the other using ResourceProcessor. Hope this help somebody.
Projections.
#Projection(name="storeProjection", types={Store.class})
interface StoreProjection {
long getId();
String getDescription();
String getPhoneNumber();
StoreStatus getStatus();
#Value("#{target.province.id}")
Long getProvinceId();
#Value("#{target.province.name}")
String getProvinceName();
}
GET http://localhost:8080/stores?projection=storeProjection
JSON result
{
//Store data...
"provinceId": "1"
"provinceName": "Prov1"
//Links,etc data
}
ResourceProcessor in order to add a new link with desired info
#Configuration
class ResourcesProcessors {
#Autowired
EntityLinks entityLinks
#Bean
public ResourceProcessor<Resource<Store>> storeProcessor() {
return new ResourceProcessor<Resource<Store>>() {
#Override
public Resource<Store> process(Resource<Store> resource) { //Este punto solo se agregan nuevos links
Store store = resource.getContent()
Link link = entityLinks.linkToSingleResource(Province.class, store.province.id);
resource.add(link);
return resource;
}
};
}
}

Related

springboot mongodb crud update only changed fields

Hello i have springboot with mongodb (spring-boot-starter-data-mongodb)
My problem is if I send only one or only the fields I want to change so the other values are set to null. I found something on the internet like #DynamicUpdate but not working on mongodb can you help me with this problem. I'm a beginner, I don't know how to help and it's quite important for me, if you need more code or more information, I'll write in the comment. I hope I have described the problem sufficiently. :)
MY POJO:
#Data
#Getter
#Setter
#NoArgsConstructor
public class Person {
#Id
private String id;
private String firstName;
private String lastName;
private boolean enabled;
private String note;
Repo
#Repository
public interface PersonRepository extends MongoRepository <Person, String> {
}
i have this call
#PutMapping("/{id}")
#ResponseBody
public void UpdatePerson (#PathVariable String id , #RequestBody Person person) {
personRepository.save(person);
}
#GetMapping(path = "/{id}")
public Person getPersonByid(#PathVariable String id ){
return personRepository.findById(id).orElseThrow(PersonNotFound::new);
}
sample:
get call before update :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Rambo",
"lastName": "Norris",
"enabled": true,
"note": "hello this is my first note from you",
}
put call :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Chuck"
}
get call after update :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Chuck",
"lastName": null,
"enabled": false,
"note": null,
}
what I would like
get call before update :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Rambo",
"lastName": "Norris",
"enabled": true,
"note": "hello this is my first note from you",
}
put call :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Chuck"
}
get call after update :
{
"id": "5fc940dc6d368377561dbb02",
"firstName": "Chuck",
"lastName": "Norris",
"enabled": true,
"note": "hello this is my first note from you",
}
You are inserting a new collection instead of updating. First, you need to get the old value from mongodb, then you need to update the collection, then save to DB.
Use the below code in #putmapping.
#PutMapping("/{id}")
#ResponseBody
public void UpdatePerson (#PathVariable String id , #RequestBody Person person) {
Person personFromDB = personRepository.findById(person.getId());
personFromDB.setFirstName(person.getFirstName());
personRepository.save(personFromDB);
}
Try updating like this
#PutMapping("/{id}")
public ResponseEntity<Person> UpdatePerson (#PathVariable String id , #RequestBody
Person person) {
Optional<Person> personData = personRepository.findById(id);
if (personData.isPresent()) {
Person _tutorial = personData.get();
if(!StringUtils.isEmpty(person.getFirstName())) {
_tutorial.setFirstName(person.getFirstName());
}
if(!StringUtils.isEmpty(person.getLastName())) {
_tutorial.setLastName(person.getLastName());
}
if(!StringUtils.isEmpty(person.getNote())) {
_tutorial.setNote(person.getNote());
}
if(!StringUtils.isEmpty(tutorial.isEnabled())) {
_tutorial.setEnabled(tutorial.isEnabled());
}
return new ResponseEntity<>(repo.save(_tutorial), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}

How to persist and serve image files for fullstack webapp (Spring Boot JPA, REST, Vue.js)?

I'm trying to build a movie database single page application with vue.js and spring-boot. I use spring and jpa to easily map my objects to a database schema.
I would now like to add a movie poster for each movie but I am unsure about where to put the image files (database or filesystem and where exactly or rather what kind of folder structure and filenames i should give them)
Movie.java
#Data
#Entity
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String director;
private Date releaseDate;
#ManyToMany
#JoinTable(
name = "MOVIE_GENRES",
joinColumns = #JoinColumn(name = "MOVIE_ID"),
inverseJoinColumns = #JoinColumn(name = "GENRE_ID"))
private Set<Genre> genres = new HashSet<>();
// TODO how to rename value column (CAST -> ACTOR_ID)
#OneToMany
#MapKeyColumn(name = "ACTOR_ROLE")
private Map<String, Actor> cast = new HashMap<>();
// TODO add fields
public Movie(String title) {
this.title = title;
}
public void addActor(String role, Actor actor) {
cast.put(role, actor);
}
public void removeActor(String role) {
cast.remove(role);
}
public void addGenre(Genre genre) {
genres.add(genre);
}
public void removeGenre(Genre genre) {
genres.remove(genre);
}
}
Result from GET /api/movies/1
{
"id": 1,
"title": "The Matrix",
"director": null,
"releaseDate": null,
"genres": [
{
"id": 4,
"name": "Science Fiction"
},
{
"id": 1,
"name": "Action"
}
],
"cast": {
"Agent Smith": {
"id": 3,
"name": "Hugo Weaving",
"gender": "MALE",
"dateOfBirth": "1960-04-04"
},
"Morpheus": {
"id": 2,
"name": "Laurence Fishburne",
"gender": "MALE",
"dateOfBirth": "1961-07-30"
},
"Thomas A. Anderson / Neo": {
"id": 1,
"name": "Keanu Reeves",
"gender": "MALE",
"dateOfBirth": "1964-09-02"
}
}
}
If I load the image files into the Movie class as a byte array, they will get persistet into the database. This would mean that the whole byte array would be transfered over JSON if i GET /api/movies... for EVERY MOVIE. This sounds horrible to be honest.
Now I don't really know where I should put the image files, how to reference them in my movie class (as a path? an absolute path? only a filename or uuid which the frontend can load via a REST call to /api/posters/id?).

Entry creation returns a 201 but accessing it returns 404

I have a simple Spring Data Rest implementation of user creation using Hibernate and MongoDB.
User.java:
#Data
#Entity
#RequiredArgsConstructor
#JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private #Id String username;
private String about;
}
UserRepository.java
#PreAuthorize("hasRole('ROLE_USER')")
#CrossOrigin(methods = {GET, PUT, POST})
public interface UserRepository extends MongoRepository<User, String> {
#Override
#PreAuthorize("hasRole('ROLE_ADMIN')")
<S extends User> S save(S s);
}
Then I make a POST call to /users with this body:
{
"username": "username1",
"about": "example"
}
I get a 201 Created response with the following body:
{
"about": "example",
"_links": {
"self": {
"href": "http://localhost:8080/api/users/username1"
},
"user": {
"href": "http://localhost:8080/api/users/username1"
}
}
}
I make a GET request to /users to see if the user was indeed added and this response is returned rightfully so:
{
"_embedded": {
"users": [
{
"about": "example",
"_links": {
"self": {
"href": "http://localhost:8080/api/users/username1"
},
"user": {
"href": "http://localhost:8080/api/users/username1"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/api/users{?page,size,sort}",
"templated": true
},
"profile": {
"href": "http://localhost:8080/api/profile/users"
}
},
"page": {
"size": 20,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
THE PROBLEM
But then I access the URL of the user provided in the links, i.e., http://localhost:8080/api/users/username1 but I get a 404 Not Found response.
What am I doing wrong? I've tried looking through examples and documentation but nothing seems to do the work. If I add the #AutoGenerated annotation it works, but I obviously want the id to be provided by the request as the username.
In User.java, I changed the declaration of username to this:
#Id
#JsonProperty("username")
#NotBlank
private String id;
MongoDb requires _id as the primary key for every document by default otherwise it won't get indexed. I had to change the field name to id so that it translates to _id on MongoDB with the #Id annotation. To fix this on the consumer side, I used #JsonProperty("username") to get the value from the username property of the request's JSON body.

How to consume _embedded resources with Spring HATEOAS

I am trying to consume the following REST HAL response from a 3rd party service:
{
"id": 51780,
"name": "Lambeth",
"description": "",
"address_id": 54225,
"website": "",
"numeric_widget_id": 3602008,
"currency_code": "GBP",
"timezone": "Europe/London",
"country_code": "gb",
"live": true,
"_embedded": {
"settings": {
"has_services": true,
"has_classes": true,
"payment_tax": 0,
"currency": "GBP",
"requires_login": false,
"has_wallets": false,
"ask_address": true,
"_links": {
"self": {
"href": "https://myhost.com/api/v1/51780/settings"
}
}
}
},
"_links": {
"self": {
"href": "https://myhost.com/api/v1/company/51780"
},
"settings": {
"href": "https://myhost.com/api/v1/51780/settings"
}
}
}
Which I would like to map to a class like this:
public class Company extends ResourceSupport {
private String name;
private CompanySettings settings;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public CompanySettings getSettings() {
return settings;
}
public void setSettings(CompanySettings settings) {
this.settings = settings;
}
}
And a class for the embedded item like this:
public class CompanySettings extends ResourceSupport {
private String currency;
public String getCurrency() {
return currency;
}
public void setCurrency(String currency) {
this.currency = currency;
}
}
However I am having no luck getting the embedded item to map to the nested settings object. My code is below.
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
ResponseEntity<Resource<Company>> responseEntity = restTemplate.exchange("https://uk.bookingbug.com/api/v1/company/51780",
HttpMethod.GET, null, new ParameterizedTypeReference<Resource<Company>>() {
}, Collections.emptyMap());
if (responseEntity.getStatusCode() == HttpStatus.OK) {
Resource<Company> userResource = responseEntity.getBody();
Company company = userResource.getContent();
}
Any help would be greatly appreciated.

Using Spring Data Projection on DBRef objects

I'm starting to learn Spring Data on a pretty simple project using a MongoDB database, and I'm having some trouble when using DBRef - and maybe in how to model a NoSQL db in general
Description
My project should organize a simple competition with a organizer (CD) and one-to-many participants. Because people can participate in multiple competitions I made respositories for both Competition and Person.
The complete code can be seen on GitHub : https://github.com/elkjaerit/rest-sample
Here is the base classes:
public class Competition {
#Id private String id;
private String name;
#DBRef
private Person organizer;
private List<Participant> participants = new ArrayList<>();
}
public class Participant {
private String freq;
#DBRef
private Person person;
}
public class Person {
#Id
private String id;
private String name;
}
Repositories:
#RepositoryRestResource(collectionResourceRel = "competition", path = "competition")
public interface CompetitionRepository extends MongoRepository<Competition, String> {
}
#RepositoryRestResource(collectionResourceRel = "person", path = "person")
public interface PersonRepository extends MongoRepository<Person, String> {
}
Problem
When I'm requesting a competition resource I'm not getting enough info on the participants - only "freq" field is shown. I've tried using #Projection and managed to get it to work for the organizer but I don't know how to get the person object for the participant ?
Result without projection
{
"_links": {
"competition": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a{?projection}",
"templated": true
},
"organizer": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a/organizer"
},
"self": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a"
}
},
"name": "Competition #1",
"participants": [
{
"freq": "F0"
},
{
"freq": "F1"
},
{
"freq": "F2"
},
{
"freq": "F3"
}
]
}
And with projection
{
"_links": {
"competition": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a{?projection}",
"templated": true
},
"organizer": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a/organizer"
},
"self": {
"href": "http://localhost:8080/competition/5710b32b03641c32671f885a"
}
},
"name": "Competition #1",
"organizer": {
"name": "Competition organizer"
},
"participants": [
{
"freq": "F0"
},
{
"freq": "F1"
},
{
"freq": "F2"
},
{
"freq": "F3"
}
]
}
Any suggestions ?
You may be able to use SPEL to call the getter for your related documents.
Your projection may look something like this -
#Projection(name = "comp", types = {Competition.class})
public interface CompetitionProjection {
String getName();
Person getOrganizer();
#Value("#{target.getParticipants()}")
List<Participant> getParticipants();
}

Resources