I have written a test application in spring boot. Employees have a relation to a department. CRUD works, but I'm not sure I'm doing it the correct way.
When I will create a new employee I have to send the following post request
"id": 3,
"firstname": "John",
"lastname": "Doe",
"salary": 50000,
"department": {
"id": 2,
"name": "Sales"
}
}
This is the employee class:
#Entity
public class Employee {
#Id
#GeneratedValue
private Long id;
private String firstname;
private String lastname;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "department_id", referencedColumnName="id")
private Department department;
private int salary;
This is the create method in the EmployeeController:
#PostMapping("/employees")
public Employee create(#RequestBody Employee employee) {
return employeeService.add(employee);
}
The department entries already exists.
Is it possible to create an employee without filling the complete relation
(department)?
I would like to add the department id only. But if I do this, the name field in the json data is empty (get request)
id 3
firstname "John"
lastname "Doe"
department
id 2
name null
salary 50000
Is there any better approach?
You are taking an Entity as a request body which is not the correct approach. You can take Some VO as Request Object and then convert to Entity inside a service.
For E.g
EmployeeVO{
private String firstname;
private String lastname;
List<Integer> departments;
}
Also, you can use Jackson annotations like
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(JsonInclude.Include.NON_NULL) to ignore unknown attributes and include not null attributes.
Related
I've two entities issue and user I would like to assign the user to an issue but when trying to do that using this JSON the user_id in the issues table is NULL.
#Data
#Entity
#Table(name = "issues")
public class Issue {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String number;
#Column
private String title;
#Column
private String description;
#Column
#Enumerated(EnumType.STRING)
private State state;
#JsonIgnore
#CreationTimestamp
#Column
private Timestamp createDate;
#JsonIgnore
#UpdateTimestamp
#Column
private Timestamp modifyDate;
#ManyToOne(targetEntity = User.class)
#JoinColumn
private User user;
public Issue() {
}
#Data
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String fullName;
#Column
private String username;
#Column
private String email;
#Column
private String password;
#Column
#Enumerated(EnumType.STRING)
private Role role;
#JsonIgnore
#CreationTimestamp
#Column
private Timestamp createDate;
#JsonIgnore
#UpdateTimestamp
#Column
private Timestamp modifyDate;
public User() {
}
public User(String fullName, String username, String email, String password, Role role) {
this.fullName = fullName;
this.username = username;
this.email = email;
this.password = password;
this.role = role;
}
Firstly I've created an user without any issues, but do not know how to manage with the issue here is the JSON I am using via postman.
{
"number": "3",
"title": "Create an working service",
"description": "The problem is that we do not have an working service.",
"state": "NEW",
"user_id": "1"
}
This part is responsible for saving the issue.
public void save(Issue issue) {
if (issue == null)
return;
Issue actual = issueRepository.findByNumber(issue.getNumber());
if (actual != null) {
actual.setNumber(issue.getNumber());
actual.setTitle(issue.getTitle());
actual.setDescription(issue.getDescription());
actual.setState(issue.getState());
actual.setUser(issue.getUser());
issueRepository.save(actual);
} else {
issueRepository.save(issue);
}
}
In the controller I just have #Valid #RequestBody Issue issue and the service saves the issue.
What you're sending as JSON is not an Issue.
First because it does not represent a database-persistent issue (and that's what the Issue class is for), but rather the JSON structure the public API expects from its clients.
Second because an Issue doesn't have any field named user_id, but your JSON does. So use a different class than Issue, which actually matches with the JSON structure that the API expects, and thus has a user_id property.
Then use this user_id to find the User by its ID using the UserRepository, and set that User into the Issue you're creating.
I would rename user_id to userId to respect the Java conventions, too.
You need to post json like this.
{
"number": "3",
"title": "Create an working service",
"description": "The problem is that we do not have an working service.",
"state": "NEW",
"user":{
"id":1
}
}
Here issue have join column of user so with user entity you have to pass userid.
Your save would be like this.
public void save(Issue issue) {
if (issue == null)
return;
Issue actual = issueRepository.findByNumber(issue.getNumber());
if (actual != null) {
actual.setNumber(issue.getNumber());
actual.setTitle(issue.getTitle());
actual.setDescription(issue.getDescription());
actual.setState(issue.getState());
actual.setUser(userRepositoy.findById(issue.getUser().getId()));
issueRepository.save(actual);
} else {
issueRepository.save(issue);
}
}
user_id key was not presented in your Item class.
As per entity mapping,
Please change your JSON to this.
{
"number": "3",
"title": "Create an working service", "description": "The problem is that we do not have an working service.",
"state": "NEW",
"user":{
"fullName":"USER",
"email":"user#gmail.com",
"username":"user"
}
}
I have two entity table called appointment and service. One appointment can be many services. Services already store in the database. We can newly add appointment. When add new appointment we selected added services from the drop down menu. Can add many services with one appointment. I need to store new record on appointment table and store relevant appointment id and services id's on another join table. Here attached image with my problem.
Already I tried many ways for do this. Bellow is one attempt.
Here is appointment class
#Entity
#Table(name="appointment")
public class Appointment extends AbstractPersistable<Long> implements Serializable {
#OneToMany
#JoinTable(name = "service_x_appointment", joinColumns = #JoinColumn(name = "appointment_id"),
inverseJoinColumns = #JoinColumn(name = "beautyservice_id"))
Set<BeautyService> beautyServices;
private String type;
private Date date;
private String time;
private String description;
private int approval;
#ManyToOne
#JoinColumn(name = "userid")
private User user;
//getter and setter
}
Here is BeautyService class
#Entity
#Table(name="beauty_service")
public class BeautyService extends AbstractPersistable<Long> {
private String serviceName;
private String timeDuration;
private String amount;
//getter and setter
}
Here is appointment controller class code,
#RequestMapping(value="/createAppointment",method = RequestMethod.POST)
public String createAppointment(#RequestBody Appointment appointment){
String response = null;
response = appointmentService.save(appointment);
return response;
}
Here is appointment service class code
public String save(Appointment appointment) {
appoinmentRepository.save(appointment);
return "Appointment added successfully";
}
Here is the my request body.
{
"type":"Type02",
"date":null,
"time":"20:56",
"description":"Hellow World",
"approval":0,
"user":{
"id":2,
"name" : "Alex",
"telephone" : "0774466886",
"age":21,
"email": null
},
"beautyServices" : [
{
"id":1,
"serviceName":"hair strate",
"timeDuration" : "02 Hours",
"amount" : 5000
},
{
"id":2,
"serviceName":"Eye brows",
"timeDuration" : "02 Hours",
"amount" : 5000
},
{
"id":3,
"serviceName":"Near cutting",
"timeDuration" : "02 Hours",
"amount" : 5000
}
]
}
Why not record in the join table? Only appointment table.
You can do it in multiple ways. One has been specified #Aritra Paul, which is actually Bidirectional representation of OneToMany mapping.
I think you want to use UniDirectional representation. In that case you won't have to use mappedBy.
Just create your entities like below:
#Entity
#Table(name="appointment", schema="ADMIN")
public class Appointment implements Serializable {
#OneToMany
#JoinColumn(name = "appointment_id")
#JoinTable(name = "service_appointment", joinColumns = #JoinColumn(name = "appointment_id"),
inverseJoinColumns = #JoinColumn(name = "service_id"))
Set<Service> services;
}
#Entity
public class Service {
// Some Properties. No need to specify reference of Appointment here.
}
If you define your entities like that you will have a join table like this
+----------------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+------------+------+-----+---------+-------+
| appointment_id | bigint(20) | NO | MUL | NULL | |
| service_id | bigint(20) | NO | PRI | NULL | |
+----------------+------------+------+-----+---------+-------+
Hope this helps!!
You definitely shouldn't create Joined table entity as you mentioned as it's more the underlying database representation than the object oriented one.
You can achieve the join table by defining something like:
#Entity
#Table(name="appointment", schema="ADMIN")
public class Appointment implements Serializable {
//...
#OneToMany(mappedBy="appointment")
#JoinTable(name="Join_Table")
Set <ServiceT> service;
use relation mapping ManyToOne or OneToMany according to your table.
#Entity
#Table(name="service", schema="ADMIN")
public class ServiceT implements Serializable {
//...
#ManyToOne
Appointment appointment;
If you want to explicitely set your column name you can use
#JoinColumn
annotation.
Can you please check if the beautyServices are actually being bound from the #RequestBody properly.
The property name is beautyServices while in Json it says "service".
In the Json it should say "beautyServices" instead of "service".
Also, check if the ids 1, 2, 3 already pre-exists on the beautyServices table.
If Not, if you want those to get inserted as well by saving appointment, you need to add CASCADETYPE.PERSIST.
I'm having troubles getting into Spring Data
I got entity Product which has Category (I'm guessing relation type is right? Product has one Category, Category has many products)
#Entity
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "category_id")
private Category category;
}
#Entity
class Category implements Serializable {
public Category() {
}
public Category(String name){
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "category_id")
private Long id;
#Column(unique = true)
private String name;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Product> products;
}
Now I try to add new Product via Postman, calling my RestController
#PostMapping("/add")
public Product addProduct(#Valid #RequestBody Product product){
return repository.save(product);
}
With 2 following requests
{
"name" : "pork",
"category" : "meat"
}
{
"name" : "chicken",
"category" : "meat"
}
In the result I got 2 following responses
{
"id": 1,
"name": "pork",
"category": {
"id": 1,
"name": "meat",
"products": null
}
}
{
"id": 2,
"name": "chicken",
"category": {
"id": 2,
"name": "meat",
"products": null
}
}
And on database I actually got 2 categories named "meat" (even tho it should be unique. What's more, do I actually need Set<Product> in my Category class? TBH, Category has no intrest in that at all.
There are a few problems with your code.
You are directly using entity as the rest API model. Suggest to create a separate ProductModel with only fields that client has access to.
You mixing category creation together inside product creation, but your category in the request only contains name. To the backend, unless you check whether such a category exists, it's always treated as a new category.
Before you call repository.save, you need let category knows what's the product inside. In your current code, only product know its category.
You don't need Set products in your Category class (and it's recommended to use only #ManyToOne).
I have a SpringBoot app and am using the CrudRepository to persist objects to the DB. I have an Product entity class which has a many to one relationship with a Vendor entity. I'm passing JSON that includes the details of both Product and the embedded vendor, but I'd ultimately like to pass just the Product details and include the vendorID. Is there some annotation that can resolve this for me?
Here is my code:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne
#JoinColumn(name="VendorID")
private Vendor vendor;
#Column(name="partnumber")
#JsonProperty("PartNumber")
private String vendorPartNumber;
#JsonProperty("Name")
private String name;
#Entity
public class Vendor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#JsonProperty("Code")
private String code;
#JsonProperty("Name")
private String name;
....
#OneToMany(mappedBy = "vendor", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JsonIgnore
private List<Product> products;
And the JSON I'm passing (which works) looks like this:
{
"Vendor": {
"Id":1,
"Code": "BB-1001",
"Name": "Best Buy",
"Address": "100 Best Buy Street",
"City": "Louisville",
"State": "KY",
"Zip": "40207",
"Phone": "502-111-9099",
"Email": "geeksquad#bestbuy.com",
"IsPreApproved": "false"
},
"PartNumber":"TEST01",
"Name":"Test Product 01",
"Price":99.99
}
I'd ultimately like to remove the JSON object reference to vendor and replace with VendorId.
Solution by annotation
You can use #JsonSerialize to provide a custom serialization to achieve what you want.
#Entity
public class Product {
...
#JsonSerialize(using = VendorToIdSerializer.class)
#ManyToOne
#JoinColumn(name="VendorID")
private Vendor vendor;
...
}
public class VendorToIdSerializer extends JsonSerializer<Vendor> {
#Override
public void serialize(Vendor vendor, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
String vendorIdAsString = String.valueOf(vendor.getId());
jsonGenerator.writeObject(vendorIdAsString);
}
}
Solution by DTO
However, as others have pointed out, the cleaner approach would be to create a DTO class which serves the role of the "view" of Product.
public class ProductView {
private final int vendor;
...
public ProductView(Vendor vendor) {
this.vendor = vendor.getId();
...
}
// getters
}
My issue was answered by using the #JsonBackReference and #JsonManagedReference annotations. I'm going to continue to research the DTOs as suggested above though. Thank-you!
I would also suggest to create a DTO that fit your needs.
With JPA you can create DTO instances using the constructor expression:
Here an example from a great article written by Vlad Mihalcea
https://vladmihalcea.com/the-best-way-to-map-a-projection-query-to-a-dto-with-jpa-and-hibernate/
List<PostDTO> postDTOs = entityManager.createQuery(
"select new " +
" com.vladmihalcea.book.hpjp.hibernate.query.dto.projection.jpa.PostDTO(" +
" p.id, " +
" p.title " +
" ) " +
"from Post p " +
"where p.createdOn > :fromTimestamp", PostDTO.class)
.setParameter( "fromTimestamp", Timestamp.from(LocalDateTime.of( 2016, 1, 1, 0, 0, 0 )
.toInstant( ZoneOffset.UTC ) ))
.getResultList();
I am developing a web project using Spring Boot, Spring Data JPA and Spring Data Rest technologies. I am able to setup everything successfully and able to get JSON of a simple POJOs. I have customized two classes to have OneToMany and ManyToOne relationship like this:-
#Entity
#Table(name="t_profile")
public class Profile {
#Id
#column(name="profile_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#JoinColumn(name = "cat_id", referencedColumnName = "category_id")
#ManyToOne(optional=false)
private Category category;
// getters and setters
}
#Entity
#Table(name="t_category")
public class Category {
#Id
#column(name="category_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#OneToMany(mappedBy="category")
private List<Profile> profile;
// getters and setters
}
http://localhost:8080/project/profiles
When I am accessing profiles using rest client; I am able to get json format with field of id, name but ManyToOne field is not coming in json, whle debugging in controller, profile list has values of category. But it is not coming in json.
Any thoughts?
you can use #RestResource(exported = false) in you ManyToOne field.
ManyToOne field will comes as a link. That is on accessing category, profile field will be listed under "_links" in JSON body like shown below:
"_links" : {
"profile" : {
"href" : "http://<host>/<baseUrl>/category/<categoryId>/profile"
}
Further to get details of profile for a given category call below api:
http://<host>/<baseUrl>/category/<categoryId>/profile