Implementing/Overriding MongoRepository Keep HATEOAS Formatting - spring

I have a simple MongoRepository I would like to modify to return the generated ObjectId on post(save()).
public interface EmployeeRepository extends MongoRepository<Employee, String>
{
public void delete(Employee employee);
public Employee save(Employee employee);
public Employee findOne(String id);
public List<Employee> findAll();
public Employee findByName(String principal);
}
I have explored ways to generate the id client side and pass it in the post BUT I really want Spring to handle this.
I've tried intercepting with a controller and returning the object in the ResponseBody, like so:
#RequestMapping(value=URI_EMPLOYEES, method=RequestMethod.POST)
public #ResponseBody Employee addEmployee(#RequestBody Employee employee) {
return repo.save(employee);
}
Problem with this is it forces me to re-work all the HATEOAS related logic Spring handles for me. Which is a MAJOR pain. (Unless I'm missing something.)
What's the most effective way of doing this without having to replace all of the methods?

Was using #Controller instead of #RepositoryRestController which was causing things to act up.
We can now easily override the POST method on this resource to return whatever we want while keeping spring-data-rest's implementation of the EmployeeRepository intact.
#RepositoryRestController
public class EmployeeController {
private final static String URI_EMPLOYEES = "/employees";
#Autowired private EmployeeRepository repo;
#RequestMapping(value=URI_EMPLOYEES, method=RequestMethod.POST)
public #ResponseBody HttpEntity<Employee> addVideo(#RequestBody Employee employee) {
return new ResponseEntity<Employee>(repo.save(employee), HttpStatus.OK);
}
}

Have a look at the interface:
public Employee save(Employee employee)
You can get the saved entity by simply doing
Employee saved = repository.save(employee);
return saved.getId();
The being said, you surely can generate the Id and set it via setId(). But once an ID is saved, it is immutable. Changing the Id and saving that entity would result in a new document saved in MongoDB.

Related

repo.save not storing data in H2

`I have created simple spring project where I am using requestmapping to store data in my H2 database. I am getting parameters in URL but data is getting saved.
I am new in spring boot and facing this issue. Please help me with this. Thanks
//Controller Class
#Controller
public class TrialTwoController {
#Autowired
Repository repos;
#RequestMapping("/")
public String home() {
return "home.jsp";
}
#RequestMapping(path="/addUser")
public TrailTwo post(#RequestBody TrailTwo trail) {
repos.save(trail);
return trail;
}
}
//Model class
#Entity
public class TrailTwo {
#Id
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Repository Interface
public interface Repository extends JpaRepository<TrailTwo, Integer> {
}
application properties
spring.h2.console.enabled=true
spring.datasource.platform=h2
spring.datasource.url=jdbc:h2:mem:anshul
`
Inside your controller, you want to return TrailTwo entity. However, because you use #Controller and not #RestController, Spring won't be able to return the entity and will try to render the view instead which will result in the exception.
In order to fix this problem, you need to add #ResponseBody for in your method.
#RequestMapping(path="/addUser")
public #ResponseBody TrailTwo post(#RequestBody TrailTwo trail) {
repos.save(trail);
return trail;
}
You can also separate the concerns and have UserController which will be annotated with #RestController and ViewController which will be annotated with #Controller. This way, you won't have to add #ResponseBody annotation inside #RestController.
On another note, make sure to use POST http method for adding new entities. Instead of using #RequestMapping, you can use #PostMapping which will limit the supported http methods to POST. For POST, parameters need to be passed as a part of request body.
Make sure you understand http methods and how to use them to build robust REST apis. It will make your life easier. You can read about different http methods and how to use them here https://restfulapi.net/http-methods/
For the full working example, you can take a look here: https://github.com/CaptainAye/repository-h2-sample

DeleteById method returns null

