I am making a restaurant management app and I am having a problem persisting a user association. For the Chef class there is a set association with the Dish class so that specific dishes can be associated with a certain chef.
I have created a method that associates a dish with a chef, and when I try calling it on my REST client the method seems to work, and it returns a JSON of the chef object with the updated info, however when I call the get chef method the JSON no longer shows the added dish item
Here is the chef class and everything related to the Dish Object
#Table(name="chef")
public class Chef {
//Chef Attributes
#Id private String username;
private String firstName;
private String lastName;
private String email;
private String password;
private String address;
private String bio;
private Boolean delivery;
private String photoURL;
// #OneToOne
// private ChefMenu menu;
#Transient
#OneToMany(mappedBy = "chef", cascade = CascadeType.ALL)
#JsonIgnoreProperties("chef")
private Set<Dish> menuItems;
public Set<Dish> getMenuItems() {
if (this.menuItems == null) {
this.menuItems = new HashSet<Dish>();
}
return this.menuItems;
}
Here is the Dish class with everything related to the Chef class
#Entity
#Table(name="dish")
public class Dish {
//Dish Attributes
#Id private String dishName;
private String cuisine;
private double price;
private String maxQuantity;
private String dietaryRestriction;
private String mealIngredients;
private String cookingTime;
#ManyToOne
#JoinColumn(name = "chef")
#JsonIgnoreProperties({"menuItems","orders","firstName", "lastName", "email", "bio", "password", "address", "delivery", "photoURL"})
private Chef chef;
public void setChef(Chef val) { this.chef = val; }
public Chef getChef() {
return this.chef;
}
Here is the method used to add a new dish to a chef from the repository
#Transactional
public Chef addDishToMenu(Chef c, Dish d) {
c.addDish(d);
entityManager.merge(c);
return c;
}
And finally here is code from the controller class:
#PostMapping("dish/create/{dishName}/{cuisine}/{price}/{maxQuantity}/{dietaryRestriction}/{mealIngredients}/{cookingTime}")
public ResponseEntity createDish(String username,
#PathVariable("dishName") String dishName, #PathVariable("cuisine") String cuisine,
#PathVariable("price") String price, #PathVariable("maxQuantity") String maxQuantity,
#PathVariable("dietaryRestriction") String dietaryRestriction,
#PathVariable("mealIngredients") String mealIngredients, #PathVariable("cookingTime") String cookingTime)
{
Dish d = new Dish();
Double p = Double.parseDouble(price);
//int mQ = Integer.parseInt(maxQuantity);
try {
d = foodRepository.createDish(dishName, cuisine, p, maxQuantity, dietaryRestriction,
mealIngredients, cookingTime);
} catch (InvalidInputException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new Response(false, e.getMessage()));
}
return ResponseEntity.status(HttpStatus.OK).body(d);
}
#PostMapping("dish/add/{username}/{dishName}")
public ResponseEntity addDishToMenu(#PathVariable("username") String username, #PathVariable("dishName") String dishName) throws NullObjectException {
Chef c = new Chef();
Dish d = new Dish();
c= chefRepository.getChef(username);
d = foodRepository.getSpecificDish(dishName);
c = foodRepository.addDishToMenu(c, d);
return ResponseEntity.status(HttpStatus.OK).body(c);
}
#GetMapping("/get/{username}")
public ResponseEntity getChef(#PathVariable("username") String username) {
// List<AppUser> user;
Chef chef = new Chef();
try {
chef = chefRepository.getChef(username);
// user = userRepository.getAppUserQuery(username);
} catch (NullObjectException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new Response(false, e.getMessage()));
}
return ResponseEntity.status(HttpStatus.OK).body(chef);// user.get(0));
}
So when I make the call on my Rest client to add a dish to a chef I get this as a response:
{
"username": "Favreau4",
"firstName": "Jon",
"lastName": "Favreau",
"email": "chefFavreau#email.com",
"password": "j+9UECq/PLA=$I+HsXngf/b82+rMtPQQO",
"address": null,
"bio": null,
"delivery": null,
"photoURL": null,
"menuItems": [
{
"dishName": "Big sandwich",
"cuisine": "american",
"price": 5,
"maxQuantity": "3",
"dietaryRestriction": "bacon",
"mealIngredients": "bacon,lettuce,tomato,bread",
"cookingTime": "10mins"
}
],
"order": [],
}
but when I use the getChef REST call I get this:
{
"username": "Favreau4",
"firstName": "Jon",
"lastName": "Favreau",
"email": "chefFavreau#email.com",
"password": "j+9UECq/PLA=$I+HsXngf/b82+rMtPQQO",
"address": null,
"bio": null,
"delivery": null,
"photoURL": null,
"menuItems": [],
"order": [],
}
Does anyone know how to resolve this issue?
Are you aware of #Transient annotation? Transient are used to mark a variable as non-presistable. So your menuitems are not getting persist or saved in database.
Related
In many cases in my application, I want to return a tree of data using #OneToMany and #ManyToOne relationships. I am implementing soft delete using #SQLDelete
#Where annotations. I cannot figure out how to keep the tree from returning soft deleted grandchild objects.
For example, my parent entity ...
#Entity
#Table(name = "gson_test_parent")
#SQLDelete(sql = "UPDATE gson_test_parent SET deleted = true, deleted_at = now() WHERE id=?")
#Where(clause = "deleted=false")
public class GsonTestParent extends SoftDeletableEntity {
public static final String STATUS_NEW = "new";
public static final String STATUS_ACTIVE = "active";
public static final String STATUS_DISABLED = "disabled";
#Expose
private String name;
#Expose
#OneToMany(fetch = FetchType.EAGER, mappedBy="gsonTestParentId")
private List<GsonTestParentToGsonTestChild> gsonTestParentToGsonTestChild = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<GsonTestParentToGsonTestChild> getGsonTestParentToGsonTestChild() {
return gsonTestParentToGsonTestChild;
}
}
... my join entity ...
#Entity
#Table(name = "gson_test_parent_to_gson_test_child")
#SQLDelete(sql = "UPDATE gson_test_parent_to_gson_test_child SET deleted = true, deleted_at = now() WHERE id=?")
#Where(clause = "deleted=false")
public class GsonTestParentToGsonTestChild extends SoftDeletableEntity {
public static final String STATUS_ACTIVE = "active";
public static final String STATUS_DISABLED = "disabled";
#Expose
private Long gsonTestParentId;
#Expose
#Transient
#GsonExcludeBackReference
private GsonTestParent gsonTestParent;
#Expose
#ManyToOne(fetch = FetchType.EAGER)
#Where(clause = "deleted=false")
private GsonTestChild gsonTestChild;
public Long getGsonTestParentId() {
return gsonTestParentId;
}
public GsonTestParent getGsonTestParent() {
return gsonTestParent;
}
public void setGsonTestParent(GsonTestParent gsonTestParent) {
this.gsonTestParent = gsonTestParent;
}
public GsonTestChild getGsonTestChild() {
return gsonTestChild;
}
}
... and the child entity ...
#Entity
#Table(name = "gson_test_child")
#SQLDelete(sql = "UPDATE gson_test_child SET deleted = true, deleted_at = now() WHERE id=?")
#Where(clause = "deleted=false")
public class GsonTestChild extends SoftDeletableEntity {
public static final String STATUS_NEW = "new";
public static final String STATUS_ACTIVE = "active";
public static final String STATUS_DISABLED = "disabled";
#Expose
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
FYI, these all extend SoftDeletableEntity, which looks like ...
#MappedSuperclass
public class SoftDeletableEntity extends BaseEntity {
public SoftDeletableEntity() {
super();
}
#Expose
protected Timestamp deletedAt;
protected Boolean deleted = Boolean.FALSE;
public Timestamp getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(Timestamp deletedAt) {
this.deletedAt = deletedAt;
}
public Boolean getDeleted() {
return deleted;
}
public void setDeleted(Boolean deleted) {
this.deleted = deleted;
}
}
When I do a find on the parent entity ...
#GetMapping(path="/{id}")
public ResponseEntity<String> get(#PathVariable Long id) throws BaseException {
Map<String, Object> responseMap = new HashMap<>();
GsonTestParent gsonTestParent = gsonTestParentService.find(id);
responseMap.put("action", "Get");
responseMap.put("message", "Entity retrieved");
responseMap.put("entityType", "GsonTestParent");
responseMap.put("entity", gsonTestParent);
return responseService.success(responseMap);
}
I get the child entity (grandchild) even though it is marked as deleted in the database ...
{
"payload": {
"entityType": "GsonTestParent",
"action": "Get",
"message": "Entity retrieved",
"entity": {
"name": "test_parent_1",
"gsonTestParentToGsonTestChild": [
{
"gsonTestParentId": 1,
"gsonTestChild": {
"name": "test_child_1",
"deletedAt": "2022-07-26T04:31:30.000",
"id": 1,
"createdAt": "2022-07-22T07:24:15.000",
"updatedAt": "2022-07-22T07:24:15.000",
"status": "active"
},
"deletedAt": null,
"id": 1,
"createdAt": "2022-07-22T07:57:46.000",
"updatedAt": "2022-07-22T07:57:46.000",
"status": "active"
}
],
"deletedAt": null,
"id": 1,
"createdAt": "2022-07-22T07:23:15.000",
"updatedAt": "2022-07-22T07:23:15.000",
"status": "active"
}
},
"status": "success"
}
The gson_test_child record in the DB
mysql> select * from gson_test_child where id = 1;
+----+---------------------+---------------------+---------------------+---------+--------+--------------+
| id | created_at | updated_at | deleted_at | deleted | status | name |
+----+---------------------+---------------------+---------------------+---------+--------+--------------+
| 1 | 2022-07-22 14:24:15 | 2022-07-22 14:24:15 | 2022-07-26 11:31:30 | 1 | active | test_child_1 |
+----+---------------------+---------------------+---------------------+---------+--------+--------------+
A few comments:
I am referencing the join table explicitly, as opposed to using the #JoinTable functionality because many of the "join" tables in my app have other meaningful fields I want to expose.
I thought the #Where annotation on the GsonTestParentToGsonTestChild.gsonTestChild field would eliminate soft deleted children, but apparently not (or I'm doing something wrong).
I know I can create explicit JOIN FETCH native queries in the repositories that will filter the deleted grandchildren, but that kind of subverts the reasons for using annotations.
Please let me know if I can provide further information.
Mike
Try adding #Where(clause = "deleted=false") also to the collections.
#Christian Beikov,
it looks like you're correct. The #Where(clause = "deleted=false") works on an #OneToMany relationship, but not on an #ManyToOne.
I'm learning how to properly use DTOs and so far everything was going well until I got this problem:
I have a Model called Student:
#Data
#Document
#Builder
public class Student {
#Id
private String id;
#Indexed(unique = true)
private Name name;
#Indexed(unique = true)
private String email;
private Gender gender;
private Address address;
private List<String> favoriteSubjects;
private BigDecimal totalSpentInBooks;
private LocalDateTime created;
}
Than I create a DTO to fetch some data:
#Data
public class StudentDto {
private String firstName;
private String lastName;
private String email;
private String country;
}
Here is the response using the DTO from Postman:
{
"firstName": "Gabriel",
"lastName": "Vendramini",
"email": "gabriel.vendramini#email.com",
"country": null
}
Here is the same response but with no DTO:
{
"id": "61f7d87bb4534b452993b02e",
"name": {
"firstName": "Gabriel",
"lastName": "Vendramini"
},
"email": "gabriel.vendramini#email.com",
"gender": "MALE",
"address": {
"country": "Brazil",
"city": "Sao Paulo",
"postCode": "08006"
},
"favoriteSubjects": [
"Computer Science",
"Mathematic",
"Physic"
],
"totalSpentInBooks": 10,
"created": "2022-01-31T13:39:23.023"
}
Here are the Controller:
#GetMapping("/get-all")
public ResponseEntity<List<Student>> fetchAllStudents() {
log.info("Get All Students - Controller Call");
return ResponseEntity.ok(studentService.getAllStudents());
}
#GetMapping("/get-all-dto")
public ResponseEntity<List<StudentDto>> fetchAllStudentsDto() {
log.info("Get All Students With DTO - Controller Call");
return ResponseEntity.ok(studentService.getAllStudents().stream().map(studen
t -> modelMapper.map(student,
StudentDto.class)).collect(Collectors.toList()));
}
Here are the Models for Address and Name:
#Data
#AllArgsConstructor
#Builder
public class Address {
private String country;
private String city;
private String postCode;
}
#Data
#AllArgsConstructor
#Builder
public class Name {
private String firstName;
private String lastName;
}
The structure is the same for both. I tried to pass all the attributes from Address, change the order.
Whenever I select all my items from db, it is being returned as an array of objects. However I want the object to contain another object with data, instead of displaying everything in one JSON. Is this doable somehow?
Below is example which will return
[
{
"id": "",
"name": "",
"surname": "",
"date_started": "",
"school_name": "",
"graduated": false,
]
What I want:
[
{
"id": "",
"name": "",
"surname": "",
"date_started": "",
"school": {
"school_name": "",
"graduated": false,
}
]
public List<Person> getPeople() {
String sql = "my query";
List<Person> people = jdbcTemplate.query(sql, (result, index) -> {
return new People(
result.getObject("id", java.util.UUID.class),
result.getString("name"),
result.getString("surname"),
result.getDate("date_started"),
result.getString("school_name"),
result.getBoolean("graduated")
);
});
return people;
}
It is posible:
first create 2 class (bean / dto) such as:
public class peopleBean {
private int id;
private String name;
private String surname;
private String date_started;
private List<schoolBean> school;
...
}
public class schoolBean {
private String school_name;
private String graduated;
...
}
then in the getPeople corret it to use listpeople, people_local and school:
public List<Person> getPeople() {
String sql = "my query";
List<peobleBean> listpeople = new ArrayList<peopleBean>();
List<Person> people = jdbcTemplate.query(sql, (result, index) -> {
peopleBean people_local = new peopleBean();
schoolBean school = new schoolBean();
return new People(
result.getObject("id", java.util.UUID.class),
result.getString("name"),
result.getString("surname"),
result.getDate("date_started"),
result.getString("school_name"),
result.getBoolean("graduated")
);
});
return listpeople;
}
I want to create this JSON using jakson annotated POJOS. The issue I have when I create a new class without #JsonProperty annotation to represent the last {"id":"123ccc","role":"dddd"}, it by default take the class name and create something like "customer":{"id": "123ccc","role":"dddd"}.
The JSON Structure I indent to build
{
"relatedParty": [
{
"contact": [
{
"mediumType": "xxx",
"characteristic": {
"city": "xxx",
"country": "xxx"
}
},
{
"mediumType": "yyy",
"characteristic": {
"emailAddress": "yyy#yy.yyy"
}
}
],
"role": "ccc",
"fullName": "ccc"
},
{
"id": "123ccc",
"role": "dddd"
}
]
}
The JSON I'm receiving from the below code.
{
"relatedParty": [
{
"contact": [
{
"mediumType": "xxx",
"characteristic": {
"city": "xxx",
"country": "xxx"
}
},
{
"mediumType": "yyy",
"characteristic": {
"emailAddress": "yyy#yy.yyy"
}
}
],
"role": "ccc",
"fullName": "ccc"
},
"customer" : {
"id": "123ccc",
"role": "dddd"
}
]
}
What would be a workaround to get the exact JSON format as the image. Current Implementation is below.
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class RelatedParty {
#JsonProperty(value = "contact")
private List<Contact> contact;
#JsonProperty(value = "role")
private String role;
#JsonProperty(value = "fullName")
private String fullName;
private Customer customer;
public List<Contact> getContact() {
return contact;
}
public void setContact(List<Contact> contact) {
this.contact = contact;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
public class Customer {
#JsonProperty(value = "id")
private String id;
#JsonProperty(value = "role")
private String role;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
You need to create additional and different POJO classes to model your JSON correctly. Basically, JSON arrays will be handle in Java lists, and JSON objects will be handled in Java classes.
Starting from the inside (most nested level) of the JSON, and working our way out:
NOTE: getters and setters not shown here
Characteristic.java
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Characteristic {
#JsonProperty("city")
private String city;
#JsonProperty("country")
private String country;
#JsonProperty("emailAddress")
private String emailAddress;
}
Contact.java (contains our characteristics):
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Contact {
#JsonProperty("mediumType")
private String mediumType;
#JsonProperty("characteristic")
private Characteristic characteristic;
}
The above two classes handle the innermost objects. If we remove them from your target JSON, that leaves the following:
{
"relatedParty": [{
"contact": [...],
"role": "ccc",
"fullName": "ccc"
}, {
"role": "dddd",
"id": "123ccc"
}]
}
Note that the contact field is a JSON array, not an object - so we do not create a Java Contact class (which would be for a JSON object).
To handle the above I create two more classes:
RelatedPartyInner.java (contains a list of contacts)
#JsonInclude(JsonInclude.Include.NON_NULL)
public class RelatedParty_ {
#JsonProperty("contact")
private List<Contact> contact = null;
#JsonProperty("role")
private String role;
#JsonProperty("fullName")
private String fullName;
#JsonProperty("id")
private String id;
}
RelatedParty.java (wraps everything in an outer object):
#JsonInclude(JsonInclude.Include.NON_NULL)
public class RelatedParty {
#JsonProperty("relatedParty")
private List<RelatedPartyInner> relatedParty = null;
}
To test this I create the following data:
Characteristic chr1 = new Characteristic();
chr1.setCity("xxx");
chr1.setCountry("xxx");
Characteristic chr2 = new Characteristic();
chr2.setEmailAddress("yyy#yy.yyy");
Contact con1 = new Contact();
con1.setMediumType("xxx");
con1.setCharacteristic(chr1);
Contact con2 = new Contact();
con2.setMediumType("yyy");
con2.setCharacteristic(chr2);
List<Contact> cons = new ArrayList<>();
cons.add(con1);
cons.add(con2);
RelatedPartyInner rpi1 = new RelatedPartyInner();
rpi1.setContact(cons);
rpi1.setRole("ccc");
rpi1.setFullName("ccc");
RelatedPartyInner rpi2 = new RelatedPartyInner();
rpi2.setId("123ccc");
rpi2.setRole("dddd");
List<RelatedPartyInner> rpis = new ArrayList<>();
rpis.add(rpi1);
rpis.add(rpi2);
RelatedParty rp = new RelatedParty();
rp.setRelatedParty(rpis);
Finally, we can generate the JSON:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValue(new File("rp.json"), rp);
The resulting file contains the following:
{
"relatedParty": [{
"contact": [{
"mediumType": "xxx",
"characteristic": {
"city": "xxx",
"country": "xxx"
}
}, {
"mediumType": "yyy",
"characteristic": {
"emailAddress": "yyy#yy.yyy"
}
}],
"role": "ccc",
"fullName": "ccc"
}, {
"role": "dddd",
"id": "123ccc"
}]
}
I have a class called Customer.
#Entity
#Table(name = "customer")
public class Customer {
#Id
#Column(unique = true)
private String userId;
#Column(unique = true)
private String userName;
private String fullName;
#Column(unique = true)
private String emailAddress;
private String password;
private String country;
#ElementCollection
private Collection<ContactNum> contactNums = new ArrayList<>();
private String district;
private String dateOfBirth;
private String gender;
}
and there is a collection of contact numbers.
#XmlRootElement
#Embeddable
public class ContactNum {
private String landLine;
#Column(unique = true)
private String mobile;
public String getLandLine() {
return landLine;
}
public void setLandLine(String landLine) {
this.landLine = landLine;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
}
My REST API is getting a POST Request JSON Object which is Customer and Contact number inside it.
{
"userName": "aaaa",
"fullName": "aaaa",
"emailAddress": "aaaa",
"password": "aaaa",
"country": "aaaa",
"contactNums" : {
"landLine": "0000000000",
"mobile": "0000000000"
},
"district": "aaaa",
"dateOfBirth": "813695400000",
"gender": "aaaa"
}
How can I map that request in my JAX-RS client? My method to get request is this. And I also use Hibernate as an ORM tool.
#POST
#Path("registerCustomer")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response registerCustomer(Customer newCustomer) {
}
If you're using Jackson to handle JSON conversion.
You can either use a custom deserializer (via #JsonDeserialize
on class level).
Or write an adapter which converts your ContactNum
to a Collection of ContactNum's.
But if you change your JSON input from
"contactNums" : {
"landLine": "0000000000",
"mobile": "0000000000"
}
to
"contactNums" : [{
"landLine": "0000000000",
"mobile": "0000000000"
}]
(contactNums changed from object to array of objects)
This way the conversion should work out of the box.