Spring boot JPA - What is the best solution for updating the intermediate table of ManyToMany relationship with extra columns in the Controller? - spring-boot

I am using Spring Boot for backend in my project. In the database (MySQL) I have a many-to-many relationship which has the next entities: User, interest and relUserInterest. The RelUserInterest is a intermediate table between User and Interest and has extra columns.
User Entity
#Entity
public class User {
#Id #GeneratedValue private Long id;
#NotNull
#Column (unique = true) private String email;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#NotNull
Set<RelUserInterest> priority = new HashSet<>();
// more attributes, constructor, get and set
}
Interest entity
#Entity
public class Interest {
#Id
#GeneratedValue
private long id;
#NotEmpty
#Column(unique = true)
private String nameInterest;
#OneToMany(mappedBy = "interest", cascade = CascadeType.ALL)
#NotNull
Set<RelUserInterest> priority = new HashSet<>();
// more attributes, constructor, get and set
}
RelUserInterest entity
#Entity
#Table(name = "rel_user_interest")
#IdClass(UserInterestId.class)
public class RelUserInterest implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
User user;
#Id
#ManyToOne
#JoinColumn(name = "interest_id", referencedColumnName = "id")
Interest interest;
int priority;
// constructor, get and set
}
So far so good. I want to update the user and their interests. My controller is this. I want to do is when I update a existing user, update the intermediate table (RelUserInterest).
#PutMapping("/update/{id}")
public ResponseEntity<?> updateUser(#Validated #RequestBody UserDTOUpdate userDTOUpdate, BindingResult result, #PathVariable Long id) {
// More code
User user = usersService.getUserById(id);
// Code updating other attributes
// Here this the problem --> I don't know how to update the attribute priority of RelUserInterest
usersService.updateUser(user);
return new ResponseEntity<>(new Mensaje("Usuario actualizado"), HttpStatus.CREATED);
}
I have found several links but I'm not sure which is the best solution and I don't know how do it with my code.
How to update ManyToMany with extra columns
Spring boot JPA many to many with extra column insert and update
issue
Spring-Data-JPA ManyToMany relationship with extra column
In the Postman i want to send the next JSON although the interest array can be different if it is necessary for the solution:
{
"age": 22,
"genre": "Male",
"userName": "Miguel",
"roles": [
"ROLE_ADMIN",
"ROLE_USER"
],
"interest": [
{
"interestID": 1,
"nameInterest": "Museum",
"priority": 9
}
]
}
Question
So, the question is: How can I update the attribute priority of the RelUserEntity table? I suppose that making an intermediate table repository is a mistake. I'm a little lost. I hope you can help me. Thank you.

Related

One To One Mapping Spring Data JPA

I've a question about One to One unidirectional Mapping in Spring Boot.
I've a Customer class with a One to One unidirectional mapping to an Address class.
But when I try to associate a new customer with an existing Address, the database is updated.
So two Customers are now associated with the one Address.
As I understand it only one Customer should be associated with one unique Address. Do I understand the concept correctly, or am I doing something wrong in Spring Boot/ Spring Data JPA/ Hibernate?
Customer
#Entity
public class Customer {
#Id
private Long cId;
private String cName;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="aid")
private Address cAddr;
:
}
Address
#Entity
public class Address {
#Id
private Long aid;
private String town;
private String county;
:
}
data.sql
insert into address values (100, "New York", "NY");
insert into customer values (1, "John Smith", 100);
Application.java
#Override
public void run(String... args) throws Exception {
Customer c1 = new Customer((long)5, "Mr. Men");
Optional<Address> a100 = ar.findById((long)100);
c1.setcAddr(a100.get());
cr.save(c1);
}
Database
There are 2 options on how to make #OneToOne relation: unidirectional and bidirectional: see hibernate doc.
When you scroll down a little bit you will find the following:
When using a bidirectional #OneToOne association, Hibernate enforces the unique constraint upon fetching the child-side. If there are more than one children associated with the same parent, Hibernate will throw a org.hibernate.exception.ConstraintViolationException
It means that you'll have the exception only on fetching and when you have a bidirectional association. Because Hibernate will make an additional query to find the dependent entities, will find 2 of them, which doesn't fit #OneToOne relation and will have to throw an exception.
One way to "fix" uniqueness for your entities, is to make cAddr unique:
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="aid", unique=true)
private Address cAddr;
If you create your db tables, by setting hbm2ddl property this will add a unique constraint to the aid column.
I really recommend to read the following:
#OneToOne javadoc itself provides examples of how to do everything correctly (for you Examples 1 and 2 are the most useful)
Check Vlad's blog about #OneToOne. It must be the best you can find. At least jump to the chapter "The most efficient mapping" and implement it bidirectional and sharing the PK, using #MapsId.
Also maybe you will come up to the idea to use #ManyToOne option (at least i can imagine that customer can have multiple addresses)
This is not One-to-Many relation. It's One-to-Many as One object has multiple related objects. Checkout this article.
Example:
Post.java
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table
public class Post {
#Id
#GeneratedValue
#Column(name = "post_id")
private Long id;
#Column
private String postHeader;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();
public void addComment(Comment comment) {
comments.add(comment);
}
public void removeComment(Comment comment) {
comments.remove(comment);
}
// equals() and hashCode()
}
Comment:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table
public class Comment {
#Id
#GeneratedValue
#Column(name = "postcom_id")
private Long id;
#Column
private String text;
// equals() and hashCode()
}
Check out step "3. Uni-directional one-to-one mapping demonstration" at this site basically carbon copy of what you're trying to do.

