How to create OneToOne and OneToMany that are all linked together - java

I'm coming from Front End so all this is new to me. I have the following entities Client, Cart, Product and they are connected between them as such:
Client <=> #OneToOne <=> Cart
Cart => #OneToMany => Product(#ManyToOne for Cart)
Running my SpringDataJpaApplication creates correctly all my 3 tables with all the respective columns and their foreign keys in Postgres. Creating a new Cart with all the fields, will create both the Client and Cartin the Postgres, but will not create the Product which is a #ManyToOne. Querying the database will generate empty fields for Product
// Client
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int client_id;
private String name;
private String lastName;
#OneToOne(mappedBy = "client")
private Cart cart;
}
// Cart
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int cart_id;
#OneToMany(mappedBy = "cart")
private List<Product> productList;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_client_id", referencedColumnName = "client_id")
private Client client;
}
//Product
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int product_id;
private String productName;
private int price;
private int qty;
#ManyToOne
#JoinColumn(name = "fk_cart_id")
private Cart cart;
}
// CartController
#RestController
public class CartController {
#Autowired
CartRepository cartRepository;
#PostMapping("/createCart")
public Cart createCart(#RequestBody Cart request) {
return cartRepository.save(request);
}
}
As mentioned, posting to the endpoint, returns a 200 with records (as mentioned above) only created for Client and Cart. The post request is as follow:
{
"productList": [
{
"productName": "mobile",
"price": 800,
"qty": 2
}
],
"client": {
"name": "John",
"lastName": "Done"
}
}
What Am I doing wrong? Why isn't Product table populated?
(Note: With only #OneToMany and #ManyToOne between Cart and Product I'm able to populate both of them, but when the Client is thrown in to the mix, I have the issue describbed above)

The concept of owning entity is what you are missing.
In the Cart entity you have:
#OneToMany(mappedBy = "cart")
private List<Product> productList;
This says that the Product entity owns the relationship. Only the entity that owns the relationship can persist a relation. This means that a Product can persist a Cart but not visa-versa. In short, you have to persist a Product yourself. So, save the cart, then set the cart in the product, and save the product.
If you didn't have it then the #ManyToOne by itself dictates the same course of action.
#ManyToOne
#JoinColumn(name = "fk_cart_id")
private Cart cart;
The productList should be considered as a query only field.
Note that you can play games with Cascade annotations in Cart but I do not recommend that. It is a somewhat complicated and vexing annotation. Instead of having a Repository in your Controller layer, create a #Service layer and put the persist, retrieve, and other business logic in there and include that in your controller. Standard pattern.
Then write junit/mockito test cases for your service layer.
EDIT: Just to be clear, a Product cannot persist a Cart, but rather must have a previously persisted Cart to set to the cart field. No such requirement for the Cart entity.

Related

How to perfom lazy loading data in spring boot

I have a Spring boot application in which I have a one to many relationship
between my category entity and my product entity. A category contains several products
and a product belongs to one and only one category.
In the database, each category contains several registered products.
The category that contains less products in database has 1000 products
I have a category of my products in database which contains 15 000 products.
At the level of my category class, I have defined the one-to-many annotation
with the product entity and I have opted for the fetch mode = FetchType.LAZY.
This only retrieves the category entity and not the product entity.
How to do for the performance problem, because I want to create a method that
retrieves for each category, all its products and loop on it.
Even if I use the fetch mode = FetchType.EAGER,
I will have the same problem, how to better use FetchType.LAZY or FetchType.EAGER
while managing well the performance problem
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Category {
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy = "category")
private List<Product> product;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Product {
#Id
#GeneratedValue
private Integer id;
private String code;
private double price;
#ManyToOne
#JoinColumn(name="id_cat")
private Category category;
}
How can i solve it
You have 2 options:
Eager fetch type with #Fetch(FetchMode.JOIN) (https://www.baeldung.com/hibernate-fetchmode),
JOIN FETCH (https://thorben-janssen.com/hibernate-tips-difference-join-left-join-fetch-join/)

Extra association table is created in spring boot

I'm currently working on developing a recipe application and I'm having trouble with DB table generation.
Here are the Entity files I'm using:
// Recipe.java
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "recipes")
public class Recipe {
#Id
#GeneratedValue
private int id;
private String name;
private String description;
private String instruction;
#ManyToOne
private User user;
#OneToMany(cascade=CascadeType.ALL)
private List<RecipeIngredient> ingredients = new ArrayList<>();
}
// Ingredient.java
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "ingredients")
public class Ingredient {
#Id
#GeneratedValue
private int id;
private String name;
}
// RecipeIngredient.java
#Data
#Entity
#AllArgsConstructor
#NoArgsConstructor
public class RecipeIngredient {
#Id
#GeneratedValue
private int id;
#ManyToOne
private Ingredient ingredient;
private String amount;
}
Spring Boot Automatically creates tables for me but I just wanna have one table for RecipeIngredient, but it creates two tables for them.
It works perfectly fine but the thing I want is just how to make these two tables into one or make spring boot not generate one of them.
If you want recipe_ingedients table only delete recipeIngredient Entity Class and if you want to keep recipe_ingredient table remove this:
#OneToMany(cascade=CascadeType.ALL)
private List<RecipeIngredient> ingredients = new ArrayList<>();

