Posting a resource with ManyToOne relationship - spring

This question is primarily on How to design a rest API and whats the best practice. I am trying to figure out REST endpoint for POST service, consider below sample code:
class Person {
String name;
#OneToMany
List<Address> addresses;
}
class Address {
String someAttribute;
#ManyToOne
Person person;
}
To add a new Address the rest endpoint could look like POST api/**v1**/address. The main problem is with the RequestBody since we will only have the id of Person so we need to pass the json like below:
{
"someAttribute": "someValue"
"personId": 1
}
And in Controller method, we can't simply use #RequestBody Address address as parameter to Rest endpoint method as we do not have personId in Address class.
I would like to understand whats the best practice here and how should the Rest endpoint be?
PS: Above code is a dummy code just to explain question easily.
update
to avoid confusion I have updated endpoint url.

Please go through #PathVariables annotations in Spring API .
Using #PathVariables you get the person_id and address_id from URL.
Creates a person (initially with n address)
POST api/{api_version}/person
Updates a person (only update Person details not address)
PUT api/{api_version}/person/{person_id}
Add a new address to person
POST api/{api_version}/person/{person_id}/addresses
Updates an existing person address
PUT api/{api_version}/person/{person_id}/addresses/{address_id}
Class Structure
class Address {
Integer id;
String someAttribute;
}
class Person {
Long id;
String name;
#OneToMany
List<Address> addresses;
}
Sample Resource Implementation
#RequestMapping(value="api/{api_version}/person/{person_id}/addresses/{address_id}", method = RequestMethod.PUT)
public ResponseEntity<ApiCustomResponse> updateAddress(
#PathVariable("person_id") Long person_id,
#PathVariable("address_id") Integer address_id, #RequestBody Address address) {
...
}

Related

Is possible to get data from jpa/hibernate withou define entity or model in spring boot?

Is possible to get data from jpa/hibernate withou define entity or model in spring boot? if possible, how to? thank you...
Yes its possible
for serverside and database you first read
https://spring.io/guides/gs/relational-data-access/
and for restful ,you must use RequestEntity<> to get a field(s) without a model and send or receive data from JSON.
// Field class is a response and this is not model class
public class Field{
private String name;
//getter and seter
}
#PostMapping("/test")
public void getFields(RequestEntity<Map<String,String>> entity){
Field field = new Field();
field.setName(entity.getBody().get("name"));
System.out.println(field);
}
It is possible to fetch the data without having it mapped to an entity by using nativeQuery and a Tuple class.
List<Tuple> resultList = entityManager
.createNativeQuery("select id as id, street as street, city as city, country as country from address ", Tuple.class
.getResultList();
Tuple firstResult = resultList.get(0);
assertEquals(1L , ((Number) firstResult.get("id")).longValue());
For more information you can read Vlad Mihalcea's blog post
https://vladmihalcea.com/the-best-way-to-map-a-projection-query-to-a-dto-with-jpa-and-hibernate/

Have one Rest repository json with everything and one with fields excluded

I have two entities: Book and Category and a repository for both. In the controller, I have set up the methods correctly as such:
#RequestMapping(value="/books", method = RequestMethod.GET)
#CrossOrigin
public #ResponseBody List<Book> bookListRest() {
return (List<Book>) bookRepository.findAll();
}
This obviously shows all books and every field in the entity that isn't #JsonIgnore'd. The problem is, I need to have:
One page with Book data (book name, author name, isbn..) without category
One page with Category data (Category name) without books
One page with Everything (book data along with categories where they belong in)
How can one accomplish this?
I somehow need to in a way ignore #jsonignore on some occasions. Should I make a new entity that extends say, Question and also make a repository for that? Surely that can't be the correct way to do this.
As khalid Ahmed Said you can use costum dtos or you can add Filters to ignore specific fields in Jackson. First, we need to define the filter on the java object:
#JsonFilter("myFilterBook")
public class Book{
...
}
#JsonFilter("myFilterCategory")
public class Category{
...
}
Before you return your ResponseBody you try to use ObjectMapper (Jackson):
The case of one page with Book data (book name, author name, isbn..) without category:
ObjectMapper mapper = new ObjectMapper();
SimpleBeanPropertyFilter theFilter = SimpleBeanPropertyFilter
.serializeAllExcept("category");
FilterProvider filters = new SimpleFilterProvider()
.addFilter("myFilterBook", theFilter);
String dtoAsString = mapper.writer(filters).writeValueAsString(book);
You can do the same think by putting what you want o ignore for the other example.
And for more details to ignore field during marshalling with jackson you can check here
What about using DTOs data transfer objects
you can create multiple DTOs to use them in the response of your API
DTO is a pojo class that customize the returning data from your entity
public class BookWithoutCategoryDTO {
private String name;
private String authorName;
.....
/// and make setters and getters for them
}
public class BookWithCategoryDTO {
private String name;
private String authorName;
private String category;
.....
/// and make setters and getters for them
}
and create your custom mapper to convert from Book to BookDTO

