Spring Boot REST API won't convert JSON to object - java

I'm running into a failure, but I can't figure out why. For a simple test, I want to quickly create a RESTful API to accept a JSON and convert it to an object (POJO).
Restcontroller:
#RestController
public class RESTController {
#Autowired
AuthorService authorService;
#RequestMapping(value = "/setsAuthor", headers = "Accept=application/json", method=RequestMethod.POST)
public void addAuthor(#RequestBody Author author){
authorService.addAuthor(author);
}
}
Data object:
#AllArgsConstructor
#Entity
#NoArgsConstructor
public class Author {
#Id
private Integer id;
private String authorName;
}
Service:
#Service
public class AuthorService {
#Autowired
AuthorRepository authorRepository;
public void addAuthor(Author author) {
try {
authorRepository.save(author);
} catch (IllegalAccessError e) {
System.out.println("cant save Author: " + author + " to Respository");
}
}
Repository interface:
public interface AuthorRepository extends CrudRepository<Author, Integer> {
}
Post Request at
http://localhost:8080/setsAuthor
With JSON:
{
"id": 1,
"authorName": "SomeAuthor"
}
I also tried to wrap the Items in the JSON in a "Author" :{}
The RestController won't map my JSON to the object. It always says id = null, authorName = null when I debug the program.
What am I doing wrong? I remember it always worked this way.

Add #Getter and #Setter to entity
#AllArgsConstructor
#Entity
#NoArgsConstructor
#Getter
#Setter
public class Author {

You can also use
#AllArgsConstructor(onConstructor = #__(#JsonCreator))
instead of #Setter

Related

Custom Return Type on Custom CrudRepository Method

I am trying to write an interface that extends CrudRepository that will return a list of a particular field. When I use that method, I get ConverterNotFoundException. I have two questions:
Is there a specific Spring Boot query if I want a list containing a specific field?
Am I implementing the converter correctly? I am not sure how to call WebConfig.
// EmployeeRepository.java
#Repository
public interface EmployeeRepository extends CrudRepository<Employee, Long> {
List<String> findByEmployeeId(String employeeId); // ConverterNotFoundException. Expecting list of employee's full name
}
// EmployeeToStringConverter.java
#Component
public class EmployeeToStringConverter implements Converter<Employee, String> {
#Override
public String convert(Employee source) {
return source.getFullName();
}
}
// WebConfig.java
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new EmployeeToStringConverter());
}
}
// Employee.java
#Entity
#Data
#NoArgsConstructor
#Getter
#Table(name = "employees")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="Id")
private Long id;
#Column(name="FullName")
private String fullName;
#Column(name="NickName")
private String nickName;
public HubKey(String fullName, String nickName) {
this.fullName = fullName;
this.nickName = nickName;
}
}
// Exception when calling EmployeeRepository.findByEmployeeId()
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.jon.demo.domain.entity.Employee] to type [java.lang.String]
The converter you have registered in the WebMvcConfigurer is used for formatting data in the view(The view in MVC).
You should add converter to Spring Data related custom conversions beans, every Spring Data sub project has its own registration entry there.
Please read the Spring Data related docs.

Bad Request while doing POST on Postman using SpringBoot

I am getting Bad Request when trying to do POST to createCategory end point. All endpoint works but not for createCategory. Am i doing something wrong?
All my DTO classes using the same annotation but only this one doesn't work. Is it possible that spring doesn't accept single variable response body?
endpoint: http://localhost:8180/api/v1/categories
request body in json:
{
"name": "Category 1"
}
CategoryController:
#RestController
#RequiredArgsConstructor
#RequestMapping("api/v1/categories")
public class CategoryController {
private final CategoryApplicationService categoryApplicationService;
#PostMapping
public ResponseEntity<Data<CategoryIDResponse>> createCategory(#RequestBody CreateCategory createCategory){
return new ResponseEntity<>(new Data<>(categoryApplicationService.createCategory(createCategory)), HttpStatus.CREATED);
}
#PatchMapping
public ResponseEntity<Data<CategoryIDResponse>> updateCategory(#RequestBody UpdateCategory updateCategory){
return new ResponseEntity<>(new Data<>(categoryApplicationService.updateCategory(updateCategory)), HttpStatus.OK);
}
#DeleteMapping("/{categoryID}")
public ResponseEntity<Data<CategoryIDResponse>> deleteCategory(#PathVariable("categoryID") UUID categoryID){
return new ResponseEntity<>(new Data<>(categoryApplicationService.deleteCategory(categoryID)), HttpStatus.OK);
}
#GetMapping("/{categoryID}")
public ResponseEntity<Data<GetCategoryResponse>> getCategory(#PathVariable("categoryID") UUID categoryID){
return new ResponseEntity<>(new Data<>(categoryApplicationService.getCategory(categoryID)), HttpStatus.OK);
}
#GetMapping
public ResponseEntity<Data<List<GetCategoryResponse>>> getAllCategory(){
return new ResponseEntity<>(new Data<>(categoryApplicationService.getAllCategory()), HttpStatus.OK);
}
}
DTO:
CreateCategory:
#Getter
#Builder
#AllArgsConstructor
public class CreateCategory {
#NotNull
private final String name;
}
This is happening because you have not added NoArgsConstructor in your DTO.
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class CreateCategory {
private String name;
}
Change your DTO to above code. Working fine for me.
Hope this helps.
Actually, you need to add #Setter annotation to the CreateCategory class by removing the final keyword, because spring will set the fields to the objects using setter methods, and you cant use setters with final variables.