Can Spring Data JPA Enforce ManyToOne Relationship via #JoinColumn

I have a Spring Boot application using Spring Data REST and Spring Data JPA. I have two domain entities: Student and Classroom, where many students can belong to the same classroom.
Student:
#Data
#Entity
#Table(name = "STUDENT")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STUDENT_ID")
private Integer studentId; // This Id has been setup as auto generated in DB
#Column(name = "ROOM_ID")
private Integer roomId;
#ManyToOne
#JoinColumn(name = "ROOM_ID", nullable = false, updatable = false, insertable = false)
private Classroom classroom;
}
Classroom:
#Data
#Entity
#Table(name = "CLASSROOM")
public class Classroom {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ROOM_ID")
private Integer roomId; // This Id has been setup as auto generated in DB
#OneToMany(mappedBy = "classroom")
private List<Student> studentList;
.....// other fields related to a classroom
}
And the Student repository:
public interface StudentRepository extends CrudRepository<Student , Integer>{
List<Student> findByClassroom(#Param("room") Classroom room);
}
And the Classroom repository:
public interface ClassroomRepository extends CrudRepository<Classroom , Integer>{
}
And I have a SpringApplication main file, but no controller.
There is already one classroom with room id=1 in the CLASSROOM table. When I gave the following request to POST to http://localhost:8080/students, a new student record was created in the Student table, which I expected it to fail because there isn't a classroom with id=100 exists in the CLASSROOM.
So my question is that: can Spring Data JPA enforce a manyToOne relationship or this foreign key enforcement has to be done on the database side (the not-null ROOM_ID column in the Student table is NOT defined as foreign key by our DBA for a legitimate consideration). If it has to be done on the database side, what is the point to define the manyToOne relationship in entity files?
Also, I know that I have redundant classroom fields in the Student entity, I just don't know which one to keep in the Student entity (the roomId or the "classroom" field), because when I create a student, I want to give only the roomId of a classroom in the request. Thanks!
{
"roomId": 100 // I expect this request to fail because no roomId=100 in the CLASSROOM table.
}
what is the point to define the manyToOne relationship in entity files
Because is an Object Relational Mapping tool that allows you define entity graphs.
You are currently passing roomId which in your Entity is just another field so you needs to remove that.
#Entity
#Table(name = "STUDENT")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STUDENT_ID")
private Integer studentId; // This Id has been setup as auto generated in DB
#ManyToOne
#JoinColumn(name = "ROOM_ID", nullable = false)
private Classroom classroom;
}
In Spring Data Rest you then defined an association by passing the self link of the referenced entity.
Your request then needs to look like the below:
{
"classroom" : "http://localhost:8080/classrooms/1"
}
Also removing the ID as you are POSTing a new record and, as you note, the ID is auto-generated in the database.
See also:
https://www.baeldung.com/spring-data-rest-relationships

How to load insert scripts for multiple tables which are linked with foreign key in spring boot