I have tried to delete a row from database using Spring data deleteById method but it returns null.
ProductServiceImpl
public void removeOne(Long id) {
Product product = findById(id);
productRepository.deleteById(product);
ProductRepository
public interface ProductRepository extends CrudRepository<Product, Long> {
void deleteById(Product product);
Controller
#RequestMapping(value="/remove", method=RequestMethod.POST)
public String remove(#ModelAttribute("id") String id, Model model) {
productService.removeOne(Long.parseLong(id.substring(10)));
List<Product> productList = productService.findAll();
model.addAttribute("productList", productList);
System.out.println("deleted successfully !!!!");
return "redirect:/product/productList";
}
Why you write it complex. Some code not necessary .First, you extends CrudRepository,it mean you don't need create custom method void deleteById(Product product); because crud contain method deleteById.
Second, Controller why you using : #RequestMapping(value="/remove", method=RequestMethod.POST) . I think it must : #DeleteMapping("/remove") . And in controller only call.
#Autowired
private ProductRepository productRepository;
#DeleteMapping("/remove/{id}")
public String remove(#PathVariable String id) {
productRepository.deleteById(id);
return "redirect:/product/productList";
}
#Autowired
private ProductRepository productRepository;
#RequestMapping(value="/remove", method=RequestMethod.POST)
public String remove(#RequestParam String id) {
productRepository.deleteById(id);
return "redirect:/product/productList";
}

Customize update entity on Spring Data repository

I have XRepository interface (extends JpaRepository). On create or update X entity i need to call method of another repository (YRepository) in transaction (exactly: update some field and use new value in created/updated entity X).
To do that i created a service class with #Transactional methods and custom REST Controller. POST mapping on controller works OK and is acceptable for me, but have problem how to implement in more elegant way update (PUT/PATCH) existing entity in my service layer. It works too, but had to use BeanUtils.copyProperties(). Is a better, more conventional way to do that ?
public interface XRepository extends JpaRepository<X, Long> {
}
public interface YRepository extends JpaRepository<Y, Long> {
}
#BasePathAwareController
public class XRestControllerCustom {
#Autowired
private MyService myService;
#PostMapping("/x")
public #ResponseBody ResponseEntity<X> create(#RequestBody Resource<X> x) {
return new ResponseEntity<X>(myService.save(x.getContent()), HttpStatus.CREATED);
}
#PatchMapping("/x/{id}")
public #ResponseBody ResponseEntity<X> update(#PathVariable Long id, #RequestBody Resource<X> x) {
myService.update(id, x.getContent());
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
}
#Component
public class MyService {
#Autowired
private XRepository xRepository;
#Autowired
private YRepository yRepository;
#Transactional
public X save(X x) {
yRepository.update();
x.setField(yRepository.get());
return xRepository.save(x);
}
#Transactional
public X update(Long id, X partial) {
X x = xRepository.getOne(id);
BeanUtils.copyProperties(x, partial);
x.setId(id); // because copyProperties makes id null
yRepository.update();
x.setField(yRepository.get());
return xRepository.save(x);
}
}

Get all documents from an index using spring-data-elasticsearch

I am trying to connect to my external ElasticSearch server with Spring Boot.
If I do a curl from command line, I get expected results.
curl "http://ipAddr:9200/indexName/TYPE/_search?pretty=true"
But getting this error when I try to access it via Spring Boot.
<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Mon Sep 11 12:39:15 IST 2017</div><div>There was an unexpected error (type=Internal Server Error, status=500).</div><div>Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: java.util.ArrayList[0]->org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl["facets"])</div></body></html>
Not sure why a NullPointerException and what is aggregartion.impl
Here is my Spring Application:
Controller:
#RestController
public class PojoController {
#Autowired
PojoService pojoService;
#RequestMapping(value = "/", method=RequestMethod.GET)
public #ResponseBody String index() {
return new String("Welcome:)");
}
#RequestMapping(value = "/all", method = RequestMethod.GET,
produces = { MediaType.APPLICATION_JSON_VALUE })
#ResponseBody List<POJO> findAll() {
try {
List<POJO> pojoObj = pojoService.findAll();
return pojoObj;
} catch (Exception exp) {
exp.printStackTrace();
return null;
}
}
}
Repository:
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
List<POJO> findAll();
}
Service:
#Service
public class POJOServiceImpl implements POJOService{
private POJORepository pojoRepository;
private ElasticsearchTemplate elasticsearchTemplate;
#Autowired
public void setPojoRepository(PojoRepository pojoRepository) {
this.pojoRepository = pojoRepository;
}
public POJO findOne(String id) {
return pojoRepository.findOne(id);
}
public List<POJO> findAll() {
return (List<POJO>) pojoRepository.findAll();
}
}
POJO class:
#Document(indexName = "INDEX", type = "TYPE")
public class POJO {
#Id
private Integer id;
private String name;
public POJO(){
// empty
}
public POJO(Integerid, String name) {
super();
this.id = id;
this.name = name;
}
// getters and setters
}
I should be able to query all the documents in the index. Later on, I will try and use filters etc.
Any help is appreciated. Thanks :)
It looks like Jackson has a problem with handling your POJO (probably related to this issue: DATAES-274) - the problematic part is casting in repository from Iterable collection to List.
Update
In case of repositories, spring-data-elasticsearch behaves a bit different than you would expect. Taking your example:
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
List<POJO> findAll();
}
and after calling in your rest controller:
List<POJO> pojoObj = pojoService.findAll();
in debugger you will see something like this:
You would expect that pojoObj list contains objects of POJO class.
And here comes the surprise - pojoObj ArrayList contains one object of AggregatedPageImpl type and its content field is the right list that contains your POJO objects.
This is the reason why you get:
Could not write JSON: ... java.util.ArrayList[0]->org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl[\"facets\"])
As I wrote before, Jackson cannot handle this while serializing POJO objects.
Solution 1
Let repositories return Iterable collection (by default).
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
}
Move the conversion part to the service but use some utility method (here with Guava) in order to have it like this:
import com.google.common.collect.Lists;
public List<POJO> findAll() {
return Lists.newArrayList(pojoRepository.findAll());
}
Solution 2
Use Page in repository (here simplified version without parameters):
#Repository
public interface PojoRepository extends ElasticsearchRepository<POJO, Integer> {
Page<TestDto> findAll();
}
If you still want to operate on list - get content from page in service:
public List<POJO> findAll() {
return testDtoRepository.findAll().getContent();
}

