Springboot Application Failed to start because of a bean problem - spring-boot

I am studying basic development practice.
While creating a CRUD Service and trying to abstract the service, an error related to the bean occurred.
here is my code
1.CrudInterface
public interface CrudInterface<Req, Res> {
Header<Res> create(Header<Req> request);
Header<Res> read(Long id);
Header<Res> update(Header<Req> request);
Header delete(Long id);
}
2.CrudController.java
#Component
public class CrudController<Req,Res,Entity> implements
CrudInterface<Req,Res> {
#Autowired(required = false)
protected BaseService<Req,Res,Entity> entityBaseService;
#Override
#PostMapping("")
public Header<Res> create(#RequestBody Header<Req> request) {
return entityBaseService.create(request);
}
#Override
#GetMapping("{id}")
public Header<Res> read(#PathVariable Long id) {
return entityBaseService.read(id);
}
#Override
#PutMapping("")
public Header<Res> update(#RequestBody Header<Req> request) {
return entityBaseService.update(request);
}
#Override
#DeleteMapping("{id}")
public Header delete(#PathVariable Long id) {
return entityBaseService.delete(id);
}
}
3.UserApiController.java
#Slf4j
#RestController
#RequestMapping("/api/user")
public class UserApiController extends
CrudController<UserApiRequest, UserApiResponse, User> {
}
4.BaseService.java
#Component
public abstract class BaseService<Req,Res,Entity> implements
CrudInterface<Req,Res> {
#Autowired(required = false)
protected JpaRepository<Entity,Long> baseRepository;
}
5.UserApiLogicService
#Service
public class UserApiLogicService extends
BaseService<UserApiRequest, UserApiResponse,User> {
#Override
public Header<UserApiResponse> create(Header<UserApiRequest> request) {
UserApiRequest body = request.getData();
User user = User.builder()
.account(body.getAccount())
.password(body.getPassword())
.status(UserStatus.REGISTERED)
.phoneNumber(body.getPhoneNumber())
.email(body.getEmail())
.registeredAt(LocalDateTime.now())
.build();
User newUser = baseRepository.save(user);
return response(newUser);
}
#Override
public Header<UserApiResponse> read(Long id) {
return baseRepository.findById(id)
.map(user -> response(user))
.orElseGet(
() -> Header.ERROR("데이터 없음")
);
}
#Override
public Header<UserApiResponse> update(Header<UserApiRequest> request) {
UserApiRequest body = request.getData();
Optional<User> optional = baseRepository.findById(body.getId());
return optional.map(user -> {
user.setAccount(body.getAccount())
.setPassword(body.getPassword())
.setStatus(body.getStatus())
.setPhoneNumber(body.getPhoneNumber())
.setEmail(body.getEmail())
.setRegisteredAt(body.getRegisteredAt())
.setUnregisteredAt(body.getUnregisteredAt());
return user;
})
.map(user -> baseRepository.save(user)) // update -> newUser
.map(updateUser -> response(updateUser)) // userApiResponse
.orElseGet(() -> Header.ERROR("데이터 없음"));
}
#Override
public Header delete(Long id) {
Optional<User> optional = baseRepository.findById(id);
// 2. repository -> delete
return optional.map(user -> {
baseRepository.delete(user);
return Header.OK();
})
.orElseGet(() -> Header.ERROR("데이터 없음"));
}
private Header<UserApiResponse> response(User user) {
UserApiResponse userApiResponse = UserApiResponse.builder()
.id(user.getId())
.account(user.getAccount())
.password(user.getPassword())
.email(user.getEmail())
.phoneNumber(user.getPhoneNumber())
.status(user.getStatus())
.registeredAt(user.getRegisteredAt())
.unregisteredAt(user.getUnregisteredAt())
.build();
return Header.OK(userApiResponse);
}
}
6.CategoryApiLogicService.java
#Service
public class CategoryApiLogicService extends
BaseService<CategoryApiRequest, CategoryApiResponse,Category> {
#Override
public Header<CategoryApiResponse> create(Header<CategoryApiRequest> request) {
CategoryApiRequest body = request.getData();
Category category = Category.builder()
.type(body.getType())
.title(body.getTitle())
.createdAt(body.getCreatedAt())
.createdBy(body.getCreatedBy())
.build();
Category newCategory = baseRepository.save(category);
return response(newCategory);
}
#Override
public Header<CategoryApiResponse> read(Long id) {
return baseRepository.findById(id)
.map(category -> response(category))
.orElseGet(()-> Header.ERROR("데이터 없음"));
}
#Override
public Header<CategoryApiResponse> update(Header<CategoryApiRequest> request) {
CategoryApiRequest body = request.getData();
return baseRepository.findById(body.getId())
.map(category -> {
category
.setType(body.getType())
.setTitle(body.getTitle())
.setCreatedAt(body.getCreatedAt())
.setCreatedBy(body.getCreatedBy());
return category;
})
.map(changeCategory -> baseRepository.save(changeCategory))
.map(newCategory -> response(newCategory))
.orElseGet(()->Header.ERROR("데이터 없음"));
}
#Override
public Header delete(Long id) {
return baseRepository.findById(id)
.map(category -> {
baseRepository.delete(category);
return Header.OK();
})
.orElseGet(()->Header.ERROR("데이터 없음"));
}
private Header<CategoryApiResponse> response(Category category){
CategoryApiResponse body = CategoryApiResponse.builder()
.id(category.getId())
.type(category.getType())
.title(category.getTitle())
.createdAt(category.getCreatedAt())
.createdBy(category.getCreatedBy())
.build();
return Header.OK(body);
}
}
8.ItemApiLogicService.java
#Service
public class ItemApiLogicService extends
BaseService<ItemApiRequest,ItemApiResponse,Item> {
#Autowired
private PartnerRepository partnerRepository;
#Override
public Header<ItemApiResponse> create(Header<ItemApiRequest> request) {
ItemApiRequest body = request.getData();
Item item = Item.builder()
.status(body.getStatus())
.name(body.getName())
.title(body.getTitle())
.content(body.getContent())
.price(body.getPrice())
.brandName(body.getBrandName())
.registeredAt(LocalDateTime.now())
.partner(partnerRepository.getOne(body.getPartnerId()))
.build();
Item newItem = baseRepository.save(item);
return response(newItem);
}
#Override
public Header<ItemApiResponse> read(Long id) {
return baseRepository.findById(id)
.map(user -> response(user))
.orElseGet(
() -> Header.ERROR("데이터 없음")
);
}
#Override
public Header<ItemApiResponse> update(Header<ItemApiRequest> request) {
ItemApiRequest body = request.getData();
return baseRepository.findById(body.getId())
.map(entityItem -> {
entityItem
.setStatus(body.getStatus())
.setName(body.getName())
.setTitle(body.getTitle())
.setContent(body.getContent())
.setPrice(body.getPrice())
.setBrandName(body.getBrandName())
.setRegisteredAt(body.getRegisteredAt())
.setUnregisteredAt(body.getUnregisteredAt());
return entityItem;
})
.map(newEntityItem -> baseRepository.save(newEntityItem))
.map(item -> response(item))
.orElseGet(() -> Header.ERROR("데이터 없음"));
}
#Override
public Header delete(Long id) {
return baseRepository.findById(id)
.map(item -> {
baseRepository.delete(item);
return Header.OK();
})
.orElseGet(() -> Header.ERROR("데이터없음"));
}
private Header<ItemApiResponse> response(Item item) {
ItemApiResponse body = ItemApiResponse.builder()
.id(item.getId())
.status(item.getStatus())
.name(item.getName())
.title(item.getTitle())
.content(item.getContent())
.price(item.getPrice())
.brandName(item.getBrandName())
.registeredAt(item.getRegisteredAt())
.unregisteredAt(item.getUnregisteredAt())
.partnerId(item.getPartner().getId())
.build();
return Header.OK(body);
}
}
and here is my error message
ERROR 4516 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
Field entityBaseService in com.example.admin.controller.CrudController required a single bean, but 6 were found:
- categoryApiLogicService: defined in file [D:\~\service\CategoryApiLogicService.class]
- itemApiLogicService: defined in file [D:\~\service\ItemApiLogicService.class]
- orderDetailApiLogicService: defined in file [D:\~\service\OrderDetailApiLogicService.class]
- orderGroupApiLogicService: defined in file [D:\~\service\OrderGroupApiLogicService.class]
- partnerApiLogicService: defined in file [D:\~\service\PartnerApiLogicService.class]
- userApiLogicService: defined in file [D:\~\service\UserApiLogicService.class]
Thanks for reading the long story.
I hope you catch the error.