How to test JSON returned by controller using expected DTO?

I use spring-test 5.0.7 and run into the next issue:
DTOs:
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class SomeDTO {
private String uid;
private Object child;
}
#Data
#AllArgsConstructor
#NoArgsConstructor
public static class AnotherDTO {
private String someField;
}
Controller:
#RestController
#RequestMapping("/api")
public static class TestController {
#GetMapping(path = "/dto/{uid}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public SomeDTO findSomeObject(#PathVariable String uid) {
return new SomeDTO(uid, new AnotherDTO("value"));
}
}
And a test that is failing:
#Test
public void testControllerResult() throws Exception {
SomeDTO dto = new SomeDTO(UUID.randomUUID().toString(), new AnotherDTO("value"));
mockMvc.perform(MockMvcRequestBuilders.get("/api/dto/{uid}", dto.getUid()))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$").value(dto));
}
Test logs:
java.lang.AssertionError: JSON path "$"
Expected :TestTest.SomeDTO(uid=7817869a-eb9a-4491-a34b-a8006a643b6c, child=TestTest.AnotherDTO(someField=value))
Actual :null
Is there any way to make it working without specifying type of field SomeDTO.child? I guess it does not work because SomeDTO does not provide information about field child(as soon as I make it private AnotherDTO child, test will pass), but it can be calculated at runtime.

Post a JSON Array into Spring Boot 2 JPA

I want to post like this :
[{
"employeeid": "1111",
"employeename": "YOA"
},
{
"employeeid": "2222",
"employeename": "OYA"
}]
My controller like this :
#PostMapping("/api/employee/save")
public Employee createEmployee(#Valid #RequestBody List<Employee> employee) {
return employeeService.save(employee);
}
Model :
#Entity
#Table(name="EMPLOYEE")
public class Employee implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
int id;
#Column(name = "EMP_ID")
int employeeid;
#Column(name = "EMPLOYEE_NAME")
String employeename;
//GETTER AND SETTER
}
When i post data, The error I get is the following:
{
"timestamp": "2018-07-13T03:36:25.898+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Invalid property 'id' of bean class [java.util.ArrayList]: Could not find field for property during fallback access!",
"path": "/api/employee/save"
}
Service :
public interface employeeService{
Employee save(List<Employee> employee);
}
Service Imp :
#Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService {
#Autowired
EmployeeRepository employeeRepository;
#Override
public Employee save(List<Employee> employee) {
return employeeRepository.save(employee);
}
}
Repository:
public interface EmployeeRepository extends Repository <Employee, Long>
Employee save(List<Employee> employee);
}
The error description is this:
Invalid property 'id' of bean class [java.util.ArrayList]: Could not find field for property during fallback access!
when using saveAll(), I Get Error message : No property saveAll found for type Employee
is there anyone who can help me ?
I have modified my question.
Regards,
Me
Problem is in this line
public Employee createEmployee(#Valid #RequestBody List<Employee> employee) {
return employeeService.save(employee); // Problem
}
employeeService.save can take only one Object of Entity in your case Employee
There are 2 ways
1.
public Boolean createEmployee(#Valid #RequestBody List<Employee> lstEmployee) {
try{
for(Employee emp : lstEmployee){
employeeService.save(employee);
}
return true;
}catch(Exception e){}
return false;
}
2.
Use saveAll instead
public Employee createEmployee(#Valid #RequestBody List<Employee> employee) {
return employeeService.saveAll(employee);
}
Edit 1:
After adding service class it looks like you are manually implementing so
Option 1:
I would suggest you directly use EmployeeRepository in your controller class.
As by manually overriding there methods you are not actually
enjoying benefit of using Repository
#Autowired
EmployeeRepository employeeRepository;
#PostMapping("/api/employee/save")
public Employee createEmployee(#Valid #RequestBody List<Employee> lstEmployee) {
return employeeRepository.saveAll(lstEmployee);
}
Option 2:
Longer way, change your implementation like this. There might be some error for Object but it should give you an idea
public interface employeeService{
Employee save(Employee employee);
public <S extends User> List<S> saveAll(Iterable<S> entites);
}
#Service("employeeService")
public class EmployeeServiceImpl implements EmployeeService {
#Autowired
EmployeeRepository employeeRepository;
#Override
public Employee save(Employee employee) {
return employeeRepository.save(employee);
}
#Override
public List<Employee> saveAll(List<Employee> employee) {
return employeeRepository.saveAll(employee);
}
}
I assume you're using spring-data-jpa repository. Let know if it is otherwise. You're trying to use the save api which only saves one entity. What you need is saveAll() which saves all the given entity. Please read the documentation.
https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html#saveAll-java.lang.Iterable-
Edit: Updated answer to save all records
The save method will return all the employees so change the type to List<Employees>
public interface EmployeeRepository extends Repository <Employee, Long>
List<Employee> saveAll(List<Employee> employee);
}
Even better use one of the pre-supplied interfaces for simple CRUD operations for example CrudRepository. It reduces the boilerplate code that you may have write.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories
public interface EmployeeRepository extends CrudRepository <Employee, Long>
}