Spring Data REST custom query integration

I want to create a REST link for an Employee entity that will basically be a findByAllFields query. Of course this should be combined with Page and Sort. In order to do that I have implemented the following code:
#Entity
public class Employee extends Persistable<Long> {
#Column
private String firstName;
#Column
private String lastName;
#Column
private String age;
#Column
#Temporal(TemporalType.TIMESTAMP)
private Date hiringDate;
}
So I would like to have lets say a query where I can do:
http://localhost:8080/myApp/employees/search/all?firstName=me&lastName=self&ageFrom=20&ageTo=30&hiringDateFrom=12234433235
So I have the following Repository
#RepositoryRestResource(collectionResourceRel="employees", path="employees")
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long>,
JpaSpecificationExecutor<Employee> {
}
Ok so now I need a RestController
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#RequestMapping(value = "/employees/search/all/search/all", method = RequestMethod.GET)
public Page<Employee> getEmployees(EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
return employeeRepository.findAll(specification, pageable);
}
Ok, obviously this does its job but it is not integrated with HATEOAS.
I have attempted to assemble a resource changing the controller to this:
public PagedResources<Resource<Employee>> getEmployees(
PagedResourcesAssembler<Employee> assembler,
EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return assembler.toResource(employees);
}
Obviously I'm missing something from the above since it doesnt work and I'm getting the following Exception:
Could not instantiate bean class [org.springframework.data.web.PagedResourcesAssembler]: No default constructor found;
Ok so to make the question clear I am trying to integrate the above resource into the rest of the HATEOAS architecture. I'm not entirely sure if this is the correct approach so any other suggestions are welcome.
EDIT:
Here you can see a similar implementation. Please take a look at the configuration, you will see that all but one of the "Person" controllers are working.
https://github.com/cgeo7/spring-rest-example
Try autowring PagedResourcesAssembler as a class member and change method signature something like below
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#Autowired
private PagedResourcesAssembler<Employee> pagedAssembler;
#RequestMapping(value = "/employees/search/all/search/all", method = RequestMethod.GET)
public ResponseEntity<Resources<Resource<Employee>>> getEmployees(EmployeeCriteria filterCriteria, Pageable pageable) {
//EmployeeSpecification uses CriteriaAPI to form dynamic query with the fields from filterCriteria
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return assembler.toResource(employees);
}
}
This works perfectly with Spring Data Rest 2.1.4.RELEASE
The code by #Stackee007 works but the resource won't include self links. In order to do that, a little more is required.
#Autowired
PagedResourcesAssembler<Appointment> pagedResourcesAssembler;
#RequestMapping(value = "/findTodaysSchedule")
public HttpEntity<PagedResources<Resource<Appointment>>> getTodaysSchedule(
PersistentEntityResourceAssembler entityAssembler, Pageable pageable) {
Page<Appointment> todaysSchedule = apptRepo.findByStartTimeBetween(beginningOfDay, endOfDay, pageable);
#SuppressWarnings({ "unchecked", "rawtypes" })
PagedResources<Resource<Appointment>> resource = pagedResourcesAssembler.toResource(todaysSchedule,
(ResourceAssembler) entityAssembler);
return new ResponseEntity<>(resource, HttpStatus.OK);
}
Spring HATEOAS has changed the name of Resource, PagedResources and some other classes. See here. Below is a working version in 2020.
#RepositoryRestController
public class EmployeeSearchController {
#Autowired
private EmployeeRepository employeRepository;
#Autowired
private PagedResourcesAssembler<Employee> pagedAssembler;
#RequestMapping(value = "/employees/search/all", method = RequestMethod.GET)
public ResponseEntity<PagedModel<EntityModel<Employee>>> getEmployees(PersistentEntityResourceAssembler entityAssembler,,
EmployeeCriteria filterCriteria,
Pageable pageable) {
Specification<Employee> specification = new EmployeeSpecification(filterCriteria);
Page<Employee> employees = employeeRepository.findAll(specification, pageable);
return ResponseEntity.ok(pagedAssembler.toModel(plants, (RepresentationModelAssembler) entityAssembler));
}
}

Resources