Maybe has 6 classes implement BaseService and you decide the abstract class with name entityBaseService, so Spring cannot bind exactly the bean you want.
You can use #primary mask on a class for default bean or using #qualifier to bind with the bean name
prefer: http://zetcode.com/springboot/qualifier

Related

spring resttemplate request object not mapping to rest controller

i have below resttempalte which invokes rest controller of another service..
#Override
public ResponseEntity<String> callRestAPI(APIReqDataMO apiReqDataMO) {
String apiURL = URIGenerator.getAPIURL(apiReqDataMO);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("Accept", MediaType.APPLICATION_JSON_VALUE);
HttpEntity<?> request = new HttpEntity<>(apiReqDataMO.getRequestObject(), headers);
ResponseEntity<String> httpRes = restTemplate.postForEntity(apiURL, request, String.class);
return httpRes;
}
and in my service i have controller, which consumes above request..
#RequestMapping(value = "/targetService/createUser", method = RequestMethod.POST, consumes = "application/json")
public String fuzzerServiceAge(UserMO userMO) {
System.out.println("---------------------age is -------------------------" + userMO.getAge());
if (userMO.getAge() > 0) {
System.out.println("error age greater than 0 ");
return "invalid user age";
} else if (userMO.getAge() == 0) {
return "invalid user age";
}
return "user added successfully";
}
when i try my test.. the age which i am pushing through rest template is not getting mapped..and i am getting age as 0 always in my system.out.. what could be wrong in my code... and is there anything missing from configuration perspective..
EDIT -
public class APIReqDataMO {
private String restAPIURL;
private Object[] pathParam;
private Object[] requestParam;
private String requestType;
private String paramType;
private Object requestObject;
public String getParamType() {
return paramType;
}
public void setParamType(String paramType) {
this.paramType = paramType;
}
public String getRequestType() {
return requestType;
}
public void setRequestType(String requestType) {
this.requestType = requestType;
}
public Object getRequestObject() {
return requestObject;
}
public void setRequestObject(Object requestObject) {
this.requestObject = requestObject;
}
public String getRestAPIURL() {
return restAPIURL;
}
public void setRestAPIURL(String restAPIURL) {
this.restAPIURL = restAPIURL;
}
public Object[] getPathParam() {
return pathParam;
}
public void setPathParam(Object[] pathParam) {
this.pathParam = pathParam;
}
public Object[] getRequestParam() {
return requestParam;
}
public void setRequestParam(Object[] requestParam) {
this.requestParam = requestParam;
}
}
controller
#PostMapping("/targetService/createUser")
public String fuzzerServiceAge(UserMO userMO) {
System.out.println("--------------------- age is -------------------------" + userMO.getAge());
if (userMO.getAge() > 0) {
// return ResponseEntity.ok("Hello World!");
} else if (userMO.getAge() == 0) {
System.out.println(" it is else block");
// return ResponseEntity.badRequest().build();
}
// return ResponseEntity.ok("user added successfully!");
return "user added successfully";
}
usermo
public class UserMO {
#JsonProperty("name")
private String name;
#JsonProperty("age")
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
Issue
There is an issue in API implementation. You are creating POST API and when the user will invoke this API by passing UserMO in the request body then mapping won't happen because the #RequestBody annotation is missing.
#PostMapping("/targetService/createUser")
public String fuzzerServiceAge(UserMO userMO) {
System.out.println("--------------------- age is -------------------------" + userMO.getAge());
if (userMO.getAge() > 0) {
// return ResponseEntity.ok("Hello World!");
} else if (userMO.getAge() == 0) {
System.out.println(" it is else block");
// return ResponseEntity.badRequest().build();
}
// return ResponseEntity.ok("user added successfully!");
return "user added successfully";
}
Solution
If you are using #RestController annotation on top of the controller class then add #RequestBody annotation before UserMO userMO and try again.
Like this
#PostMapping("/targetService/createUser")
public String fuzzerServiceAge(#RequestBody UserMO userMO) {
//logic
}
if you are using #Controller annotation on top of the controller class then add #ResponseBody annotation on top of method fuzzerServiceAge() and #RequestBody annotation before UserMO userMO and try again.
Like this
#PostMapping("/targetService/createUser")
#ResponseBody
public String fuzzerServiceAge(#RequestBody UserMO userMO) {
//logic
}

Use custom inheritor from the hateoas.CollectionModel in hateoas.server.RepresentationModelProcessor

I have simple RestController that return CollectionModel:
#RequestMapping("/test")
public ResponseEntity<?> index() {
List<DemoEntity> all = Arrays.asList(new DemoEntity(1L, "first"),
new DemoEntity(2L, "second"),
new DemoEntity(3L, "third"));
List<EntityModel<DemoEntity>> list = new ArrayList<>();
all.forEach(demoEntity -> list.add(new EntityModel<>(demoEntity)));
CollectionModel<EntityModel<DemoEntity>> collectionModel = new CollectionModel<>(list);
return ResponseEntity.ok(collectionModel);
}
DemoEntity has only two fields, id and name. SecondEntity is the same.
I am trying to use RepresentationModelProcessor:
#Configuration
public class SpringDataRestConfig {
#Bean
public RepresentationModelProcessor<EntityModel<DemoEntity>> demoEntityProcessor() {
return new RepresentationModelProcessor<EntityModel<DemoEntity>>() {
#Override
public EntityModel<DemoEntity> process(EntityModel<DemoEntity> entity) {
return new MyHateoasEntityModel<>(entity.getContent(), entity.getLink("self").orElse(new Link("self")));
}
};
}
#Bean
public RepresentationModelProcessor<CollectionModel<EntityModel<DemoEntity>>> demoEntitiesProcessor() {
return new RepresentationModelProcessor<CollectionModel<EntityModel<DemoEntity>>>() {
#Override
public CollectionModel<EntityModel<DemoEntity>> process(CollectionModel<EntityModel<DemoEntity>> collection) {
// return new CollectionModel<>(collection.getContent());
return new MyHateoasCollectionModel<>(collection.getContent());
}
};
}
#Bean
public RepresentationModelProcessor<EntityModel<SecondEntity>> secondEntityProcessor() {
return new RepresentationModelProcessor<EntityModel<SecondEntity>>() {
#Override
public EntityModel<SecondEntity> process(EntityModel<SecondEntity> entity) {
return new MyHateoasEntityModel<>(entity.getContent(), entity.getLink("self").orElse(new Link("self")));
}
};
}
#Bean
public RepresentationModelProcessor<CollectionModel<EntityModel<SecondEntity>>> secondEntitiesProcessor() {
return new RepresentationModelProcessor<CollectionModel<EntityModel<SecondEntity>>>() {
#Override
public CollectionModel<EntityModel<SecondEntity>> process(CollectionModel<EntityModel<SecondEntity>> collection) {
// return new CollectionModel<>(collection.getContent());
return new MyHateoasCollectionModel<>(collection.getContent());
}
};
}
}
One thing here is that I want to use my own classes MyHateoasEntityModel and MyHateoasCollectionModel. They are quite simple:
public class MyHateoasEntityModel<T> extends EntityModel<T> {
private T entity;
public MyHateoasEntityModel(T entity, Iterable<Link> links) {
super(entity, Collections.emptyList());
this.entity = entity;
}
public MyHateoasEntityModel(T entity, Link... links) {
this(entity, Collections.emptyList());
}
}
public class MyHateoasCollectionModel<T> extends CollectionModel<T> {
public MyHateoasCollectionModel(Iterable<T> content, Link... links) {
super(content, Collections.emptyList());
}
}
The question is that when the controller is called, the demoEntityProcessor, demoEntitiesProcessor methods are called in turn. And this is what i want from the application. But then, somehow, secondEntitiesProcessor is called, but shouldn't.
Earlier, spring boot 1.5.17 was used, and everything works fine.
All code on: https://github.com/KarinaPleskach/Hateoas

How can I support PUT method in my Spring Boot app?

I'm using Angular CLI and Spring Boot.
Everything works (getEmployee, deleteEmployee) but when I want to Update or Create (same method, and same HTML Form) an Employee I get in HTML Console/Network this error:
[ERROR] message: "Request method 'PUT' not supported"
This is my Controller:
#RestController
#RequestMapping("/api")
public class EmployeeController {
private final EmployeeServiceImpl employeeService;
#Autowired
public EmployeeController(EmployeeServiceImpl employeeService) {
this.employeeService = employeeService;
}
#GetMapping("/employees")
public List<Employee> getAllEmployees() {
return employeeService.findAllEmployees();
}
#GetMapping("/employees/{id}")
public ResponseEntity<Employee> getEmployeeById(#PathVariable(value = "id") Long employeeId) {
Employee employee = employeeService.findById(employeeId).get();
return ResponseEntity.ok().body(employee);
}
#PutMapping("/employees/{id}")
public ResponseEntity<Employee> updateEmployee(#PathVariable(value = "id") Long employeeId,
#Valid #RequestBody Employee employeeDetails) {
Employee employee = employeeService.findById(employeeId).get();
employee.setEmailAddress(employeeDetails.getEmailAddress());
employee.setLastName(employeeDetails.getLastName());
employee.setFirstName(employeeDetails.getFirstName());
employee.setStatus(employeeDetails.getStatus());
employee.setSkills(employeeDetails.getSkills());
final Employee updatedEmployee = employeeService.saveEmployee(employee);
return ResponseEntity.ok(updatedEmployee);
}
#DeleteMapping("/employees/{id}")
public Map<String, Boolean> deleteEmployee(#PathVariable(value = "id") Long employeeId) {
Employee employee = employeeService.findById(employeeId).get();
employeeService.deleteEmployee(employee);
Map<String, Boolean> response = new HashMap<>();
response.put("deleted", Boolean.TRUE);
return response;
}
}//close class
This is also my CORSConfig:
#Configuration
public class CORSConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedHeaders("*")
.allowedMethods("GET", "POST", "DELETE","PUT");
}
}//close class
Here you can see my Angular code:
EmployeeDetail.ts
export class EmployeeDetailsComponent implements OnInit {
#Input()
employee: Employee;
skills;
constructor(
private route: ActivatedRoute, /* holds information about the route to this instance of the EmployeeDetailsComponent */
private location: Location,
private employeeService: EmployeeService,
private skillService: SkillService
) {
}
ngOnInit() {
this.getEmployee();
this.skillService.getSkills().subscribe(res => this.skills = res);
}
getEmployee(): void {
const id = +this.route.snapshot.paramMap.get('id');
if (id === -1) {
this.employee = new Employee();
} else {
this.employeeService.getEmployee(id)
.subscribe(employee => this.employee = employee);
}
}
save(): void {
this.employeeService.updateEmployee(this.employee)
.subscribe(() => this.goBack());
console.log('test', this.employee);
}
goBack(): void {
this.location.back();
}
}
When I click on save the method redirect me to the Update method in my service;
service.ts
const httpOptions = {
headers: new HttpHeaders({'Content-Type': 'application/json'})
};
#Injectable({
providedIn: 'root'
})
export class EmployeeService {
private baseUrl = 'http://localhost:8080/api/employees';
constructor(private http: HttpClient) { }
/** GET Employees from the server */
getEmployees(): Observable<Employee[]> {
return this.http.get<Employee[]>(this.baseUrl);
}
getEmployee(id: number): Observable<Employee> {
const url = this.baseUrl + '/' + id;
return this.http.get<Employee>(url);
}
/** PUT: update the employee on the server */
updateEmployee(employee: Employee): Observable<any> {
return this.http.put(this.baseUrl, employee, httpOptions);
}
deleteEmployee(id: number): Observable<any> {
return this.http.delete(`${this.baseUrl}/${id}`, { responseType: 'text' });
}
}
This is my first SpringBoot app, also w/Angular so I never seen this error before.
What can I do?
Your Angular code shows the following method
updateEmployee(employee: Employee): Observable<any> {
return this.http.put(this.baseUrl, employee, httpOptions);
}
You did not include id in angular side. But for your spring boot side requires id. Since you are not passing the id the framework cannot find matching method resulting in 405 method not allowed error
#PutMapping("/employees/{id}")
public ResponseEntity<Employee> updateEmployee(#PathVariable(value = "id") Long employeeId,
#Valid #RequestBody Employee employeeDetails) {
Employee employee = employeeService.findById(employeeId).get();
employee.setEmailAddress(employeeDetails.getEmailAddress());
employee.setLastName(employeeDetails.getLastName());
employee.setFirstName(employeeDetails.getFirstName());
employee.setStatus(employeeDetails.getStatus());
employee.setSkills(employeeDetails.getSkills());
final Employee updatedEmployee = employeeService.saveEmployee(employee);
return ResponseEntity.ok(updatedEmployee);
}

