How to add new fields / values to an existing entity in Spring Data JPA - spring-boot

How can I add new fields or values or properties to an existing entity and store them in the same table?
Customer entity already exists and have fields as
- id
- name
- lastName
Want to add contactNumber (as seen in the api structure below) to this existing Customer entity. Don't want a new entity for contactNumber
The expected request body is as below:
{
"id": "int auto generated",
"name": "String",
"lasName": "String",
"contactNumber":
{
"mobile":"long",
"office":"long",
"home":"long"
}
}
How can this be achieved ? Checked some blogs related to mapstruct but not getting proper solution.

You can use #Embeddable :
#Embeddable
public class ContactNumber {
private Long mobile;
private Long office;
private Long home;
// getters, setters...
}
Customer Entity:
#Entity
public class Customer {
#Id
private Long id;
private String name;
private String lastName;
#Embedded
private ContactNumber contactNumber;
// getters, setters...
}
With this mapping three columns(mobile, office, home) will be added to the Customer table.
You can simply save the Customer with the request body in the question using (#RequestBody Customer customer) parameter:
#PostMapping(value="/customers")
public void saveCustomers(#RequestBody Customer customer) {
customerRepository.save(customer);
}
For more information take a look at here.

Related

Child table is not mapping in OneToMany relationship in JPA

I am trying to establish One to many relationship between User and Role.
One user can have many role.
Here is the code for User class
#Entity
public class User {
#Id
private int id;
private String name;
private String password;
private String email;
private String phoneNo;
#OneToMany(
targetEntity = Role.class,
mappedBy = "user",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
private Set<Role> roles;
// Getters, setters and Constructor
Code for the Role class
#Entity
public class Role {
#Id
#GeneratedValue
private int roleId;
private String role;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
// Getters, setters and Constructor
POST request on Postman is
{
"id":101,
"name": "rahul",
"password": "456",
"email": "rahul#gmail.com",
"phoneNo": "1234561234",
"role": [{
"role":"USER"
}]
}
Code on Configuration part
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http)throws Exception
{
http.csrf().disable();
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
}
Code On Controller part
#RestController
public class AdminController {
#Autowired
UserRepository userRepo;
#Autowired
BCryptPasswordEncoder encryptPassword;
#PostMapping("/admin/add")
public String addUserByAdmin(#RequestBody User user)
{
String pass = user.getPassword();
String encrypt = encryptPassword.encode(pass);
user.setPassword(encrypt);
userRepo.save(user);
return "User added Successfully";
}
}
Role table connection to database through Jpa
public interface RoleRepository extends JpaRepository<Role, Integer> {
}
User table connection to database through Jpa
public interface UserRepository extends JpaRepository<User,Integer>{
}
Here problem is User table is mapped properly but Role table is not getting mapped.
roleId role user_id
NULL NULL NULL
Where I am wrong ? Could anyone help me ?
On your controller method below, have you tried debugging the incoming request User object?
Anyways, I have below points here:
First, looking into your request body, the field for your roles is named role while your User object has a field roles thus, I'm pretty sure it is null during your processing since it will not be deserialized there due to mismatch field names. Try changing your request body to something like this:
{
"id":101,
"name": "rahul",
"password": "456",
"email": "rahul#gmail.com",
"phoneNo": "1234561234",
"roles": [{
"role":"USER"
}]
}
Second, if you check your database, the roles will be persisted however your foreign key user_id is null. This is expected. The cascade you did on the User object will only means that (since you use CascadeType.ALL) once you save the User object the save operation will also be cascaded to the Role object however, JPA still needs to know the relationship thus you have to set the user for each role object. Hence, you can update your controller method to something below:
#PostMapping("/admin/add")
public String addUserByAdmin(#RequestBody User user)
{
String pass = user.getPassword();
String encrypt = encryptPassword.encode(pass);
user.setPassword(encrypt);
// JPA needs to know this relationship...
user.getRoles().forEach(role -> role.setUser(user));
userRepo.save(user);
return "User added Successfully";
}
Now you can try and see that your expected behavior should now be happening.
Additional recommendations:
Why are we passing ID field on the user request? You can just remove that from your request body and use below to auto-generate your IDs to avoid Unique index or primary key violation exceptions on all of your entities:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
You can also remove the targetEntity = Role.class on the mapping as it is only used for generics and for your case clearly you are not using generics for Set. Update your User object for roles mapping:
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Role> roles;
Lastly, it is better if you can wrap your incoming payload to a DTO since you would not want to expose your entity/model to your API but I am thinking this is just for your test environment.
You need to flush the changes to the database when using save(), try this instead:
userRepo.saveAndFlush(user);

Spring Data JPA save child object with the ID of parent object

I have two objects, one parent and one child as follows :
#Entity
#Table(name="category")
public class CategoryModel {
private #Id #GeneratedValue Long id;
private String name;
#OneToMany(mappedBy="category", cascade=CascadeType.PERSIST)
private List<AttributeModel> attributes;
}
#Entity
#Table(name="attribute")
public class AttributeModel {
private #Id #GeneratedValue Long id;
private String name;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="category_id")
private CategoryModel category;
}
I also have dtos which maps to these model objects but I ommited them.
When I try to save a category object with this payload Attribute values are also created in the attribute table but with null category ids.
{
"name":"Chemicals",
"attributes":[
{"name": "volume"}, {"name":"humidity"}
]
}
What can I do to have my attribute values persisted into the database with the category id which is created before them?
First of all, this problem is not a "Spring Data JPA" problem, it is a JPA (probably Hibernate) problem.
Analysis
Since you left out the code for the controller and the JSON mapping, I have to guess a bit:
fact 1: The relationship between category and attributes is controlled by the attribute AttributeModel.category but not by CategoryModel.attributes. (That is how JPA works).
observation 2: Your JSON object define CategoryModel.attributes (i.e. opposite to how JPA works).
Without knowing your JSON mapping configuration and controller code, I would guess that the problem is: that your JSON mapper does not set the AttributeModel.category field when it deserialises the JSON object.
Solution
So you need to instruct the JSON mapper to set the AttributeModel.category field during deserialisation. If you use Jackson, you could use:
#JsonManagedReference and
#JsonBackReference
#Entity
#Table(name="category")
public class CategoryModel {
...
#JsonManagedReference
#OneToMany(mappedBy="category", cascade=CascadeType.PERSIST)
private List<AttributeModel> attributes;
}
#Entity
#Table(name="attribute")
public class AttributeModel {
...
#JsonBackReference
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="category_id")
private CategoryModel category;
}
I solved this by manually setting child object's reference to the parent object as follows :
public Long createCategory(CategoryDto categoryDto) {
CategoryModel categoryModel = categoryDto.toModel(true,true);
categoryModel.getAttributes().forEach(a -> a.setCategory(categoryModel));
return categoryRepository.save(categoryModel).getId();
}

Spring boot one to one with foreign key

I am want to store room details and room types in my db. I have created an entity for Room as follows.
#Data
#Entity
public class Room {
#Id
private String room_id;
private Boolean status;
private Double price;
private Long type;
private Long occupancy;
#OneToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "type_id", nullable = false)
private RoomType roomType;
}
I have create a RoomType entity as follows:
#Data
#Entity
public class RoomType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String type;
}
To save a Room Entity in my DB, the following is the request body format need to be given
{
"room_id": "string",
"status": true,
"price": 0,
"roomType": {
"id": 1,
"type": "string"
}
}
I want to have the request body as follows
{
"room_id": "string",
"status": true,
"price": 0,
"roomType": 1 # This the roomType foreign key
}
I want to have one to one relationship, so when retrieving the Room details, I will get the roomType values also in the following format.
{
"room_id": "string",
"status": true,
"price": 0,
"roomType": {
"id": 0,
"type": "string"
}
}
Or Is there any better way to handle this type of problem in spring boot??
I think there are two different concepts in here:
The inner representation of a Room in your service (The classes you use and their relations)
The outer representation (The one exposed by your API)
If you want those two representation to be the same, you could just serialize/deserialize data using the Room class directly (which I assume is what you are currently doing). For example:
#Controller
class MyController {
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public Room createRoom(#RequestBody Room room) {
// Here room will be automatically deserialized from the request body
// Do stuff with the received room in here
// ...
// Here the returned room will be automatically serialized to
// the response body
return createdRoom;
}
}
However, if you don't want the inner and outer representations to be the same, you can use the DTO pattern (Data Transfer Object). Example in Spring here.
Now, you would have three different classes, Room and it's DTOs:
public class RoomRequestDTO {
private String room_id;
private Boolean status;
private Double price;
private Long type;
private Long occupancy;
private Integer roomType; // This is now an Integer
// Setters and Getters ...
}
public class RoomResponseDTO {
private String room_id;
private Boolean status;
private Double price;
private Long type;
private Long occupancy;
private RoomType roomType; // This is a RoomType instance
// Setters and Getters ...
}
Now, your controller would receive RoomRequestDTOs and send RoomResponseDTOs:
#Controller
class MyController {
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
#ResponseBody
public RoomResponseDTO createRoom(#RequestBody RoomRequestDTO roomReqDTO) {
Room room = ... // Convert from DTO to Room
// Do stuff with the received room in here
RoomResponseDTO roomRespDTO = ... // Convert to the DTO
return roomRespDTO;
}
}
This approach also has the advantage that you can now change the inner representaion of Room without afecting the API. This is, you could decide to merge the Room and RoomType classes in one, without affecting the outer representation.
Note: In the example I have linked, they use the ModelMapper library. If you do not desire to introduce that dependecy, you could simple add a contructor and a method to the DTOs as such
public RoomResponseDTO(Room room) {
// Manually asign the desired fields in here
this.id = room.getId();
// ...
}
public Room toRoom() {
Room room = new Room();
// Manually asing the desired field in here
room.setId(this.id);
// ...
return room;
}
However, this approach would make you have to change these methods whenever you want to change the representations of Room. The ModeMapper library does this for you.

Retrieving foreign key attributes in DTO

I am using java+Spring framework+Hibernate for creating rest api but I have stumbled upon retrieving details of a table using foreign key attributes.
I have the following tables::
https://i.stack.imgur.com/lG7UR.png
I am retrieving all the ratings given using product id and then mapping to DTO, now I also want to populate the username using idusers as this is my foreign key.
Same is the case when I try to retrieve ratings given by the users, instead of displaying idproducts I want to display the product name and product description as It is a foreign key.
Any advice on how to do so using DTO's.
This is a perfect use case for Blaze-Persistence Entity Views.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
Assuming you have an entity model like this
#Entity
public class User {
#Id
Integer id;
String role;
String username;
String password;
boolean enabled;
}
#Entity
public class Product {
#Id
Integer id;
String imageUrl;
String category;
int productPrice;
int productQuantity;
String productName;
String productDesc;
#OneToMany(mappedBy = "product")
Set<Rating> ratings;
}
#Entity
public class Rating {
#Id
Integer id;
int rating;
String review;
String ratingscol;
#ManyToOne(fetch = LAZY)
Product product;
#ManyToOne(fetch = LAZY)
User user;
}
A DTO mapping for your model could look as simple as the following
#EntityView(Rating.class)
interface RatingDto {
Integer getId();
UserDto getUser();
ProductDto getProduct();
}
#EntityView(User.class)
interface UserDto {
Integer getId();
String getUsername();
}
#EntityView(Rating.class)
interface ProductDto {
Integer getId();
String getProductName();
String getProductDesc();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
RatingDto dto = entityViewManager.find(entityManager, RatingDto.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
It will only fetch the mappings that you tell it to fetch
You can use ModelMapper when converting a DTO to an Entity bean and back from Entity bean to a DTO.
Add ModelMapper to your project
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.5</version>
</dependency>
Define the ModelMapper bean in your Spring configuration
#Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
Assuming the following models based on the given ER diagram you have given
public class UserDto {
Integer userId;
String role;
String username;
String password;
boolean enabled;
...default and parameterized constructor
...getter and setter methods
}
public class ProductDto {
Integer productId;
String imageUrl;
String category;
int productPrice;
int productQuantity;
String productName;
String productDesc;
...default and parameterized constructor
...getter and setter methods
}
public class RatingDto {
#Id
Integer id;
int rating;
String review;
String ratingscol;
ProductDto productDto;
UserDto userDto;
...default and parameterized constructor
...getter and setter methods
}
You can retrieve the ratings of a product using product id along with the user details by using the following method
#Repository
public interface RatingRepository extends JpaRepository<Rating, Integer>{
List<Rating> findByProduct_ProductId(Integer productId);
}
Then mapping rating objects to DTO
RatingDto ratingDto = modelMapper.map(rating, RatingDto.class);
Now you can retrieve username as following
ratingsDto.getUserDto().getUserName()
The same way you can retrieve the ratings by userId and access product details

Passing parent id reference when creating child object through REST api

I am using spring boot (version - 2.1.1). I have a one to many database model exposed for CRUD operations through rest api's. The model looks as below. How do I configure the POST /departments api (that creates a department object) to accept just the organization id in the input json body?
#PostMapping
public Long createDepartment(#RequestBody Department Department) {
Department d = departmentService.save(Department);
return d.getId();
}
Note - I do not want to allow creating organization object when creating a department.
Model object mapping
#Entity
#Table(name="ORGANIZATIONS")
public class Organization{
#Id
#GeneratedValue
Private long id;
#Column(unique=true)
Private String name;
#OneToMany(mappedBy = "organization", fetch = FetchType.EAGER)
private List<Department> departments;
}
#Entity
#Table(name="DEPARTMENTS")
Public class Department{
#Id
#GeneratedValue
Private long id;
#Column(unique=true)
Private String name;
#ManyToOne(fetch = FetchType.EAGER)
private Organization organization;
}
Thanks!
The easiest and most sane way in my opinion is to utilize the DTO (Data Transfer Object) pattern.
Create a class that represent the model you want to get as your input:
public class CreateDepartmentRequest {
private long id;
// getters and setters
}
Then use it in your controller:
#PostMapping
public Long createDepartment(#RequestBody CreateDepartmentRequest request) {
Department d = new Department();
d.setId(request.getId());
Department d = departmentService.save(d);
return d.getId();
}
Side note, its better to ALWAYS return JSON through REST API (unless you use some other format across your APIs) so you can also utilize the same pattern as I mentioned above to return a proper model as a result of the POST operation or a simple Map if you don't want to create to many models.

Resources