I am using spring data rest in my project and have question about
application/merge-patch+json request.
While application/json-patch+json request works good i have troubles with merge-patch.
For example i have nested object without repository like this
"student":{
"id":1,
"address":{
"id":1,
"street":2,
"building":2
}
}
And i am sending PATCH, application/merge-patch+json to students/1
with this payload
{
"address":{
"street":3
}
}
I am getting this result
"student":{
"id":1,
"address":{
"id":2,
"street":3,
"building":null
}
}
So spring data rest just created new address object instead of merging.
Java code is like this
#Entity
#Table(name = "Student")
public class Student {
#Id
#GeneratedValue
private long studentId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "addressId")
private Address address;
//getters and setters
}
Address class:
#Entity
#Table(name = "Address")
public class Address {
#Id
#GeneratedValue
private long addressId;
private String street;
private String building;
//getters and setters
}
Also student have rest repository and address have not
My question is how i can achieve correct behavior in merging patch requests in spring data rest?
Though it is delayed response, it can be helpful to others who has same issue
In the input JSON, address id is missing.
{
"address":{
"id" : 1,
"street":3
}
}
Related
I have 2 entities/model. Department and Employee
Relationship is 1 Department has MANY Employee.
My understanding of how to go about implementing a Bi-directional relationship of entities is:
Identify the Parent and Child
Identify the owner of relationship (in this case, Department owns Employee). Then add mappedBy='fieldname'
Annotate the both entities with inverse. #OneToMany on 1st entity and #ManyToOne on the 2nd entity
Below is how I designed the app.
Model
Department
#Entity
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String departmentName;
#OneToMany(mappedBy = "department")
private Set<Employee> employees;
//getters and setters...
}
Employee
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String middleName;
private String lastName;
#ManyToOne
#JsonIgnore
private Department department;
}
Now, if I GET department by id (e.g. localhost:8080/department/1)
I'll get :
{
"id": 1,
"departmentName": "Accounting",
"employees": [
{
"id": 1,
"firstName": "John",
"middleName": "Johnny",
"lastName": "Doe"
}
]
}
which is fine because I can see the Employee(s) who belongs to a specific Department.
However, on the employee side, if I GET employee by id (e.g. localhost:8080/employee/1)
I would get a json result which only displays the employee name BUT does NOT include the Department where the Employee belongs.
{
"id": 1,
"firstName": "John",
"middleName": "Johnny",
"lastName": "Doe"
}
I had to add #JsonIgnore to department field in Employee class to avoid stackoverflow/endless nested json serialization when doing a GET department (e.g. localhost:8080/department/1)
I have 2 questions :
How do I also display the department information when doing a GET employee by id?
Do I need to create a custom query in the Repository interface using #Query instead? At least for the purpose of displaying the Department name along with Employee when I do a GET Employee by id?
Thank you.
You have to use DTOs in your project. DTO stand for data transfer object. As the name suggest it carries data between program layers, APIs, etc. - a read on dto.
Department dto may look like this for example:
public class DepartmentDto {
private Long id;
private String name;
//getters setters
}
Employee dto:
public class EmployeeDto {
private Long id;
private String firstName;
private DepartmentDto department;
//getters setters
}
Then you controller method to get employee by id may look like this:
#GetMapping("/employee/{employeeId}")
public EmployeeDto findEmployeeById(#PathVariable Long employeeId) {
Employee employee = this.employeeRepository.findById(employeeId).orElseThrow(() -> new RuntimeException("Employee not found"));
DepartmentDto departmentDto = new DepartmentDto();
departmentDto.setId(employee.getDepartment().getId());
departmentDto.setName(employee.getDepartment().getDepartmentName());
EmployeeDto employeeDto = new EmployeeDto();
employeeDto.setId(employee.getId());
employeeDto.setFirstName(employee.getFirstName());
employeeDto.setDepartment(departmentDto);
return employeeDto;
}
Like this you won't get stackoverflow. Also mapping entity into a dto normally is not done by hand as in this example, but by third party libraries. You can read for some of them here - java mapping libraries.
Sometimes you may get stuck with circular references in DTO's as well. You can handle them using #JsonIdentityInfo for example. In this question you can find several options how to handle them if you need to.
I am stuck on given scenario:
There are three entity
a) Bill (many to one relationship with vendor) [bi-directional]
b) Vendor (one to many relationship with both vendor and vendorbank)
c) VendorBank (many to one relationship with vendor)[uni-directional]
Bills : Showing limited fields
#Entity
#Getter
#Setter
#ToString
#NoArgsConstructor
#Table(name="bill_details")
public class Bills {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="bill_id")
private int bill_id;
#Column(name="bill_no")
private String billno;
#ManyToOne(cascade = {CascadeType.MERGE})
#JoinColumn(name="b_vendor_id")
private Vendors vendors;
/* Args contructor code here */
#JsonManagedReference
public Vendors getVendors() { return vendors; }
VendorBank:Showing limited fields
public class VendorBank {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="v_bank_id")
private int vendorBankId;
#Column(name="v_acc_no")
private String accountNumber;
#Column(name="v_vendor_id")
private int vendor_id;
/*Both constructor code here*/
}
Vendor Class:Showing imp fields only
/*Lombok code here*/
public class Vendors {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="vendor_id")
private int vendor_id;
#Column(name="vendor_name")
private String vendor_name;
#OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name="v_vendor_id")
private List<VendorBank> vendorBank;
#OneToMany(mappedBy = "vendors",cascade = {CascadeType.DETACH,CascadeType.MERGE,
CascadeType.PERSIST,CascadeType.REFRESH})
private List<Bills> bills;
#JsonBackReference
public List<Bills> getBills() {
return bills;
}
//#JsonManagedReference()
// #JsonIgnore
public List<VendorBank> getVendorBank() {
return vendorBank;
}
Output:
{
"bill_id": 102,
"billno": "B-858",
"vendors": {
"vendor_id": 3,
"vendor_name": "ABC Company",
"vendorBank": [
{
"vendorBankId": 14,
"accountNumber": "502998745002",
"vendor_id": 3
}
]
}
}
1.When I call vendor endpoint I get data from vendor + vendor bank as desired.
2.But When I call the Bill endpoints then I get data from Bill + vendor + vendorbank as above. I don't want vendor bank to come.[If I use JsonIgnore on vendorbank then i get the correct output but then above point 1 goes wrong]
OK, so what you want to achieve is to include some data from the entity in one context and not include it in another context.
I don't think you can do this by using purely annotations you put on entities, since they don't have the calling context.
So, what can we do here ?
We can use Jackson Mixins. For example:
class YourClass {
public int ignoreThis() { return 0; }
}
With this Mixin
abstract class MixIn {
#JsonIgnore abstract int ignoreThis(); // we don't need it!
}
With this:
objectMapper.getSerializationConfig().addMixInAnnotations(YourClass.class, MixIn.class)
And you can then use this to serialize the object into json in your controller (where you get it to the service) and then add it as the response body and send to the user.
It would be ideal if you could somehow configure the ObjectMapper, which is used by Spring behind the scenes to do this, however, this is not possible ( at least easily ), because you need to tie this objectMapper to your specific controller only and not all controllers.
I am using Spring Boot with simple REST API on server. I have 2 resources: users and articles. Here is Article class:
#Entity(name = "article")
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(nullable = false)
private String text;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
public User getUser() { // I need this method.
return user;
}
// All other setters and getters.
}
Now, if I fetch some article by its ID using REST API, response looks like:
{
"id": 5,
"text": "Content of article...",
"user": {
"id": 1,
"username": "user#email.com",
"password": "$2a$10$CbsH93d8s5NX6Gx/N5zcwemUJ7YXXjRIQAE2InW9zyHlcTh6zWrua"
}
}
How can I exclude user field from response? If I remove Article.getUser method, everything works fine and response looks like:
{
"id": 5,
"text": "Content of article..."
}
This is desired result. However, I need Article.getUser, because e. g. if someone want delete article, I need check, if author of the request is author of the article, so user cannot delete articles of other users.
You can use #JsonIgnore on like below:
#JsonIgnore
private User user;
The other way is Projection in which you have more control on what should be included in response.
I have a problem using JPA and RelationsShips One to Many with Jackson and Spring Rest ... I try to find multiples solutions but anything is working for me , and I don't kno where is the problem.
For example I have a table Team that has One to Many/Many To One relationship
I have two repository one for Team and another for Player
Team >>> has Many >> Player
Player >>> many to one >> Team
My entity Team has the following content
#Entity
#Table(name = "teams")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Team {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private long teamId;
private String abbreviation;
private String team;
private String simpleName;
private String logo;
#OneToMany(cascade = {CascadeType.ALL,CascadeType.PERSIST,CascadeType.MERGE}, mappedBy = "team")
#Column(nullable = false)
private List<Player> players;
Theirs getters/setter , hashcodes and string similars.
On the other hand the entity Player
#Entity
#Table(name = "player")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teams_id", nullable=true)
private Team team;
private String name;
So , I have the typical get call in a controller in a repository.
#RestController
#RequestMapping("/api/public/team")
public class TeamController {
#Autowired
private TeamRepository teamRepository;
#Autowired
private GenericMethods genericMethods;
#GetMapping(value = "/{id}")
public Team getPersona(#PathVariable("id") int id) {
return teamRepository.findOne(genericMethods.toLong(id));
}
And repository
#Repository
public interface TeamRepository extends JpaRepository<Team, Long> {
}
Now , when I call this endpoint I receive the following answer and I think that is incorrect , I only need a List With Players
{
"id":2,
"teamId":0,
"abbreviation":null,
"team":null,
"simpleName":"Betis",
"logo":null,
"players":[
{
"id":1,
"team":2,
"category":{
"id":1,
"nombre":"juvenil a",
"language":null,
"description":null,
"league":[
],
"players":[
1,
{
"id":2,
"team":2,
"category":1,
"name":"hulio"
}
]
},
"name":"pepe"
},
2
]
}
I need to acces at information with Player and Team so I can't use #JsonIgnoreProperties
Could anyone help to solve this problem ?
Depending on what you really want to achieve you may try different options. I'm not sure if you're using (or intending to use) spring-data-rest or not.
1. Dedicated repository
Spring data rest will embed the related entities if they don't have their own repository. Try creating public interface PlayersRepository extends JpaRepository...
2. Lazy loading
Why are you using FetchType.EAGER ? Try without it.
3. Projections
Projections are only applicable to lists, not to individual entities (i.e. not explicitly what you're asking for). You can hide players from the Teams collection even if it was returned by default like so:
#Projection(name = "noPlayers", types = { Team.class })
public interface TeamWithoutPlayers {
Long getId();
long getTeamId();
String getAbbreviation();
String getTeam();
String getSimpleName();
String getLogo();
}
More info - Spring Data Rest Projections
4. Ignore during serialization in Team Entity using #JsonIgnore
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "teams_id", nullable=true)
private Team team;
Final thought
With spring-data-rest you can extend a CrudRepository instead of JpaRepository and access the item directly through the repository. That way you don't need to write a controller.
I need to know is there any possibility to manage several entities with one crud repository in spring data rest.
Example :
Library entity
#Entity
public class Library {
#Id
#GeneratedValue
private long id;
#Column
private String name;
#OneToMany(mappedBy = "library")
private List<Book> books;
}
Book entity
#Entity
public class Book {
#Id
#GeneratedValue
private long id;
#Column(nullable=false)
private String title;
#ManyToOne
#JoinColumn(name="library_id")
private Library library;
}
My requirement is
public interface LibraryRepository extends CrudRepository<Library, Long> { }
is to have only this repository to manage both library and the book entities.
I tried inserting and it is working well so far. but other operations are not supported by this approach. is there any other approach rather than having two crud repositories to do this.
Of course you can. Just correct a little your Library like this:
#OneToMany(mappedBy = "library", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Book> books;
Then you can create/update your Library and its books with this payload:
{
"name": "library1",
"books": [
{
"title": "book1"
},
{
"title": "book2"
}
]
}
Code example of the Spring Data author.
My example.
You cannot do that simply, since a bean will be created for each repository and this bean should be instantiated with entity type defined