관계 매핑 전략(@OneToMany, @ManyToOne, @OneToOne, @ManyToMany), 상속 매핑 전략 (SINGLE_TABLE, JOINED, TABLE_PER_CLASS)

2024. 5. 30. 09:38Back-End

Java Persistence API (JPA)는 객체 관계 매핑(ORM) 기술을 통해 객체 지향 프로그래밍 언어의 객체를 관계형 데이터베이스의 테이블에 매핑한다. JPA에서는 다양한 엔티티 간의 관계를 정의할 수 있다. 본 글에서는 JPA에서의 @OneToMany, @ManyToOne, @OneToOne, @ManyToMany에 대해 설명하고, 각각의 관계에서 데이터를 저장, 수정, 삭제하는 예시를 다룰 것이다. 또한 JPA의 상속 매핑 전략에 대해서도 자세히 살펴보자.

엔티티 간의 관계

@OneToMany와 @ManyToOne

@OneToMany와 @ManyToOne 관계는 가장 흔히 사용되는 관계 중 하나로, 하나의 엔티티가 여러 엔티티와 관계를 맺는 경우이다.

예시: 국가와 도시의 관계

  • Country: 여러 City를 가질 수 있다.
  • City: 하나의 Country에 속할 수 있다.
@Entity
public class Country {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "country", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<City> cities = new ArrayList<>();

    // Getter, Setter, Constructor
}

@Entity
public class City {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "country_id")
    private Country country;

    // Getter, Setter, Constructor
}

데이터 저장, 수정, 삭제 예시

public void saveCountryAndCities(EntityManager em) {
    em.getTransaction().begin();

    Country country = new Country();
    country.setName("USA");

    City city1 = new City();
    city1.setName("New York");
    city1.setCountry(country);

    City city2 = new City();
    city2.setName("Los Angeles");
    city2.setCountry(country);

    country.getCities().add(city1);
    country.getCities().add(city2);

    em.persist(country);
    em.getTransaction().commit();
}

public void updateCity(EntityManager em, Long cityId) {
    em.getTransaction().begin();
    City city = em.find(City.class, cityId);
    if (city != null) {
        city.setName("San Francisco");
    }
    em.getTransaction().commit();
}

public void deleteCountry(EntityManager em, Long countryId) {
    em.getTransaction().begin();
    Country country = em.find(Country.class, countryId);
    if (country != null) {
        em.remove(country);
    }
    em.getTransaction().commit();
}

@OneToOne

@OneToOne 관계는 하나의 엔티티가 다른 하나의 엔티티와 1:1로 연결되는 경우이다.

예시: 사용자와 프로필의 관계

  • User: 하나의 Profile을 가질 수 있다.
  • Profile: 하나의 User에 속할 수 있다.
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "profile_id")
    private Profile profile;

    // Getter, Setter, Constructor
}

@Entity
public class Profile {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String bio;

    // Getter, Setter, Constructor
}

데이터 저장, 수정, 삭제 예시

public void saveUserAndProfile(EntityManager em) {
    em.getTransaction().begin();

    User user = new User();
    user.setUsername("johndoe");

    Profile profile = new Profile();
    profile.setBio("Software Developer");

    user.setProfile(profile);

    em.persist(user);
    em.getTransaction().commit();
}

public void updateProfile(EntityManager em, Long profileId) {
    em.getTransaction().begin();
    Profile profile = em.find(Profile.class, profileId);
    if (profile != null) {
        profile.setBio("Senior Software Developer");
    }
    em.getTransaction().commit();
}

public void deleteUser(EntityManager em, Long userId) {
    em.getTransaction().begin();
    User user = em.find(User.class, userId);
    if (user != null) {
        em.remove(user);
    }
    em.getTransaction().commit();
}
 

@ManyToMany

@ManyToMany 관계는 여러 엔티티가 서로 다수와 연결될 수 있는 경우이다.

예시: 학생과 수업의 관계

  • Student: 여러 Course를 수강할 수 있다.
  • Course: 여러 Student가 수강할 수 있다.
 
@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToMany
    @JoinTable(
        name = "student_course",
        joinColumns = @JoinColumn(name = "student_id"),
        inverseJoinColumns = @JoinColumn(name = "course_id")
    )
    private Set<Course> courses = new HashSet<>();

    // Getter, Setter, Constructor
}

@Entity
public class Course {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    @ManyToMany(mappedBy = "courses")
    private Set<Student> students = new HashSet<>();

