springboot mongodb crud update only changed fields - spring

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);
}
}

Related

Spring GraphQL mutation NullPointerException

I'm learning GraphQL in spring boot and I have created a mutation that saves used details in database. When I try to call the mutation from GraphiQL UI using following mutation query, it throws NullPointerException because the object I passed in mutation is not mapped with UserDto and I don't know why.
I have following code:
Controller
#Controller
public class UserController {
private UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
#QueryMapping(name = "userById")
public UserDto findUserById(#Argument Long id) {
return userService.findById(id);
}
#QueryMapping
public String removeUserById(#Argument Long id) {
return userService.removeById(id);
}
#MutationMapping(name = "save")
public UserDto save(#Argument UserDto user) {
return userService.save(user);
}
#QueryMapping(name = "users")
public List<UserDto> findAll() {
return userService.findAll();
}
}
GraphQL Schema
schema {
query: Query
mutation: Mutation
}
type Query{
users:[User]
userById(id:ID):User
}
type Mutation{
save(userInput:UserInput):User
}
#input types
input UserInput{
firstName:String!
lastName:String!
emailAddress:String!
ipAddress:String!
address:AddressInput!
}
input AddressInput{
addressLine1:String!
addressLine2:String!
addressLine3:String!
addressLine4:String!
addressLine5:String!
addressPostCode:Int!
}
#object types
type User{
firstName:String!
lastName:String!
emailAddress:String!
ipAddress:String!
address:Address!
}
type Address{
addressLine1:String!
addressLine2:String!
addressLine3:String!
addressLine4:String!
addressLine5:String!
addressPostCode:Int!
}
Mutation Query
mutation Save($userDto: UserInput!) {
save(userInput: $userDto) {
firstName
lastName
emailAddress
ipAddress
address {
addressLine1
addressLine2
addressLine3
addressLine4
addressLine5
addressPostCode
}
}
}
Variables
{
"userDto": {
"ipAddress": "192.168.0.124",
"firstName": "John",
"lastName": "Mark",
"emailAddress": "john#gmail.com",
"address": {
"addressLine1": "251 WC",
"addressLine2": "UC MAIN",
"addressLine3": "PB121",
"addressLine4": "New York",
"addressLine5": "USA",
"addressPostCode": 457821
}
}
}
Results
{
"errors": [
{
"message": "INTERNAL_ERROR for b6287602-fc10-6ecf-2091-b57ceaeb9f0a",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": [
"save"
],
"extensions": {
"classification": "INTERNAL_ERROR"
}
}
],
"data": {
"save": null
}
}
When I run the GraphQL query, it throws NullPointerException in console.
Console Error
java.lang.NullPointerException: Cannot invoke "com.graphql.sample.dto.UserDto.getEmailAddress()" because "userDto" is null
I'm answering my question because I have already solved it. I had made a small mistake in controller near #Argument UserDto user as below:
#MutationMapping(name = "save")
public UserDto save(#Argument(name = "userInput") UserDto user) {
return userService.save(user);
}
The name in #Argument should match the name of parameter in Mutation type as below:
type Mutation{
save(userInput:UserInput):User
}
type User{
firstName:String!
lastName:String!
emailAddress:String!
ipAddress:String!
address:Address!
}
Reason 1:
Your save mutation result is returned to this entity. But it can be possible that a property that you are receiving might be null that's why this error is thrown, as you are using ! with each property. You can remove ! if a value can/is null
Reason 2:
I couldn't see your resolver so it might be possible that the result is not an object or the property names that you are receiving don't match with the property names inside type User. It should be same or use resolver to provide their values

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?).

How can I get the nested list of object with reactive couchbase?

