Question:
Interested in how to create entities with annotations:
-
@OneToOne
-
@OneToMany
-
@ManyToOne
-
@ManyToMany
Also variations with bidirectional
and unidirectional
It would also be nice to add explanations like Cascade.**
Answer:
This answer will be updated as needed.
The answer is based on examples from this resource – How to synchronize bidirectional entity associations with JPA and Hibernate . This resource was updated on January 19, 2020, so if you have questions/problems with the answers below, please check out this website
Update#3 05/26/2020
-
Don't use
@Data
,@EqualsAndHashcode
from Lombok!! These issues are discussed in the following sources:
Item 45: Why Avoid Lombok @EqualsAndHashCode in Entities
The best way to implement equals, hashCode, and toString with JPA and Hibernate
Ultimate Guide to Implementing equals() and hashCode() with Hibernate
You can use@Setter
and@Getter
-
Don't use GenerationStrategy
AUTO
for ID. Read here
Update №2 02.02.2020
- IMPORTANT It is better not to use
Set<..>
instead ofList<..>
when dealing with..Many..
. A lot of problems arise, especially when working with forms. Also read this article by one of the main contributors to the Hibernate project – The best way to fix the Hibernate MultipleBagFetchException (This is an addition to Update # 1 (point 1) )
Update №1 11.09.2019:
-
Instead of
List<>
, useSet<>
where possible . (No, better not. Read more about it in the update below)Set<..>
better to use instead ofList<...>
otherwise, when working withjoin fetch
(custom hql query), you will get an error:org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple Bags:
More details about the error are written here.
-
Don't use CascadeType.ALL
In short, it's better to use only with
@OneToOne
, because when used with multiple relationships, unnecessary entries from the database may be deleted.This point is described in detail in this source.
GITHUB Demo (Spring 5)
I show all the examples below in detail in my mini-project, which can be downloaded from GitHub
You can read more about cascading here .
Project Lombok
In my answers I use Project Lombok annotations:
– @Data – Annotation that adds Getters/Setters, Equals, ToString, HashCode to your project
– @AllArgsConstructor – Constructor containing all global variables written in this class
– @NoArgsConstructor – An empty constructor. If we do not want to write data in the constructor itself, but use Setter
– @ToString(exclude – "VariableName") – When working with bidirectional
, we will loop objects. To prevent this, you need to get rid of them.
Other Project Lombok annotations
Installing the Project Lombok Plugin in IntelliJ and Eclipse
@OneToOne
unidirectional
option:
From the User to the Buyer, but not from the Buyer to the User
User.java:
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table
public class User {
@Id
@GeneratedValue
private long id;
@Column
private String username;
//Some code
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinColumn(name = "customer_id", unique = true)
private Customer customer;
}
Customer.java
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table
public class Customer {
@Id
@GeneratedValue
@Column(name = "country_id")
private long id;
//Some code
@Column(name = "customer_name")
private String customerName;
}
bidirectional
variant:
From User to Buyer and from Buyer to User
@OneToMany
unidirectional
option:
A Post has multiple Comments, but we don't need to search for a Post from a Comment
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);
}
}
Comment.java:
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table
public class Comment {
@Id
@GeneratedValue
@Column(name = "postcom_id")
private Long id;
@Column
private String text;
}
bidirectional
variant:
*Professor on the course can get information about students, at the same time students can get information about Professor*
Professor.java:
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table
public class Professor {
@Id
@GeneratedValue
@Column(name = "professor_id")
private Long id;
@Column
private String name;
@OneToMany(
mappedBy = "professor",
cascade = CascadeType.ALL,
orphanRemoval = true
)
List<Student> students = new ArrayList<>();
/*
As you see we need to do something like "recursion" below
*/
public void addStudent(Student student) {
students.add(student);
student.setProfessor(this);
}
public void removeStudent(Student student) {
students.remove(student);
student.setProfessor(null);
}
}
Student.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table
public class Student {
@Id
@GeneratedValue
@Column(name = "student_id")
private Long id;
@Column
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="professor_id")
private Professor professor;
}
@ManyToOne
@ManyToMany
unidirectional
option:
A User can have multiple Roles, a Role can be assigned to multiple Users
User.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table
public class User {
@Id
@GeneratedValue
@Column(name = "user_id")
private long id;
...
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
private List<Role> roles = new ArrayList<>();
public void addRoles(Role role) {
roles.add(role);
}
public void removeRoles(Role role) {
roles.remove(role);
}
}
Role.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table
public class Role {
@Id
@GeneratedValue
@Column(name = "role_id")
private int id;
@Column(name = "role")
private String role;
}
bidirectional
variant:
A trader can trade on several Exchanges, Exchanges can be visited by several Traders
Trader.java:
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table
public class Trader {
@Id
@GeneratedValue
@Column(name = "trader_id")
private Long id;
@Column(name = "trader_name")
private String traderName;
@ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
@JoinTable(name = "trader_stockmarket",
joinColumns = { @JoinColumn(name = "trader_id") },
inverseJoinColumns = { @JoinColumn(name = "stockmarket_id") })
private List<Stockmarket> stockmarkets = new ArrayList<>();
/*
We need to add methods below to make everything work correctly
*/
public void addStockmarket(Stockmarket stockmarket) {
stockmarkets.add(stockmarket);
stockmarket.getTraders().add(this);
}
public void removeStockmarket(Stockmarket stockmarket) {
stockmarkets.remove(stockmarket);
stockmarket.getTraders().remove(this);
}
}
Stockmarket.java
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table
public class Stockmarket{
@Id
@GeneratedValue
@Column(name = "stockmarket_id")
private Long id;
@Column(name = "stockmarket_name")
private String stockmarketName;
@ManyToMany(mappedBy="stockmarkets")
private List<Trader> traders = new ArrayList<>();
/*
We need to add methods below to make everything work correctly
*/
public void addTrader(Trader trader) {
traders.add(trader);
trader.getStockmarkets().add(this);
}
public void removeTrader(Trader trader) {
traders.remove(trader);
trader.getStockmarkets().remove(this);
}
}