How to properly implement adding a new record into Elasticsearch using Spring Boot?

Getting started with Spring Boot / Spring Data / Elasticsearch application.
ES -> 6.1
Have a simple repository:
public interface BusinessMetadataRepository extends ElasticsearchRepository<BusinessMetadata, Long> {
List<BusinessMetadata> findByName(String name);
List<BusinessMetadata> findById(Long id);
}
And a Business Object:
import org.springframework.data.elasticsearch.annotations.Document;
#Document(indexName = "bsn", type = "mtd", shards = 1)
public class BusinessMetadata {
private Long id;
private String name;
public Long getId() {return id;}
public void setId(Long id) {this.id = id;}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public BusinessMetadata(Long id, String name) {
this.id = id;
this.name = name;
}
public BusinessMetadata() {
}
}
Elastic Configuration:
#Configuration
#EnableElasticsearchRepositories(basePackages = "com.discover.harmony.elastic.repository")
public class ElasticConfiguration {
#Bean
public NodeBuilder nodeBuilder() {
return new NodeBuilder();
}
#Bean
public ElasticsearchOperations elasticsearchTemplate() throws IOException {
File tmpDir = File.createTempFile("elastic", Long.toString(System.nanoTime()));
System.out.println("Temp directory: " + tmpDir.getAbsolutePath());
Settings.Builder elasticsearchSettings =
Settings.settingsBuilder()
.put("http.enabled", "true") // 1
.put("index.number_of_shards", "1")
.put("path.data", new File(tmpDir, "data").getAbsolutePath()) // 2
.put("path.logs", new File(tmpDir, "logs").getAbsolutePath()) // 2
.put("path.work", new File(tmpDir, "work").getAbsolutePath()) // 2
.put("path.home", tmpDir); // 3
return new ElasticsearchTemplate(nodeBuilder()
.local(true)
.settings(elasticsearchSettings.build())
.node()
.client());
}
}
My Rest Controller for doing search works fine:
#RestController
#RequestMapping("/rest/search")
public class SearchResource {
#Autowired
BusinessMetadataRepository businessMetadataRepository;
#GetMapping(value = "/name/{text}")
public List<BusinessMetadata> searchName(#PathVariable final String text) {
return businessMetadataRepository.findByName(text);
}
#GetMapping(value = "/all")
public List<BusinessMetadata> searchAll() {
List<BusinessMetadata> businessMetadataList = new ArrayList<>();
Iterable<BusinessMetadata> businessMetadata = businessMetadataRepository.findAll();
businessMetadata.forEach(businessMetadataList::add);
return businessMetadataList;
}
}
My Rest Controller for doing save:
#RestController
#RequestMapping("/rest/save")
public class SaveResource {
#Autowired
BusinessMetadataRepository businessMetadataRepository;
#GetMapping(value = "/name/{text}")
public void Save(String text) {
businessMetadataRepository.save(new BusinessMetadata((long)99, text));
}
}
When I test the save using Postman, I get this error:
{
"timestamp": 1514325625996,
"status": 405,
"error": "Method Not Allowed",
"exception": "org.springframework.web.HttpRequestMethodNotSupportedException",
"message": "Request method 'POST' not supported",
"path": "/rest/save/name/new-1"
}
What changes do I need to make in order to properly configure this project to support inserting new documents?
Based on the comment from AntJavaDev, I have modified my controller in the following way:
#RestController
#RequestMapping("/rest/save")
public class SaveResource {
#Autowired
BusinessMetadataRepository businessMetadataRepository;
#PostMapping("/name/{text}")
public void Save(#PathVariable String text) {
BusinessMetadata mtd = businessMetadataRepository.save(new BusinessMetadata(text));
}
}
The 2 key changes are: replace #GetMapping with #PostMapping, and include #PathVariable as a parameter qualifier.
Now it works as expected