I am using spring boot. I am loading test data through yml by defining spring.datasource.data=classpath:/test-data/sql_file_EntityOne.sql, classpath:/test-data/sql_file_EntityTwo.sql,...
For every single entity it works seamlessly but problem comes when EntityOne and EntityTwo have foreign key relationship and the corresponding insert statements are written in 2 different SQL files as depicted above.
I am using in memory h2 dB for local.
sql_file_EntityOne.sql
(Id_One, data_1,data_2) values(101, 'dat', 5)
sql_file_EntityTwo.sql
(Id_two, Id_Onethis is fk, data_3,data_4)
values(1,101, 'dat2', null, 5)
EntityOne
#Id
IdOne
....
#OneToMany(Cascade.All, mappedBy="entityOneRef")
List entityTwoRef
EntityTwo
#Id
IdTwo
....
#ManyToOne(Cascade.All)
#JoinColumn("entityTwoRef")
EntityOne entityOneRef
Can you please mention the error you are getting here?
You can use the following hibernate annotations for bidirectional relationship:
#OneToMany(mappedBy = ) on parent enity
#ManyToOne #JoinColumn(name = , nullable = false) on child entity
for example, let's take an example of Cart and Item as two entities with a Cart related as one to many with item:
//Cart
#Entity
#Data
public class Cart {
#Id
private Integer cartId;
#Column
private String data;
#OneToMany(mappedBy = "cart")
private Set<Item> items;
}
//Item
#Entity
#Data
public class Item {
#Id
private Integer itemId;
#Column
private String data;
#ManyToOne
#JoinColumn(name = "cart_id", nullable = false)
private Cart cart;
}
#Data is just lombok annotation for getters and setters. Scripts as below:
//Script1
INSERT INTO CART(cart_id,data) VALUES (101,'data1')
//Script2
INSERT INTO ITEM(item_id,cart_id,data) VALUES (1,101,'data2')
Then load the scripts in spring-boot in order:
spring.datasource.data=classpath:sql_script1.sql,classpath:sql_script2.sql
Hope it helps :)

non fetched entity appears in JSON result

I have a user entity:
#Entity(name = "users")
#DynamicUpdate
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
#JsonIgnoreProperties("user")
private Set<Car> cars;
}
and a car entity:
#Entity(name = "cars")
#DynamicUpdate
public class Car implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "plate_number")
#JsonView(View.Summary.class)
private String plateNumber;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
#JsonIgnore
#JsonManagedReference("user-coordinate")
#Access(value = AccessType.FIELD)
private User user;
}
when I get cars list I get this:
{
"id": 5,
"plateNumber": "aaaaada",
"user": {
"id": 110,
"name": null
}
},
But I want to get this(without user entity!):
{
"id": 5,
"plateNumber": "aaaaada",
},
I have 7 years' experience in PHP (I said it to know I am familiar with how to search and get what I want) but I am a beginner in java spring I searched a lot for the answer all I get is that it's related to Jackson but I don't know how to solve it please help me with details I will appreciate it.
Thank you
you are using #JsonManagedReference("user-coordinate") which overrides #JsonIgnore
JsonManagedReference directs Jackson to serialize the user when serializing car, so if you want user to not be serialized you need to removed it so #JsonIgnore takes place
For anyone had this problem two, I had two dependencies for JsonIgnore I was using:
org.codehaus.jackson.annotate
that was the problem I changed it to:
com.fasterxml.jackson.annotation
And the problem solved

How to send POST request with Many-to-many relationship in Spring?

I'm trying to add a order with equipment list, here's my entities:
the order entity
#Entity #Table(name = "orders") public class Order extends Ticket{
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private Set<OrderEquipment> orderEquipments = new HashSet<>();}
the equipment entity
#Entity #Table(name = "equipments") public class Equipment extends DateAudit {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotBlank
#Size(max = 30)
private String name;
#NotNull
private Long nbr_piece ;
#OneToMany(mappedBy = "equipment", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<OrderEquipment> orderEquipments = new HashSet<>();}
and the order_equipment entity
#Entity #Table(name = "order_equipment") public class OrderEquipment extends DateAudit { #Id
#ManyToOne
#JoinColumn(name = "order_id")
private Order order;
#Id
#ManyToOne
#JoinColumn(name = "equipment_id")
private Equipment equipment;
#NotBlank
#Column(name = "quantity")
private Long quantity;}
here is the add function in the orderController
#PostMapping("/orders")
public Order createOrder(#Valid #RequestBody Order Order){
Order.setObservateurEmail(Order.getObservateurEmail());
Order.setObject(Order.getObject());
Order.setDescription(Order.getDescription());
return orderRepository.save(Order);
}
I have seen a mistakes there, lemme try to help you. Since you issue is not clear, please lemme know if it does/does not work:
You have two bidirectional mappings there:
Order (with ALL cascade) <-> OrderEquipment
Equipment (with ALL cascade) <-> OrderEquipment
You are using #JoinColumn for both of them, even though they are bidirectional. Please take a look at this. You should always use the mappedBy attribute when defining bidirectional relationships.
Now, you are receiving an Order object from a POST request, making changes to 3 attributes and then saving it. Since the mapping between the Order and OrderEquipment have the CascadeType.ALL attribute, any save on the Order object will save all OrderEquipment children associated. If the Order object you are receiving already have OrderEquipment children, your method will also save/update them.
Your POST mapping looks good to me, just take care with your table relationship definitions.
Take a look at this answer to check how a lits of entities should be formatted on a JSON POST.

Resources