How to write a RestController to update a JPA entity from an XML request, the Spring Data JPA way?

I have a database with one table named person:
id | first_name | last_name | date_of_birth
----|------------|-----------|---------------
1 | Tin | Tin | 2000-10-10
There's a JPA entity named Person that maps to this table:
#Entity
#XmlRootElement(name = "person")
#XmlAccessorType(NONE)
public class Person {
#Id
#GeneratedValue
private Long id;
#XmlAttribute(name = "id")
private Long externalId;
#XmlAttribute(name = "first-name")
private String firstName;
#XmlAttribute(name = "last-name")
private String lastName;
#XmlAttribute(name = "dob")
private String dateOfBirth;
// setters and getters
}
The entity is also annotated with JAXB annotations to allow XML payload in
HTTP requests to be mapped to instances of the entity.
I want to implement an endpoint for retrieving and updating an entity with a given id.
According to this answer to a similar question,
all I need to do is to implement the handler method as follows:
#RestController
#RequestMapping(
path = "/persons",
consumes = APPLICATION_XML_VALUE,
produces = APPLICATION_XML_VALUE
)
public class PersonController {
private final PersonRepository personRepository;
#Autowired
public PersonController(final PersonRepository personRepository) {
this.personRepository = personRepository;
}
#PutMapping(value = "/{person}")
public Person savePerson(#ModelAttribute Person person) {
return personRepository.save(person);
}
}
However this is not working as expected as can be verified by the following failing test case:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
public class PersonControllerTest {
#Autowired
private TestRestTemplate restTemplate;
private HttpHeaders headers;
#Before
public void before() {
headers = new HttpHeaders();
headers.setContentType(APPLICATION_XML);
}
// Test fails
#Test
#DirtiesContext
public void testSavePerson() {
final HttpEntity<Object> request = new HttpEntity<>("<person first-name=\"Tin Tin\" last-name=\"Herge\" dob=\"1907-05-22\"></person>", headers);
final ResponseEntity<Person> response = restTemplate.exchange("/persons/1", PUT, request, Person.class, "1");
assertThat(response.getStatusCode(), equalTo(OK));
final Person body = response.getBody();
assertThat(body.getFirstName(), equalTo("Tin Tin")); // Fails
assertThat(body.getLastName(), equalTo("Herge"));
assertThat(body.getDateOfBirth(), equalTo("1907-05-22"));
}
}
The first assertion fails with:
java.lang.AssertionError:
Expected: "Tin Tin"
but: was "Tin"
Expected :Tin Tin
Actual :Tin
In other words:
No server-side exceptions occur (status code is 200)
Spring successfully loads the Person instance with id=1
But its properties do not get updated
Any ideas what am I missing here?
Note 1
The solution provided here is not working.
Note 2
Full working code that demonstrates the problem is provided
here.
More Details
Expected behavior:
Load the Person instance with id=1
Populate the properties of the loaded person entity with the XML payload using Jaxb2RootElementHttpMessageConverter or MappingJackson2XmlHttpMessageConverter
Hand it to the controller's action handler as its person argument
Actual behavior:
The Person instance with id=1 is loaded
The instance's properties are not updated to match the XML in the request payload
Properties of the person instance handed to the controller's action handler method are not updated
this '#PutMapping(value = "/{person}")' brings some magic, because {person} in your case is just '1', but it happens to load it from database and put to ModelAttribute in controller. Whatever you change in test ( it can be even empty) spring will load person from database ( effectively ignoring your input ), you can stop with debugger at the very first line of controller to verify it.
You can work with it this way:
#PutMapping(value = "/{id}")
public Person savePerson(#RequestBody Person person, #PathVariable("id") Long id ) {
Person found = personRepository.findOne(id);
//merge 'found' from database with send person, or just send it with id
//Person merged..
return personRepository.save(merged);
}
wrong mapping in controller
to update entity you need to get it in persisted (managed) state first, then copy desired state on it.
consider introducing DTO for your bussiness objects, as, later, responding with persisted state entities could cause troubles (e.g. undesired lazy collections fetching or entities relations serialization to XML, JSON could cause stackoverflow due to infinite method calls)
Below is simple case of fixing your test:
#PutMapping(value = "/{id}")
public Person savePerson(#PathVariable Long id, #RequestBody Person person) {
Person persisted = personRepository.findOne(id);
if (persisted != null) {
persisted.setFirstName(person.getFirstName());
persisted.setLastName(person.getLastName());
persisted.setDateOfBirth(person.getDateOfBirth());
return persisted;
} else {
return personRepository.save(person);
}
}
Update
#PutMapping(value = "/{person}")
public Person savePerson(#ModelAttribute Person person, #RequestBody Person req) {
person.setFirstName(req.getFirstName());
person.setLastName(req.getLastName());
person.setDateOfBirth(req.getDateOfBirth());
return person;
}
The issue is that when you call personRepository.save(person) your person entity does not have the primary key field(id) and so the database ends up having two records with the new records primary key being generated by the db. The fix will be to create a setter for your id field and use it to set the entity's id before saving it:
#PutMapping(value = "/{id}")
public Person savePerson(#RequestBody Person person, #PathVariable("id") Long id) {
person.setId(id);
return personRepository.save(person);
}
Also, like has been suggested by #freakman you should use #RequestBody to capture the raw json/xml and transform it to a domain model. Also, if you don't want to create a setter for your primary key field, another option may be to support an update operation based on any other unique field (like externalId) and call that instead.
For updating any entity the load and save must be in same Transaction,else it will create new one on save() call,or will throw duplicate primary key constraint violation Exception.
To update any we need to put entity ,load()/find() and save() in same transaction, or write JPQL UPDATE query in #Repository class,and annotate that method with #Modifying .
#Modifying annotation will not fire additional select query to load entity object to update it,rather presumes that there must be a record in DB with input pk,which needs to update.