Is my implementation of a simple CRUD service with Spring WebFlux correct?

I am implementing a simple Movie based CRUD API using Spring WebFlux and Reactive Spring Data MongoDB. I want to make sure that my implementation is correct and that I am properly using Flux and Mono to implement the CRUD operations. I also want to make sure that I am properly handling any errors or null values. I am very new to this programming paradigm and Spring WebFlux, so I am not sure about the correctness of the implementation of the Controller and Service layer, I want to make sure I am adhering to Spring WebFlux and Project Reactor best practices.
#Repository
public interface MovieRepository extends ReactiveMongoRepository<Movie, String> {
Flux<Movie> findByRating(String rating);
}
public interface MovieService {
Flux<Movie> list();
Flux<Movie> findByRating(String rating);
Mono<Movie> update(String id, MovieRequest movieRequest);
Mono<Movie> create(Mono<MovieRequest> movieRequest);
Mono<Movie> read(String id);
Mono<Movie> delete(String id);
}
#Service
public class MovieServiceImpl implements MovieService {
#Autowired
private MovieRepository movieRepository;
#Override
public Flux<Movie> list(){
return movieRepository.findAll();
}
#Override
public Flux<Movie> findByRating(final String rating){
return movieRepository.findByRating(rating);
}
#Override
public Mono<Movie> update(String id, MovieRequest movieRequest) {
return movieRepository.findOne(id).map(existingMovie -> {
if(movieRequest.getDescription() != null){
existingMovie.setDescription(movieRequest.getDescription());
}
if(movieRequest.getRating() != null){
existingMovie.setRating(movieRequest.getRating());
}
if(movieRequest.getTitle() != null) {
existingMovie.setTitle(movieRequest.getTitle());
}
return existingMovie;
}).then(movieRepository::save);
}
#Override
public Mono<Movie> create(Mono<MovieRequest> movieRequest) {
return movieRequest.map(newMovie -> {
Movie movie = new Movie();
if(newMovie.getDescription() != null){
movie.setDescription(newMovie.getDescription());
}
if(newMovie.getRating() != null){
movie.setRating(newMovie.getRating());
}
if(newMovie.getTitle() != null) {
movie.setTitle(newMovie.getTitle());
}
return movie;
}).then(movieRepository::save);
}
#Override
public Mono<Movie> read(String id) {
return movieRepository.findOne(id);
}
#Override
public Mono<Movie> delete(String id) {
Mono<Movie> movie = movieRepository.findOne(id);
movieRepository.delete(id);
return movie;
}
}
#RestController
public class MovieRestController {
#Autowired
private MovieService movieService;
#GetMapping(value = "/movies")
public Flux<ResponseEntity<Movie>> list() {
return movieService.list().map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
#GetMapping(value = "/moviesByRating")
public Flux<ResponseEntity<Movie>> findByRating(
#RequestParam(value = "rating", required = false) final String rating) {
return movieService.findByRating(rating)
.map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
#GetMapping("/movies/{movieId}")
public Mono<ResponseEntity<Movie>> read(
#PathVariable("movieId") final String movieId) {
return movieService.read(movieId)
.map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
#DeleteMapping("/movies/{movieId}")
public Mono<ResponseEntity<Movie>> delete(
#PathVariable("movieId") final String movieId) {
return movieService.delete(movieId)
.map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
#PutMapping("/movies/{movieId}")
public Mono<ResponseEntity<Movie>> update(
#PathVariable("movieId") final String movieId,
#RequestBody final MovieRequest movieRequest) {
return movieService.update(movieId, movieRequest)
.map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
#PostMapping("/movies")
public Mono<ResponseEntity<Movie>> create(
#RequestBody final Mono<MovieRequest> movieRequest) {
return movieService.create(movieRequest)
.map(m -> new ResponseEntity<>(m, HttpStatus.OK));
}
}

Resources