cascade = CascadeType.ALL doesn´t work on OneToOne relation

I have one problem with One to One relation.
I have one USER that has only one TEAM
User Entity
#Entity
#Table(name = "USER")
#Data #NoArgsConstructor #AllArgsConstructor #Builder
public class User implements Serializable {
private static final long serialVersionUID = 3233149207833106460L;
#Id
#GeneratedValue
#Column(name = "USERID")
private Long id;
...
#OneToOne(mappedBy = "user")
private Team team;
}
Team Entity
#Entity
#Table(name = "TEAM")
#Data #NoArgsConstructor #AllArgsConstructor #Builder
public class Team implements Serializable {
private static final long serialVersionUID = 3233149207833106460L;
#Id
#GeneratedValue
#Column(name = "TEAMID")
private Long id;
...
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "USERID")
private User user;
}
This is the way I´m trying to create object.
//create Team and add to User
Team team = Team
.builder()
.value(TeamConstants.INITIAL_VALUE)
.name(userDetailsForm.getUsername()+" "+TeamConstants.TEAM)
.country(userDetailsForm.getUsername()+" "+TeamConstants.COUNTRY)
.build();
User user = User
.builder()
.username(userDetailsForm.getUsername())
.password(this.passwordEncoder.encode(userDetailsForm.getPassword()))
.email(userDetailsForm.getEmail())
.registerDate(DateUtil.getNow())
.role(userDetailsForm.getRole())
.team(team)
.build();
team.setUser(user);
this.userRepository.save(user);
And when I try to save user (and create automatically the son team), it gaves me the error
Blockquote
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.soccermanager.business.entities.user.User.team -> com.soccermanager.business.entities.team.Team;
Any idea with the solution?
This is my first question here, I hope its correct.
Thanks for your time
please interact with Team entity then user will be change follow CascadeType
or you need add
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL) private Team team; in User class.
Please try both and you can see the different. Hope this post answer can help with you
You should save your team entity before your user.
You should do as you do only if you are working in the same JPA Transaction unit. If not, it will not work and you get that exception.
Try adding #Transactional annotation on the method, with optional params like propagation = Propagation.REQUIRED and readonly=false because you are creating/updating objects here :
#Transactional(propagation = Propagation.REQUIRED, readOnly = false)
protected User saveTeamWithUser(UserDetailsForm form) {
// ...
}

Problems making a many-to-many relationship work across 2 microservices using Spring Boot & Hibernate

