// @OneToOne: User has one UserProfile
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
// Owning side: has the foreign key column
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "profile_id", unique = true)
private UserProfile profile;
}
@Entity
public class UserProfile {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String bio;
private String website;
// Inverse side (mappedBy = field name in User)
@OneToOne(mappedBy = "profile")
private User user;
}
// @OneToMany / @ManyToOne: User has many Orders
@Entity
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
// One user has many orders
// mappedBy = field name in Order that owns the relationship
@OneToMany(mappedBy = "user",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// Helper method to maintain bidirectional consistency
public void addOrder(Order order) {
orders.add(order);
order.setUser(this);
}
}
@Entity
@Table(name = "orders")
public class Order {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private java.math.BigDecimal total;
// Many orders belong to one user
// This side owns the relationship (has the FK column)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
}
Cascade Types and Fetch Types
Cascade Type
Description
ALL
All operations cascade (PERSIST, MERGE, REMOVE, REFRESH, DETACH)
PERSIST
Save cascades to related entities
MERGE
Merge cascades to related entities
REMOVE
Delete cascades to related entities
REFRESH
Refresh cascades to related entities
DETACH
Detach cascades to related entities
@ManyToMany with Join Table
// @ManyToMany: Student can enroll in many Courses
// Course can have many Students
@Entity
public class Student {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Owning side: defines the join table
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.LAZY)
@JoinTable(
name = "student_course", // Join table name
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}
@Entity
public class Course {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
// Inverse side
@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
}
// Usage
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Student student = new Student("Alice");
Course java = new Course("Java Programming");
Course spring = new Course("Spring Framework");
student.getCourses().add(java);
student.getCourses().add(spring);
java.getStudents().add(student);
spring.getStudents().add(student);
session.persist(student);
session.persist(java);
session.persist(spring);
tx.commit();
session.close();
Fetch Types - EAGER vs LAZY
// EAGER: related entities loaded immediately with parent
// Default for @ManyToOne and @OneToOne
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "department_id")
private Department department; // Loaded with Employee in same query
// LAZY: related entities loaded only when accessed
// Default for @OneToMany and @ManyToMany
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Order> orders; // Loaded only when orders is accessed
// LAZY loading requires open session when accessing the collection
// Use JOIN FETCH in HQL to avoid N+1 problem:
List<User> users = session.createQuery(
"SELECT DISTINCT u FROM User u LEFT JOIN FETCH u.orders", User.class)
.list();
// This loads users AND their orders in a single query