SpringBoot: Is this correct way to save a new entry which has ManyToOne relationship?

I have two entities Person and Visit
Person has OneToMany relationship with Visit.
I was wondering if I want to save an new entry of Visit, and interm of using RestController. Is my approach correct? Or is there another way which is more efficient?
So I have the following controller which takes a VisitModel from the RequestBody, is it a correct way to call it like so?
VisitModel has the ID of person, and the needed properties for the Visit entity. I use the ID of person to look up in the personRepository for the related Person entry, whereafter I issue it to a new instance of Visit and then use the visitRepository to save it.
#RequestMapping(value="", method=RequestMethod.POST)
public String checkIn(#RequestBody VisitModel visit) {
Person person = personRepository.findById(visit.personId);
Visit newVisit = new Visit(visit.getCheckIn, person);
visitRepository.save(newVisit);
return "success";
}
The Visit entity looks as following
#Entity
public class Visit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonProperty("check_in")
private Date checkIn;
#JsonProperty("check_out")
private Date checkOut;
#ManyToOne
#JoinColumn(name="personId")
private Person person;
public Visit(Date checkIn, Person person) {
this.checkIn = checkIn;
this.person = person;
}
public Date getCheckIn() {
return checkIn;
}
public void setCheckIn(Date checkIn) {
this.checkIn = checkIn;
}
public Date getCheckOut() {
return checkOut;
}
public void setCheckOut(Date checkOut) {
this.checkOut = checkOut;
}
public Person getPerson() {
return person;
}
}
I want to know of the following approach is correct. Or is there another way which is better?
You don't need to get a Person from the database to associate it with a Visit, of course. Because of, you need to have only id of a Person to save it in the foreign key column personId.
If you use JPA EntityManager
Person person = entityManager.getReference(Person.class, visit.personId);
for Hibernate Session
Person person = session.load(Person.class, visit.personId);
This methods just create a proxy and don't do any database requests.
With Hibernate Session I used new Person(personId) as #MadhusudanaReddySunnapu suggested. Everything worked fine.
What is the difference between EntityManager.find() and EntityManger.getReference()?
Hibernate: Difference between session.get and session.load
Yes, that seems to me to be the standard way to map a bidirectional relationship. EDIT: The personId column points to the "id" field of the Person entity.Eg:
#Id
private Long id;
UPDATE: 1: The VisitModel is a 'DTO' or Data Transfer Object. Any separate package is fine. You could consider putting them into a separate jar, so that anyone using your API (with java) can use the jar to create the data before making the call. 2) The way you save it is fine as far as I can see.