Spring Data REST - RepositoryEventHandler methods not getting invoked for POST method?

I have the following domain object and DTO defined.
Country.java
#Data
#Entity
public class Country extends ResourceSupport {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long countryID;
#NotBlank(message = "Country name is a required field")
private String countryName;
private String countryNationality;
}
CountryDTO.java
#Data
public class CountryDTO {
private List<Country> countries;
}
I have overridden the POST method in the RepositoryRestController for the country class.
#RepositoryRestController
public class CountryController {
#Autowired
private CountryRepository repo;
#RequestMapping(method = POST, value = "countries")
public #ResponseBody ResponseEntity<?> createCountry(#RequestBody Resource<CountryDTO> dto,
Pageable page, PersistentEntityResourceAssembler resourceAssembler) {
Country savedCountry = repo.save(dto.getContent().getCountries());
return new ResponseEntity<>(resourceAssembler.toResource(savedCountry), HttpStatus.OK);
}
}
Now I have defined a RepositoryEventHandler to handle validations.
#Component
#RepositoryEventHandler
public class CountryHandler {
#HandleBeforeCreate
public void handleBeforeCreate(Country country) {
System.out.println("testing");
}
But when I send a POST request to the endpoint http://localhost:8080/countries, the eventhandler does not get invoked. Is there anything I am doing wrong?
UPDATE 1:
I am sending the following JSON to the endpoint using Postman.
"countries":[{
"countryName":"Australia",
"countryNationality":"Australian"
}]
It is difficult to give you an exact solution not knowing how you are invoking the request. But possible reason is that you are missing the slash symbol #RequestMapping value attribute:
#RequestMapping(method = POST, value = "countries")
Should be:
#RequestMapping(method = POST, value = "/countries")
Define a Bean in AppConfigration as
#Configuration
#EnableAsync
public class AppConfig {
#Bean
CountryHandler countryHandler (){
return new CountryHandler ();
}
}
It will work then.
Try editing maybe the Controller class annotation from:
#RepositoryRestController
to
#RestController
and mainly the method annotation from:
#RequestMapping(method = POST, value = "countries")
to
#RequestMapping(value = "/countries", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
PS: produces = MediaType.APPLICATION_JSON_VALUE if you are going to return json.
I know this is older but this works as it is supposed to.
The methods defined in a #RepositoryRestController implementation replace the methods in the default RepositoryEntityController which publish #RepositoryEventHandler events.
So your controller needs to publish a create event:
#RepositoryRestController
public class CountryController {
#Autowired
private CountryRepository repo;
private final ApplicationEventPublisher publisher; //This changed
#RequestMapping(method = POST, value = "countries")
public #ResponseBody ResponseEntity<?> createCountry(#RequestBody Resource<CountryDTO> dto,
Pageable page, PersistentEntityResourceAssembler resourceAssembler) {
Country savedCountry = repo.save(dto.getContent().getCountries());
publisher.publishEvent(new BeforeCreateEvent(savedCountry)); //This changed
return new ResponseEntity<>(resourceAssembler.toResource(savedCountry), HttpStatus.OK);
}
}

Categories

Resources