So for this assignment, I'm supposed to have 2 microservices (task-service & user-service) with each having their own database.
A task can have multiple users assigned it but multiple users can be assigned to multiple tasks so therefore it's a many-to-many relationship.
Both microservices are Spring Boot applications using Hibernate and I'm struggling to make this relationship happen between task and user inside the task-service because I don't think it's a good idea to copy the user model that already exists in the user-service, over to the task-service.
This assignment assumes that there is consistency in the user-ids across both databases without using foreign keys.
For context:
Desired entity relationship diagram generated from Hibernate:
Task.java [task-service]
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#EqualsAndHashCode(callSuper = true)
public class Task extends BaseEntity {
private String name, description;
#ManyToOne
private Lane lane;
#OneToMany
private List<TaskUser> users;
}
TaskUser.java [task-service]
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Data
#EqualsAndHashCode
public class TaskUser {
#Id
private Long id;
#Column(name = "task_id")
private Long taskId;
#Column(name = "user_id")
private Long userId;
}
User.java [user-service]
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Data
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public class User extends BaseEntity {
#Column(unique = true)
private String username;
}
Currently, Hibernate is generating both a task_user & task_users table inside the task-service database and I'd like to just have 1 intermediate table instead of two.
Question: How would I make this many-to-many relationship work with Hibernate whilst working with two different microservices?
Maybe a better hibernate mapping :
Task.java [task-service]
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#EqualsAndHashCode(callSuper = true)
public class Task extends BaseEntity {
private String name, description;
#ManyToOne
private Lane lane;
#OneToMany(mappedBy="task")
private List<TaskUser> users;
}
TaskUser.java [task-service]
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Data
#EqualsAndHashCode
public class TaskUser {
#Id
private Long id;
#ManyToOne
#JoinColumn(name="task_id", nullable=false)
private Task task;
#Column(name = "user_id")
private Long userId;
}
Use case: assign an already existing task to an already existing user.
You can assign multiple users at a time to 1 single task.
PostRequest
/tasks/allocation/new
Request Body:
{
"task-id": 12345,
"users": [
{
"username": "user-1",
"user-id": 101
},
{
"username": "user-2",
"user-id": 102
},
{
"username": "user-3",
"user-id": 103
}
]
}
Post request is having one task and list of user-details to be allocated to that task.
TaskAllocation.java
#Data
public class TaskAllocation{
#jsonProperty("task-id")
private long taskId;
#JsonProperty("users")
private List<Users> userList;
}
Users.java
#Data
public class Users{
#jsonProperty("username")
private String username;
#JsonProperty("user-id")
private Long userId;
}
RestController
#PostMapping("/tasks/allocation/new")
public CompletableFuture<ResponseEntity<?>> assignTaskToUsers(#ResponseBody #Valid TaskAllocation taskAllocation){
// service call
}
Inside service:
fetch the task from task db(verify if task exist)
If needed, fetch details of users from user-service(for each user), need a async or rest call to user-service. Task service is not having details of users.
For each user:
1.Create new Allocation
Set task Id
Set user-id or username
Save
Task is already existing
Table Task-Allocation
--------------------------------------------------------------
alloc-id(Pk) task-Id(Fk) user-id timetamp
--------------------------------------------------------------
1 12345 101 123123123123
2 12345 102 123123123123
3 12345 103 123123123123
Entities
Task and TaskAllocation has 1:n relationship i.e. task-allocation table consists of multiple records with same task-id.
Task.java
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "sirf_tournament")
public class Task extends Auditable<String> implements Serializable {
#Id
#GeneratedValue
private Long taskId;
private String taskName;
// others
#OneToMany(
fetch = FetchType.LAZY,
mappedBy = "task",
cascade = CascadeType.ALL,
orphanRemoval = true)
private Collection<TaskAllocation> taskAllocation = new HashSet<>();
}
TaskAllocation.java
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "sirf_tournament")
public class TaskAllocation extends Auditable<String> implements Serializable {
#Id
#GeneratedValue
private Long allocId;
private Long userId;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "taskId")
private Task task;
// others
}
User table and other related entities are not mentioned here. Task service has no information of users.
It would be good if you verify every users(all ids can be verified in a single call, return invalid ids from user service) and task ids before persisting them into table.

Java-JPA: Is it possible to map two relationships between two entities by more tables depending on a value a field has in one of the fields?

The idea that raised this question is the following:
Each User has an active cart and a history of purchased/finalized carts.
I know there might be logical/reasoning flaws in my approach so I'm basically just asking if this is doable: Is it possible to have the 2 separate tables for the list of carts which contains carts with a certain value in the status field? I'd like for a cart to be in a certain table while status is active and to move to the other table once it's finalized.
#Entity
#Table(name = "users")
class User{
#Id
private Long id;
#OneToOne
private ShoppingCart activeCart;
#OneToMany
private List<ShoppingCart> shoppingCartHistory;
//getters setters
}
#Entity
#Table(name = "carts")
class ShoppingCart{
#Id
Long id;
private List<String> items;
private String status;
//getters setters
}

Categories