Can Spring Data JPA Enforce ManyToOne Relationship via #JoinColumn - spring-boot

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

Related

store #ManytoOne relationship in elastiicsearch in springboot

I am using JpaRepository and ElasticsearchRepository(spring-data-3.x) in spring boot Application ,saving data in relational db as well as in elasticsearch.
Below is Student Entity
#Entity
#Table(name = "student")
#Document(indexName = "student" ,type="_doc")
class Student{
#id
private Long id;
private String studentName;
----
}
Below is course entity
#Entity
#Table(name = "course")
#Document(indexName = "course" ,type="_doc")
class Course{
#Id
private Long id;
private String courseName;
------
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties(value = "courses", allowSetters = true)
private Student student;
}
When I saved data in db using repository ,id of student is stored in course relation and when we fetch data ,course data fetched with student. But when I store data in elasticsearch student data stored as a sub-document in course document. If I make any updation in student like change student name and then search course for that student from elastic search .It shows old data as on updation sub-document is not updated. So How we can store data in elasticsearch when we are using relationship #ManyToOne.

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

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.

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.

Two tables connected via Primary Key

I have read about the use of #MapsId and #PrimaryKeyJoinColumn annotations, which sounds like a great options. I have two tables (UserList and UserInformation) which have a child, parent relationship, respectively; both classes below are abbreviated to just include the relevant columns. UserInformation's primary key value is always null and does not take the value of its parent column.
User Class
#Entity
#Data
#Table(name = "user_list")
public class UserList {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// List of foreign keys connecting different entities
#OneToOne(mappedBy = "user")
#MapsId("id")
private UserInformation userInfo;
}
UserInformation Class
#Entity
#Data
#Table(name = "user_information")
public class UserInformation implements Serializable {
#Id
private Integer userId;
#OneToOne
private UserList user;
}
I would prefer to not use an intermediary class if possible. I'm not tied to MapsId or even this implementation if there is a better solution.
Thanks!
The question is not very clear to me, but I think you could improve the following in the modeling of the entity:
The #column annotation can only be omitted when the class parameter is called exactly the same as the database column, taking into account the table name nomenclature, could it be that the column is user_id ?, if so the id parameter should be :
#Id
#column(name="USER_ID")
private Integer userId;
In the user entity being id, it will match the DB ID field so the #column annotation is not necessary

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 :)

Resources