    // Getter, Setter, Constructor
}

데이터 저장, 수정, 삭제 예시

public void saveStudentAndCourses(EntityManager em) {
    em.getTransaction().begin();

    Student student = new Student();
    student.setName("Alice");

    Course course1 = new Course();
    course1.setTitle("Mathematics");

    Course course2 = new Course();
    course2.setTitle("Physics");

    student.getCourses().add(course1);
    student.getCourses().add(course2);

    course1.getStudents().add(student);
    course2.getStudents().add(student);

    em.persist(student);
    em.getTransaction().commit();
}

public void updateCourseTitle(EntityManager em, Long courseId) {
    em.getTransaction().begin();
    Course course = em.find(Course.class, courseId);
    if (course != null) {
        course.setTitle("Advanced Mathematics");
    }
    em.getTransaction().commit();
}

public void deleteStudent(EntityManager em, Long studentId) {
    em.getTransaction().begin();
    Student student = em.find(Student.class, studentId);
    if (student != null) {
        em.remove(student);
    }
    em.getTransaction().commit();
}

엔티티 상속

JPA는 엔티티 간의 상속 관계를 정의할 수 있다. 주요 상속 전략으로는 SINGLE_TABLE, JOINED, TABLE_PER_CLASS가 있다.

SINGLE_TABLE 전략

모든 엔티티를 하나의 테이블에 저장하고, 구분 컬럼을 사용하여 각 엔티티 유형을 식별하는 전략이다.

예시

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "employee_type")
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // Getter, Setter, Constructor
}

@Entity
@DiscriminatorValue("MGR")
public class Manager extends Employee {
    private String department;

    // Getter, Setter, Constructor
}

@Entity
@DiscriminatorValue("ENG")
public class Engineer extends Employee {
    private String specialty;

    // Getter, Setter, Constructor
}
 

JOINED 전략

각 엔티티를 별도의 테이블에 저장하고, 기본 키를 사용하여 상속 계층 구조를 연결하는 전략이다.

예시

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // Getter, Setter, Constructor
}

@Entity
public class Manager extends Employee {
    private String department;

    // Getter, Setter, Constructor
}

@Entity
public class Engineer extends Employee {
    private String specialty;

    // Getter, Setter, Constructor
}

TABLE_PER_CLASS 전략

각 엔티티를 별도의 테이블에 저장하고, 각 테이블이 상속된 모든 속성을 가지는 전략이다.

예시

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    // Getter, Setter, Constructor
}

@Entity
public class Manager extends Employee {
    private String department;

    // Getter, Setter, Constructor
}

@Entity
public class Engineer extends Employee {
    private String specialty;

    // Getter, Setter, Constructor
}

데이터 저장, 수정, 삭제 예시

public void saveEmployeeAndManager(EntityManager em) {
    em.getTransaction().begin();

    Employee employee = new Employee();
    employee.setName("John Smith");

    Manager manager = new Manager();
    manager.setName("Jane Doe");
    manager.setDepartment("HR");

    em.persist(employee);
    em.persist(manager);
    em.getTransaction().commit();
}

public void updateManagerDepartment(EntityManager em, Long managerId) {
    em.getTransaction().begin();
    Manager manager = em.find(Manager.class, managerId);
    if (manager != null) {
        manager.setDepartment("Finance");
    }
    em.getTransaction().commit();
}

public void deleteEmployee(EntityManager em, Long employeeId) {
    em.getTransaction().begin();
    Employee employee = em.find(Employee.class, employeeId);
    if (employee != null) {
        em.remove(employee);
    }
    em.getTransaction().commit();
}

결론

JPA는 엔티티 간의 다양한 관계를 정의하고 관리할 수 있는 강력한 기능을 제공한다. @OneToMany, @ManyToOne, @OneToOne, @ManyToMany를 통해 데이터베이스 모델을 객체 지향적으로 표현할 수 있다. 또한, 상속 매핑 전략(SINGLE_TABLE, JOINED, TABLE_PER_CLASS)을 통해 엔티티 간의 상속 관계를 효과적으로 구현할 수 있다. 

'Back-End' 카테고리의 다른 글

kafka  (0) 2024.08.04
[AWS] EC2와 RDS 설정 및 프로젝트 배포  (0) 2024.06.12
로깅(SLF4J)  (0) 2024.05.30
JPA와 ORM  (0) 2024.05.30