I recently started reviewing/learning some about reactive couchbase and I'm trying to implement a sample using that technology with spring data and spring boot, I have the following models:
#Document
public class Person {
#Id
private String id;
#Field
private String name;
#Field
private String lastName;
#Field
private List<Address> address;
// Getters & Setters
}
#Document
public class Address {
#Id
private String code;
#Field
private String mainStreet;
#Field
private String secondStreet;
// Getters & Setters
}
Repositories:
#N1qlPrimaryIndexed
#ViewIndexed(designDoc = "address")
public interface AddressRepository extends ReactiveCouchbaseRepository<Address, String> {
}
#N1qlPrimaryIndexed
#ViewIndexed(designDoc = "person")
public interface PersonRepository extends ReactiveCouchbaseRepository<Person, String> {
}
Service:
public interface PersonService {
Flux<Person> getAllPersons();
Mono<Person> getPerson(String id);
Flux<Address> getAddressesByPerson(String id);
}
#Service
public class PersonServiceImpl implements PersonService {
#Autowired
private PersonRepository personRepository;
#Override
public Flux<Person> getAllPersons() {
return personRepository.findAll();
}
#Override
public Mono<Person> getPerson(String id) {
return personRepository.findById(id);
}
#Override
public Flux<Address> getAddressesByPerson(String id) {
return null;
}
}
Controller:
#RestController
#RequestMapping("/sample_couchbase")
public class PersonController {
#Autowired
private PersonService personService;
#GetMapping("/people")
public Flux<Person> getAllPersons() {
return personService.getAllPersons();
}
#GetMapping("/person/{id}")
public Mono<ResponseEntity<Person>> getPersonsById(#PathVariable String id) {
return personService.getPerson(id)
.map(person -> ResponseEntity.status(HttpStatus.OK).body(person))
.defaultIfEmpty(ResponseEntity.notFound().build());
}
}
As so far this works well, I can retrieve all the people and also filter by an id of a specific person, on the other hand I would like to retrieve all list of addresses of a specific person, I mean I have this document in couchbase:
{
"id": "1",
"name": "Scooby",
"lastName": "Doo",
"address": [
{
"code": "A1",
"mainStreet": "AAA",
"secondStreet": "BBB",
"phone": "11111",
"place": "home"
},
{
"code": "A2",
"mainStreet": "CCC",
"secondStreet": "DDD",
"phone": "22222",
"place": "work"
},
{
"code": "A3",
"mainStreet": "EEE",
"secondStreet": "FFF",
"phone": "33333",
"place": "treasury"
}
],
"classType": "com.jcalvopinam.model.Person"
}
When I call the service e.g.: http://localhost:8080/sample_couchbase/person/1/addresses I want to get this:
"address": [
{
"code": "A1",
"mainStreet": "AAA",
"secondStreet": "BBB",
"phone": "11111",
"place": "home"
},
{
"code": "A2",
"mainStreet": "CCC",
"secondStreet": "DDD",
"phone": "22222",
"place": "work"
},
{
"code": "A3",
"mainStreet": "EEE",
"secondStreet": "FFF",
"phone": "33333",
"place": "treasury"
}
]
I imagined create in the repository a method something like this:
Mono<Person> findById(String id);
And in the service layer I imagine getting the whole object and then filtering by Address, but this is something different from what I'm used to doing, now I'm using Mono and Flux and not a simple object or a List, so I don't know how to do that, can someone give me an idea? or is there a better solution?
I found an easy way to retrieve the Addresses of a specific Person, I used a query where I specified what I want to retrieve:
#Query("SELECT addresses, META(sample).id as _ID, META(sample).cas as _CAS FROM `sample` WHERE id = $1")
Flux<Person> findAddressesByPerson(String id);
I noticed that it is necessary to specify the META(sample).id as _ID and META(sample).cas as _CAS attributes to avoid the following error:
org.springframework.data.couchbase.core.CouchbaseQueryExecutionException: Unable to retrieve enough metadata for N1QL to entity mapping, have you selected _ID and _CAS?; nested exception is rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: com.couchbase.client.java.query.DefaultAsyncN1qlQueryRow.class
If you want to take a look at the implementation in more detail, here is the sample: https://github.com/juanca87/sample-couchbase-rx

resource link rel with id spring data rest

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;
}
};
}
}

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.

Resources