Canonical _links with Spring HATEOAS

We're building a RESTful web service similiar to the spring.io guide "Accessing JPA Data with REST". To reproduce the sample outputs below, it suffices to add a ManyToOne-Relation to Person as follows:
// ...
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
#ManyToOne
private Person father;
// getters and setters
}
A GET request after adding some sample data yields:
{
"firstName" : "Paul",
"lastName" : "Mustermann",
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/1"
},
"father" : {
"href" : "http://localhost:8080/people/1/father"
}
}
}
But, given Paul's father is stored with ID 2, our desired result would be the canonical url for its relation:
// ...
"father" : {
"href" : "http://localhost:8080/people/2"
}
// ...
This of course will cause problems if father is null on some persons (OK, this does not make much sense here... ;)), but in this case we would like to not render the link in JSON at all.
We already tried to implement a ResourceProcessor to achieve this, but it seems that by the time the processor is called the links are not populated yet. We managed to add additional links pointing to the desired canonical url, but failed to modify the somehow later added links.
Question: Is there a generic approach to customize the link generation for all resources?
To clarify our need for canonical URLs: We use the SproutCore Javascript framework to access the RESTful web service. It uses an "ORM-like" abstraction of data sources for which we've implemented a generic handler of the JSON output Spring produces. When querying for all persons, we would need to send n*(1+q) requests (instead of just n) for n persons with q relations to other persons to sync them to the client side data source. This is because the default "non-canonical" link contains absolutely no information about a father being set or the father's id. It just seems that this causes a huge amount of unnecessary requests which could be easily avoided if the initial response would contain a little more information in the first place.
Another solution would be to add the father's id to the relation, e.g.:
"father" : {
"href" : "http://localhost:8080/people/1/father",
"id" : 2
}
There is a discussion somewhere Spring Data Rest team explained why properties are rendered as links that way. Having said you could still achieve what you like by suppressing link generated by SDR and implementing ResourceProcessor. So your Person class would look like below. Note the annotation #RestResource(exported = false) to suppress link
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
#ManyToOne
#RestResource(exported = false)
private Person father;
// getters and setters
}
Your ResourceProcessor class would look like
public class EmployeeResourceProcessor implements
ResourceProcessor<Resource<Person>> {
#Autowired
private EntityLinks entityLinks;
#Override
public Resource<Person> process(Resource<Person> resource) {
Person person = resource.getContent();
if (person.getFather() != null) {
resource.add(entityLinks.linkForSingleResour(Person.class, person.getFather().getId())
.withRel("father"));
}
return resource;
}
}
The above solution works only if father value is eagerly fetched along with Person. Otherwise you need to have property fatherId and use it instead of father property. Don't forget to use Jackson #ignore... to hide fatherId in response JSON.
Note: I haven't tested this myself but guessing it would work
Since I had the same problem, I created a Jira issue at spring-data-rest:
https://jira.spring.io/browse/DATAREST-682
If enough people vote for it, perhaps we can convince some of the developers to implement it :-).
It's weird that you're pushing to display the canonical link. Once that resource at /father is retrieved the self link should be canonical...but there's really not a good reason to force the father relationship to be canonical...maybe some cacheing scheme?
To your specific question...you're relying on auto-generated controllers so you've given up the right to make decisions about a lot of your links. If you were to have your own PersonController than you would be more in charge of the link structure. If you created your own controller you could then use EntityLinks https://github.com/spring-projects/spring-hateoas#entitylinks with the Father's ID..IE
#Controller
#ExposesResourceFor(Person.class)
#RequestMapping("/people")
class PersonController {
#RequestMapping
ResponseEntity people(…) { … }
#RequestMapping("/{id}")
ResponseEntity person(#PathVariable("id") … ) {
PersonResource p = ....
if(p.father){
p.addLink(entityLinks.linkToSingleResource(Person.class, orderId).withRel("father");
}
}
}
But that seems like a lot of work to just change the URL

Resources