🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [48]일차
🚀48일차에는 JPA에서의 관계형 테이블을 실습해본다. 1:N 처럼 (일 대 다) 관계가 아닌 N:N (다 대 다)관계와 1:1 (일 대 일)관계의 테이블을 실습해보면서 1:N 테이블과의 다른점을 배울 수 있었다.
학습 목표 : 영속성 컨텍스트 사용 시 엔티티 간 관계 정립을 이해해야함, 예외 발생 가능성을 생각해야함
학습 과정 : 회고를 통해 작성
fetch
- 기존 find() 메소드
// 데이터 조회
private static void find(){
EntityManager em = JPAUtil.getEntityManagerFactory().createEntityManager();
em.getTransaction().begin();
try{
em.find(Book.class, 1L);
}finally{
em.close();
}
}

- 이처럼 find(Book.class, 1L)을 해보면 Book의 정보뿐만 아니라
authors 테이블의 정보도 같이 가져와진 것을 확인할 수 있다.
Book
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
- 이때 Book 엔티티에 @ManyToOne(fetch = FetchType.LAZY) 처럼 fetch옵션을 줄 수 있다.
- 이 fetch옵션은 쿼리를 어떻게 가져올 것인지 설정 가능
➡️Lazy Loading 설정을 하게되면, Book엔티티를 로드할때마다 Author엔티티가 즉시 로드되지 않도록하여
성능 최적화를 할 수 있음

- 다시실행해보면 authors 테이블의 정보는 가져오지 않음
- FetchType은 기본값이 Eager Loading (빠른 로드)이므로 Book엔티티를 로드할때마다
➡️Author 엔티티가 즉시로드되는 것
➡️LAZY로 설정하면 즉시 로드되지 않도록 바꿔줄 수 있음

- 반면 @OneToMany 같은 경우는 LAZY가 기본이다.
(Book엔티티처럼 @ManyToOne 인경우 LAZY설정을 별도로 해주는 것) - 따라서 현재 Author 엔티티는 LAZY로 fetch옵션이 설정되어있다. 따라서 EAGER로 바꿔서 테스트를 해볼 수 있다.


- <- 기본 출력 /// Eager 옵션 설정 후 출력 ->
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<Book> books = new ArrayList<>();
- 정리하자면 1:N (일 대 다) 관계에서
➡️“1”에 해당하는 엔티티로 조회할때는 EAGER가 기본값
(=~Many로 끝나는 엔티티들 ➡️LAZY) : 많은 값을 가져와야할 수 있기때문에 LAZY(지연 로딩) 설정
➡️“다”에 해당하는 엔티티로 조회할때는 LAZY가 기본값
(=~One로 끝나는 엔티티들은 ➡️EAGER) = 적은 값을 가져오기때문에 EAGER(빠른 로딩) 설정
외래키 제약조건
- ForeignKey 의 제약조건은 null이거나 연관된 테이블의 기본키가 있어야한다.
- 만약 외래키로 연관되어있는 상태에서 부모 엔티티를 지우고자하면 에러가 발생할 수 있다.
- 자식 엔티티를 먼저 모두 지우고나서 부모 엔티티를 지워야하는데 번거로울 수 있다.
✅해결방법 : 해결해줄 수 있는 옵션이 cascade = CascadeType.ALL 과 같은 옵션
부모 엔티티가 삭제되면 자식 엔티티도 같이 삭제되도록 함 - orphanRemoval = true
➡️부모 엔티티가 삭제되었을 경우 그에 연관된 자식 엔티티가 더이상 소속하는 곳이 없이
“남겨진 상태”가 되는데 이 때 true 처리로 고아 객체가 생기지 않도록 삭제할 수 있다.
🚀실습 - N : N 관계의 테이블
- 다대다 관계인 두 테이블 (하지만 테이블은 employees, projects, employees_projects 3개 존재)
➡️이처럼 다대다 관계인 테이블을 연관짓고자 할때는 두 테이블을 연결하는 조인 테이블이 반드시 필요
ex. employees_projects
이 조인 테이블은 자동으로 만들어주기도 하지만 MySQL상에서 정의한 조인 테이블로 명시
(자동으로 만들어질땐 "em_pj" 처럼 만들어짐)
➡️persistence.xml 의 <property name="hibernate.hbm2ddl.auto" value="update"/> 가 자동으로 만드는 기능 수행 - 사원은 프로젝트를 여러개 할 수 있고, 프로젝트에는 사원이 여러 명 포함될 수 있으므로 (N : N 관계의 테이블인것)
- 하지만 엔티티는 employees_projects 테이블은 포함될 필요 없이
employees, projects가 각각 서로의 리스트를 가지도록 구현하면된다. - 또한 주된 엔티티 설정이 필요(=주인 엔티티 결정)
ex. Employee를 주된 엔티티로 설정하거나 Project를 주된 엔티티로 설정
둘다 주인 엔티티로써 관계 테이블의 연관 정보를 갖고 있으면 안되고,둘 중 하나의 엔티티에서 관계를 관리해야함
Employee
@ManyToMany
@JoinTable(
name = "employees_projects",
joinColumns = @JoinColumn(name = "employee_id"),
inverseJoinColumns = @JoinColumn(name = "project_id")
)
private Set<Project> projects= new HashSet<>();
- Set 자료구조를 선택한 이유
➡️리스트를 사용해도되지만 똑같은 사원이 두 번 들어가면 안되기때문에 중복을 허용하지 않는 Set을 선택 - @JoinTable 어노테이션을 사용하여 두 테이블이 연결된 조인 테이블을 명시 (employees_projects)
CREATE TABLE employees_projects (
employee_id BIGINT NOT NULL,
project_id BIGINT NOT NULL,
PRIMARY KEY (employee_id, project_id),
FOREIGN KEY (employee_id) REFERENCES employees(id),
FOREIGN KEY (project_id) REFERENCES projects(id)
);
- MySQL에서 조인 테이블 쿼리 생성
- @joinColumns : 주된 엔티티쪽의 컬럼을 명시 (Employee의 기본키인 Long id)
- @inverseJoinColumns : 반대쪽의 컬럼을 명시 (Project의 기본키인 Long id)
Project
@ManyToMany(mappedBy = "projects")
private Set<Employee> employees = new HashSet<>();
- 주된 엔티티의 반대 엔티티인 Project 엔티티에서는 @ManyToMany 어노테이션으로 mappedBy를 지정해주어야한다.
데이터 조회 - find()
- 데이터 조회 시에는 N : N 관계의 테이블이므로 각각의 엔티티를 기준으로 조회해볼 수 있다.
1.프로젝트 기준) 한 프로젝트에 속한 사원들 출력
// 1) 한 프로젝트에 속한 사원들 출력
Project project = em.find(Project.class, 1L);
log.info("[SELECT] 프로젝트 이름 : {}", project.getTitle());
for(Employee employee : project.getEmployees()){
log.info("{}의 사원 이름 : {}", project.getTitle(), employee.getName());
}

2.사원기준) 한 사원이 진행한 프로젝트들 출력
// 2) 한 사원이 진행한 프로젝트들 출력
Employee employee = em.find(Employee.class, 1L);
log.info("[SELECT] 사원 이름 : {}", employee.getName());
for(Project project2 : employee.getProjects()){
log.info("{}의 프로젝트명 : {}", employee.getName(), project2.getTitle());
}

데이터 생성 - create()
- ⚠️@ToString을 무분별하게 사용하면 다대다 테이블에서 하나의 컬럼을 출력하고자할때
그 컬럼에 해당되는 모든 다른 컬럼들도 출력이되는 무한반복 현상이 일어날 수 있다.
✅해결방법 : @ToString은 모든 필드를 toStinrg()화 해주는 것이므로 컬렉션으로 선언된 필드를 제외한
id, name(or title) 등의 필드만 toString()으로 직접 오버라이딩해준다.
// Employee 엔티티
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\\'' +
'}';
}
// Project 엔티티
@Override
public String toString() {
return "Project{" +
"id=" + id +
", title='" + title + '\\'' +
'}';
}
Employee employee = new Employee("Neco");
employee.setName("Williams");
Project project = new Project();
project.setTitle("Rightback's Defense Skills");
employee.getProjects().add(project); // 각각 넣어줌
project.getEmployees().add(employee); // 각각 넣어줌
em.persist(employee); // 각각 영속상태로 바꿔줌
em.persist(project); // 각각 영속상태로 바꿔줌
em.getTransaction().commit();
- 각각 서로의 엔티티에 추가시켜주고, 두 엔티티 모두 persist()로 영속상태 변경을 해주어야한다


데이터 생성 - create() + 예외 처리
- 예외발생 시 롤백해야한다.
두 테이블이 연관된 관계를 맺고 있기때문이다.(=트랜잭션이 2개 활동하고 있기때문이다.) - 즉 한 테이블에서의 예외발생 시에는 롤백을 해야한다는 것
- ❓예외발생 상황 가능성 :
➡️데이터 저장(persist) 또는 트랜잭션 커밋(commit) 과정에서 예외가 발생할 수 있음.
ex. 데이터 무결성 제약 조건 위반, 엔터티 매핑 오류, 영속성 컨텍스트 문제 등이 있음 - 이 프로젝트에서는 em.persist(project) 가 생략되면 영속성 컨텍스트로 관리되지 않으므로
em.persist(employee);
➡️이 코드가 실행되면 project가 persist되지 않기때문에
em.getTransaction().commit();
➡️이 메소드 호출 시점에 코드에서 예외가 발생할 것이다.
➡️commit() 시점에서 employee의 projects 관계를 저장하려고 할 때
JPA는 비영속 상태의 project를 참조하고 있다는 것을 감지하여 예외를 발생시키는 것 - ❓예외 발생 이유
1. @ManyToMany 관계는 조인 테이블을 사용하여 DB에 저장되는데
2. project가 비영속 상태이므로 employee_project(조인 테이블)에 저장할 수 없음
3. 따라서 TransientObjectException 혹은 PersistentObjectException 등의 예외가 발생하는 것
✅해결방법
1. 기존 코드처럼 em.persist(project)로 project 엔티티를 영속 상태로 변경하거나
2. @ManyToMany 관계에 cascade = CascadeType.PERSIST를 설정하여
employee를 persist()할 때, projects도 자동으로 persist()되도록 함
@ManyToMany(cascade = CascadeType.PERSIST)
private List<Project> projects = new ArrayList<>();
데이터 생성 - create() + 예외 처리 추가
catch(Exception e){ // 예외발생시
if(em.getTransaction().isActive()){ // 트랜잭션이 활동 중(=활성화중)이면
em.getTransaction().rollback(); // 롤백시켜주라는 명령
}
throw e; // 호출한 곳에서 예외를 던질 수 있도록 함
}
- try-finally문에 catch를 추가
- isActive() : 현재 EntityManager의 트랜잭션이 아직 활성화된 상태인지 확인
즉 트랜잭션이 커밋되지 않고 진행 중이라면, 이를 롤백해야 함.
(=commit() 호출 전에 예외가 발생하면, 트랜잭션은 여전히 활성화된 상태일 수 있음.) - rollback() : 예외가 발생했을 때, 이미 실행된 변경 사항을 모두 취소
즉 employee와 project가 부분적으로 저장되는 것을 방지. - throw e
➡️현재 발생한 예외를 다시 던져서 호출한 곳에서 처리할 수 있도록 함.
예외를 단순히 무시하면 문제 원인을 찾기 어려우므로,
상위 계층에서 로그를 남기거나 추가 처리를 할 수 있도록 예외를 다시 던지는 것 - 흐름을 정리하자면
➡️예외 발생 → 트랜잭션이 아직 열려 있으면 롤백 → 예외를 다시 던져서 상위 코드에서 처리
→ EntityManager 닫기
throw e보단 throw new RuntimeException("에러 메시지", e);
➡️에러 메시지와 함께 넘기면 에러 추적이 용이
데이터 수정 - update()
Employee employee = em.find(Employee.class, 1L);
String beforeEmpName = employee.getName();
employee.setName("Sven");
// Project의 3번 id에 변경된 이름의 사원을 추가
employee.getProjects().add(em.find(Project.class, 3L));
log.info("[UPDATE] 사원 이름 변경 : {} -> {}", beforeEmpName, employee.getName());
em.getTransaction().commit();
- 수정 시 insert 쿼리가 수행되어 관계를 맺고있는 조인 테이블(employees_projects)에
변경된 이름의 사원을 다시 추가하게된다. - employee.getProject9).add(em.find(Project.class, 3L));
➡️JPA는 employee와 project(3L) 간의 새로운 관계를 추가한다고 판단
➡️employees_projects(조인 테이블)에 새로운 매핑 데이터가 추가되어야 하므로, INSERT 쿼리가 발생
여기서 employee(1L)가 project(3L)와 관계가 없었다면, 새로운 관계를 생성해야 하므로
employees_projects 테이블에 INSERT 수행하는 것
따라서 update()를 수행하면 employees_projects(employee_id, project_id) 테이블에
(1L, 3L)을 추가하는 INSERT 쿼리가 발생하는 것
즉 이 코드를 수행하게되면 employee와 project 간의 새로운 관계를 저장하기 위해
employees_projects 테이블에 INSERT를 수행한다.
🚀 employee가 영속성 컨텍스트에서 영속상태로 관리하고 있는 것을 확인 및 분석
Employee employee = em.find(Employee.class, 1L);
➡️employee를 불러오고, employee.setName(”Sven”)처럼 값을 변경시켰음에도
em.persist(employee)로 해주지 않는 이유로
이미 employee가 영속상태이기때문이고 이러한 영속 상태에서는 setter를 호출해서 필드를 변경하면
자동으로 변경 사항이 감지(=Dirty Checking)되므로 명시적으로 persist()를 호출할 필요가 없음
데이터삭제 - delete()
Employee employee = em.find(Employee.class, 1L);
em.remove(employee);
em.getTransaction().commit();
- em.remove(employee)
➡️1번 사원을 삭제할때
조인 테이블(employees_projects 테이블의 employee_id = 1에 해당하는 값도 삭제되어야한다)
즉 관계테이블에 있는 사원(1L)이 삭제되어야한다는 것 - select * from employees_projects;
조인 테이블을 확인해보면


- employee_id = 1에 해당하는 row가 3개가 존재하는데 delete()를 실행하고나면
조인 테이블 자체는 엔티티가 없기때문에 관계테이블의 정보를 얻어낼수도, 알아낼 필요도 없다.
다만 연관된 엔티티가 있는 테이블(employees, projects)에 CRUD연산이 일어났을 경우
관계테이블에도 영향이 미친다는 것


- delete() 후 DB를 확인해보면 조인 테이블(employees_projects)의 employee_id = 1번 데이터가 삭제되었고,
employees 테이블의 id=1 데이터도 삭제된 것을 확인 가능
🚀실습 - 1:1관계의 테이블
CREATE TABLE persons (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE passports (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
passport_number VARCHAR(255) NOT NULL,
person_id BIGINT UNIQUE,
FOREIGN KEY (person_id) REFERENCES persons(id)
);
- passports 테이블에서 person_id라는 외래키를 갖고 있음
- 즉 DB에서 외래 키를 관리하는 쪽은 Passport 테이블이므로 “주된 엔티티는 Passport 엔티티”
Person
@OneToOne(mappedBy = "person")
private Passport passport;
public Person(String name) {
this.name = name;
}
- mappedBy 를 사용한 Person엔티티는 “주된 엔티티”가 아니며
외래키를 관리하는 반대쪽의 Passport 엔티티가 “주된 엔티티”이다.
Passport
@OneToOne
@JoinColumn(name = "person_id", unique = true)
private Person person;
public Passport(String passport_number) {
this.passport_number = passport_number;
}
- 즉 DB에서는 외래키를 관리하는 테이블이 JPA에서도 연관관계의 주인 이라는 것
- mappedBy를 사용한 Person 엔티티는 연관관계의 주인 엔티티가 아니므로,
Person엔티티에서 passport 필드에 값을 추가해도 JPA는 INSERT / UPDATE 쿼리를 실행하지 않는다.
그 예시)
Person person = new Person("Peter");
Passport passport = new Passport("A12345");
person.setPassport(passport); // 관계의 주인이 아님
- Setter로 값을 변경하려해도 JPA는 DB에 값을 변경하지 않는다.
⚠️passport 테이블의 person_id가 NULL로 남을 수도 있다. - ✅해결방법
passport.setPerson(person); // 연관관계의 주인(Passport)에서 관계 설정
- passport에서 Setter를 수행하면 passport 테이블의 person_id가 정상적으로 업데이트 된다.
Passport
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "person_id", unique = true)
private Person person;
- 이처럼 Passport 테이블이 person_id(외래 키)를 가지는 주된 엔티티
- unique = true
➡️해당 컬럼(person_id)가 유일한 값만 가질 수 있도록 제약조건을 걸어준다.
➡️각 passport는 반드시 하나의 person만 참조할 수 있으며,
동일한 person_id를 갖는 다른 passport는 존재할 수 없다는 것 (=person_id가 중복될 수 없음)
즉 unique = true 이면 한 사람은 하나의 여권만 가지도록 설정하는 것
unique = false 이면 한 사람이 여러 개의 여권을 가질 수 있게 되는 것
1:1관계를 강제할 수 있다는 특징이 있음
➡️@OneToOne 관계 설정으로도 JPA에서는 논리적인 1:1 관계를 형성할 수 있지만
DB 수준에서 강제하려면 unique=true를 추가하는 것이 좋음
데이터 조회 - find()
- 1. 여권으로 데이터 조회
Passport passport = em.find(Passport.class, 1L);
log.info("여권 번호 : {}", passport.getPassportNumber());
log.info("여권 주인 : {}", passport.getPerson().getName());

- 2. 여권 주인으로 데이터 조회
// 2. 여권 주인으로 데이터 조회
Person person = em.find(Person.class, 1L);
log.info("여권 주인 : {}", passport.getPerson().getName());
log.info("여권 번호 : {}", passport.getPassportNumber());

- 여권 데이터 조회와 여권 주인 데이터조회를 함께 출력한 예시
➡️순서를 바꿔 어느 쪽이 주체로 삼아져 데이터를 조회했는지 구분 - 만약 Person 엔티티에 @OneToOne(mappedBy = “person”) 만 선언하고 cascade 옵션은 지정하지 않았다면
Person person = new Person("Theo");
Passport passport = new Passport("Z5091312");
em.persist(passport); // 먼저 passport 엔티티를영속화시키고
person.setPassport(passport); // 영속화된 passport를 person 엔티티와 연결
em.persist(person); // passport가 담긴 person 엔티티를 영속화시킴
- 이처럼 순서에 따라 두 엔티티 모두 영속화 시켜야함
- @OneToOne(mappedBy = "person", cascade = CascadeType.*ALL*, orphanRemoval = true)
➡️이 옵션을 지정 시 Person을 persist()할때 자동으로 Passport도 영속상태로 변경될 수 있도록하므로
cascade 사용이 좀 더 코드
⚠️passports 테이블을 조회 시 새로 만든 passport 엔티티의 person_id가 NULL인 오류 해결
(person_id가 NULL로 저장되었다는 것 = Passport와 Person의 관계가 DB에 반영되지 않았다는 것)
✅해결 방법 :
Person과 Passport의 양방향 관계가 완전히 설정되지 않은 것이므로
Person person = new Person("Theo");
Passport passport = new Passport("Z5091312");
// 연관 관계 설정
person.setPassport(passport); // Person에 Passport 설정
passport.setPerson(person); // Passport에도 Person 설정
em.persist(person);
em.getTransaction().commit();
이처럼 각각의 엔티티에 연관 관계를 Setter로써 설정해주어야함
JPA의 연관관계에 따른 동작흐름을 보면
1. person.setPassport(passport); ➡️Person 객체에서 Passport를 참조하도록 설정 (JPA가 알지만 DB 반영 X)
2. passport.setPerson(person); ➡️"연관관계의 주인"인 Passport가 Person을 참조 (DB 반영 O)
3. em.persist(person); ➡️CascadeType.ALL 덕분에 Passport도 자동 저장
4. DB에서는 passports.person_id = persons.id로 정상 반영
데이터 생성 - create() : 양방향 연관관계 설정
try{
Person person = new Person("Theo");
Passport passport = new Passport("Z5091312");
// 연관 관계 설정
person.setPassport(passport);
passport.setPerson(person);
em.persist(person); // passport가 담긴 person 엔티티를 영속화시킴
em.getTransaction().commit();
}catch(Exception e){
if(em.getTransaction().isActive()){
em.getTransaction().rollback();
}
throw new RuntimeException("에러발생!", e);
}finally{
em.close();
}

- 연관 관계 설정을 제대로 해주면 person_id도 제대로 출력되는 것을 확인 가능
- 만약 em.persist(person) 이 아닌 em.persist(passport) 만 해주어도
person이 담긴 passport 엔티티를 영속화시키려면, Passport 엔티티에도 cascade 옵션을 추가
➡️양방향의 1:1 관계이기때문에 서로의 CRUD에 영향을 끼칠 수 있는 것 - CascadeType.ALL : 모든 생성, 수정, 삭제에도 영향이 생길 수 있기때문에
CascadeType.PERSIST 로 설정
Person
@OneToOne(mappedBy = "person", cascade = CascadeType.PERSIST, orphanRemoval = true)
private Passport passport;
데이터 수정 - update()
Person person = em.find(Person.class, 5L);
Passport passport = em.find(Passport.class, 4L);
String beforePNum = passport.getPassportNumber();
passport.setPassportNumber("D1111111");
person.setPassport(passport);
log.info("[UPDATE] {}의 여권 번호 변경 : {} -> {}", person.getName(), beforePNum, passport.getPassportNumber());
em.persist(person); // passport가 담긴 person 엔티티를 영속화시킴
em.getTransaction().commit();


- em.persist(person) 만해주어도 passport도 자동으로 영속을 따라가는데
그 이유는 위에서 CascadeType.PERSIST처럼 Person엔티티에 옵션을 주었기때문이다.
데이터 삭제 - delete()
- Person을 삭제
// 1. 삭제 가능
Person person = em.find(Person.class, 4L);
em.remove(person);
em.getTransaction().commit();

- Passport를 삭제
// 2. 삭제 가능하려면 추가 코드 필요
Passport passport = em.find(Passport.class, 3L);
if(passport != null){
passport.getPerson().setPassport(null);
}
em.remove(passport);
- setPassport()로 Passport를 먼저 null로 바꿔주어야한다.
- 🚀person_id가 NULL인 Passport 값 삭제
if (passport != null) {
if (passport.getPerson() != null) { // person이 null이 아닐 때만 setPassport(null)
passport.getPerson().setPassport(null);
}
em.remove(passport); // 바로 삭제
}

- 데이터가 불완전한 행(id = 3인 Passport의 person_id 값이 NULL)을 지우고자할때
- passport.getPerson()을 호출하면 null이 되어 NullPointerException이 발생한 것
- passport.getPerson()을 호출하기 전에 null인지 체크하고, setPassport(null)을 호출하지 않고 삭제
- if(passport ! = null) 블럭 안에서 passport.getPerson() ! = null 조건에 해당하지 않으면
바로 em.remove(passport)를 통해 삭제

🚀회고 결과 :
이번 회고에서는 JPA에서의 1:1, N:N 관계의 테이블 실습을 진행하였는데
실습에 오류가 많이 발생해서
(person_id 가 NULL인 경우와 예외처리 방법 등) 오류 해결을 위한 추가 공부가 필요했다.
또한 회고를 통해 양방향이나 주인 엔티티에 대한 개념을 이해하게 되었다.
- CascadeType.PERSIST 속성
- 양방향 관계 설정하는 방법
- unique = true로 1:1관계 강제하는법
- isActive(), rollback() 으로 예외처리하는 방법
느낀 점 :
다양한 관계의 테이블이 있을때 테이블 설계를 어떻게 진행해야할지를 조금이나마 배울 수 있었다.
비슷한 예제로 여러 번 실습을 진행하다보니 어노테이션 설정이나 테이블 간 관계를 설정해주는 것에 있어서
점점 이해할 수 있게된 회고였다.
향후 계획 :
- 1:1과 N:N을 나타내는 다른 테이블로 실습진행 (ex. 식당 : 가게번호, 군인 : 군번 등)
- update() 메소드 구현 시 관계형 테이블의 연관을 설정하는 부분에 대한 연습 필요
- 직접 CREATE TABLE 생성하는 연습
- persistence.xml 설정파일 공부
'Recording > 멋쟁이사자처럼 BE 13기' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_50일차_"Spring Data JPA" (0) | 2025.02.19 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_49일차_"JPA 상속 관계 매핑" (1) | 2025.02.18 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_47일차_"JPA 엔티티 매핑" (0) | 2025.02.13 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_46일차_"JPA" (0) | 2025.02.12 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_44일차_"페이징 처리" (0) | 2025.02.10 |
🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [48]일차
🚀48일차에는 JPA에서의 관계형 테이블을 실습해본다. 1:N 처럼 (일 대 다) 관계가 아닌 N:N (다 대 다)관계와 1:1 (일 대 일)관계의 테이블을 실습해보면서 1:N 테이블과의 다른점을 배울 수 있었다.
학습 목표 : 영속성 컨텍스트 사용 시 엔티티 간 관계 정립을 이해해야함, 예외 발생 가능성을 생각해야함
학습 과정 : 회고를 통해 작성
fetch
- 기존 find() 메소드
// 데이터 조회
private static void find(){
EntityManager em = JPAUtil.getEntityManagerFactory().createEntityManager();
em.getTransaction().begin();
try{
em.find(Book.class, 1L);
}finally{
em.close();
}
}

- 이처럼 find(Book.class, 1L)을 해보면 Book의 정보뿐만 아니라
authors 테이블의 정보도 같이 가져와진 것을 확인할 수 있다.
Book
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private Author author;
- 이때 Book 엔티티에 @ManyToOne(fetch = FetchType.LAZY) 처럼 fetch옵션을 줄 수 있다.
- 이 fetch옵션은 쿼리를 어떻게 가져올 것인지 설정 가능
➡️Lazy Loading 설정을 하게되면, Book엔티티를 로드할때마다 Author엔티티가 즉시 로드되지 않도록하여
성능 최적화를 할 수 있음

- 다시실행해보면 authors 테이블의 정보는 가져오지 않음
- FetchType은 기본값이 Eager Loading (빠른 로드)이므로 Book엔티티를 로드할때마다
➡️Author 엔티티가 즉시로드되는 것
➡️LAZY로 설정하면 즉시 로드되지 않도록 바꿔줄 수 있음

- 반면 @OneToMany 같은 경우는 LAZY가 기본이다.
(Book엔티티처럼 @ManyToOne 인경우 LAZY설정을 별도로 해주는 것) - 따라서 현재 Author 엔티티는 LAZY로 fetch옵션이 설정되어있다. 따라서 EAGER로 바꿔서 테스트를 해볼 수 있다.


- <- 기본 출력 /// Eager 옵션 설정 후 출력 ->
@OneToMany(mappedBy = "author", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<Book> books = new ArrayList<>();
- 정리하자면 1:N (일 대 다) 관계에서
➡️“1”에 해당하는 엔티티로 조회할때는 EAGER가 기본값
(=~Many로 끝나는 엔티티들 ➡️LAZY) : 많은 값을 가져와야할 수 있기때문에 LAZY(지연 로딩) 설정
➡️“다”에 해당하는 엔티티로 조회할때는 LAZY가 기본값
(=~One로 끝나는 엔티티들은 ➡️EAGER) = 적은 값을 가져오기때문에 EAGER(빠른 로딩) 설정
외래키 제약조건
- ForeignKey 의 제약조건은 null이거나 연관된 테이블의 기본키가 있어야한다.
- 만약 외래키로 연관되어있는 상태에서 부모 엔티티를 지우고자하면 에러가 발생할 수 있다.
- 자식 엔티티를 먼저 모두 지우고나서 부모 엔티티를 지워야하는데 번거로울 수 있다.
✅해결방법 : 해결해줄 수 있는 옵션이 cascade = CascadeType.ALL 과 같은 옵션
부모 엔티티가 삭제되면 자식 엔티티도 같이 삭제되도록 함 - orphanRemoval = true
➡️부모 엔티티가 삭제되었을 경우 그에 연관된 자식 엔티티가 더이상 소속하는 곳이 없이
“남겨진 상태”가 되는데 이 때 true 처리로 고아 객체가 생기지 않도록 삭제할 수 있다.
🚀실습 - N : N 관계의 테이블
- 다대다 관계인 두 테이블 (하지만 테이블은 employees, projects, employees_projects 3개 존재)
➡️이처럼 다대다 관계인 테이블을 연관짓고자 할때는 두 테이블을 연결하는 조인 테이블이 반드시 필요
ex. employees_projects
이 조인 테이블은 자동으로 만들어주기도 하지만 MySQL상에서 정의한 조인 테이블로 명시
(자동으로 만들어질땐 "em_pj" 처럼 만들어짐)
➡️persistence.xml 의 <property name="hibernate.hbm2ddl.auto" value="update"/> 가 자동으로 만드는 기능 수행 - 사원은 프로젝트를 여러개 할 수 있고, 프로젝트에는 사원이 여러 명 포함될 수 있으므로 (N : N 관계의 테이블인것)
- 하지만 엔티티는 employees_projects 테이블은 포함될 필요 없이
employees, projects가 각각 서로의 리스트를 가지도록 구현하면된다. - 또한 주된 엔티티 설정이 필요(=주인 엔티티 결정)
ex. Employee를 주된 엔티티로 설정하거나 Project를 주된 엔티티로 설정
둘다 주인 엔티티로써 관계 테이블의 연관 정보를 갖고 있으면 안되고,둘 중 하나의 엔티티에서 관계를 관리해야함
Employee
@ManyToMany
@JoinTable(
name = "employees_projects",
joinColumns = @JoinColumn(name = "employee_id"),
inverseJoinColumns = @JoinColumn(name = "project_id")
)
private Set<Project> projects= new HashSet<>();
- Set 자료구조를 선택한 이유
➡️리스트를 사용해도되지만 똑같은 사원이 두 번 들어가면 안되기때문에 중복을 허용하지 않는 Set을 선택 - @JoinTable 어노테이션을 사용하여 두 테이블이 연결된 조인 테이블을 명시 (employees_projects)
CREATE TABLE employees_projects (
employee_id BIGINT NOT NULL,
project_id BIGINT NOT NULL,
PRIMARY KEY (employee_id, project_id),
FOREIGN KEY (employee_id) REFERENCES employees(id),
FOREIGN KEY (project_id) REFERENCES projects(id)
);
- MySQL에서 조인 테이블 쿼리 생성
- @joinColumns : 주된 엔티티쪽의 컬럼을 명시 (Employee의 기본키인 Long id)
- @inverseJoinColumns : 반대쪽의 컬럼을 명시 (Project의 기본키인 Long id)
Project
@ManyToMany(mappedBy = "projects")
private Set<Employee> employees = new HashSet<>();
- 주된 엔티티의 반대 엔티티인 Project 엔티티에서는 @ManyToMany 어노테이션으로 mappedBy를 지정해주어야한다.
데이터 조회 - find()
- 데이터 조회 시에는 N : N 관계의 테이블이므로 각각의 엔티티를 기준으로 조회해볼 수 있다.
1.프로젝트 기준) 한 프로젝트에 속한 사원들 출력
// 1) 한 프로젝트에 속한 사원들 출력
Project project = em.find(Project.class, 1L);
log.info("[SELECT] 프로젝트 이름 : {}", project.getTitle());
for(Employee employee : project.getEmployees()){
log.info("{}의 사원 이름 : {}", project.getTitle(), employee.getName());
}

2.사원기준) 한 사원이 진행한 프로젝트들 출력
// 2) 한 사원이 진행한 프로젝트들 출력
Employee employee = em.find(Employee.class, 1L);
log.info("[SELECT] 사원 이름 : {}", employee.getName());
for(Project project2 : employee.getProjects()){
log.info("{}의 프로젝트명 : {}", employee.getName(), project2.getTitle());
}

데이터 생성 - create()
- ⚠️@ToString을 무분별하게 사용하면 다대다 테이블에서 하나의 컬럼을 출력하고자할때
그 컬럼에 해당되는 모든 다른 컬럼들도 출력이되는 무한반복 현상이 일어날 수 있다.
✅해결방법 : @ToString은 모든 필드를 toStinrg()화 해주는 것이므로 컬렉션으로 선언된 필드를 제외한
id, name(or title) 등의 필드만 toString()으로 직접 오버라이딩해준다.
// Employee 엔티티
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\\'' +
'}';
}
// Project 엔티티
@Override
public String toString() {
return "Project{" +
"id=" + id +
", title='" + title + '\\'' +
'}';
}
Employee employee = new Employee("Neco");
employee.setName("Williams");
Project project = new Project();
project.setTitle("Rightback's Defense Skills");
employee.getProjects().add(project); // 각각 넣어줌
project.getEmployees().add(employee); // 각각 넣어줌
em.persist(employee); // 각각 영속상태로 바꿔줌
em.persist(project); // 각각 영속상태로 바꿔줌
em.getTransaction().commit();
- 각각 서로의 엔티티에 추가시켜주고, 두 엔티티 모두 persist()로 영속상태 변경을 해주어야한다


데이터 생성 - create() + 예외 처리
- 예외발생 시 롤백해야한다.
두 테이블이 연관된 관계를 맺고 있기때문이다.(=트랜잭션이 2개 활동하고 있기때문이다.) - 즉 한 테이블에서의 예외발생 시에는 롤백을 해야한다는 것
- ❓예외발생 상황 가능성 :
➡️데이터 저장(persist) 또는 트랜잭션 커밋(commit) 과정에서 예외가 발생할 수 있음.
ex. 데이터 무결성 제약 조건 위반, 엔터티 매핑 오류, 영속성 컨텍스트 문제 등이 있음 - 이 프로젝트에서는 em.persist(project) 가 생략되면 영속성 컨텍스트로 관리되지 않으므로
em.persist(employee);
➡️이 코드가 실행되면 project가 persist되지 않기때문에
em.getTransaction().commit();
➡️이 메소드 호출 시점에 코드에서 예외가 발생할 것이다.
➡️commit() 시점에서 employee의 projects 관계를 저장하려고 할 때
JPA는 비영속 상태의 project를 참조하고 있다는 것을 감지하여 예외를 발생시키는 것 - ❓예외 발생 이유
1. @ManyToMany 관계는 조인 테이블을 사용하여 DB에 저장되는데
2. project가 비영속 상태이므로 employee_project(조인 테이블)에 저장할 수 없음
3. 따라서 TransientObjectException 혹은 PersistentObjectException 등의 예외가 발생하는 것
✅해결방법
1. 기존 코드처럼 em.persist(project)로 project 엔티티를 영속 상태로 변경하거나
2. @ManyToMany 관계에 cascade = CascadeType.PERSIST를 설정하여
employee를 persist()할 때, projects도 자동으로 persist()되도록 함
@ManyToMany(cascade = CascadeType.PERSIST)
private List<Project> projects = new ArrayList<>();
데이터 생성 - create() + 예외 처리 추가
catch(Exception e){ // 예외발생시
if(em.getTransaction().isActive()){ // 트랜잭션이 활동 중(=활성화중)이면
em.getTransaction().rollback(); // 롤백시켜주라는 명령
}
throw e; // 호출한 곳에서 예외를 던질 수 있도록 함
}
- try-finally문에 catch를 추가
- isActive() : 현재 EntityManager의 트랜잭션이 아직 활성화된 상태인지 확인
즉 트랜잭션이 커밋되지 않고 진행 중이라면, 이를 롤백해야 함.
(=commit() 호출 전에 예외가 발생하면, 트랜잭션은 여전히 활성화된 상태일 수 있음.) - rollback() : 예외가 발생했을 때, 이미 실행된 변경 사항을 모두 취소
즉 employee와 project가 부분적으로 저장되는 것을 방지. - throw e
➡️현재 발생한 예외를 다시 던져서 호출한 곳에서 처리할 수 있도록 함.
예외를 단순히 무시하면 문제 원인을 찾기 어려우므로,
상위 계층에서 로그를 남기거나 추가 처리를 할 수 있도록 예외를 다시 던지는 것 - 흐름을 정리하자면
➡️예외 발생 → 트랜잭션이 아직 열려 있으면 롤백 → 예외를 다시 던져서 상위 코드에서 처리
→ EntityManager 닫기
throw e보단 throw new RuntimeException("에러 메시지", e);
➡️에러 메시지와 함께 넘기면 에러 추적이 용이
데이터 수정 - update()
Employee employee = em.find(Employee.class, 1L);
String beforeEmpName = employee.getName();
employee.setName("Sven");
// Project의 3번 id에 변경된 이름의 사원을 추가
employee.getProjects().add(em.find(Project.class, 3L));
log.info("[UPDATE] 사원 이름 변경 : {} -> {}", beforeEmpName, employee.getName());
em.getTransaction().commit();
- 수정 시 insert 쿼리가 수행되어 관계를 맺고있는 조인 테이블(employees_projects)에
변경된 이름의 사원을 다시 추가하게된다. - employee.getProject9).add(em.find(Project.class, 3L));
➡️JPA는 employee와 project(3L) 간의 새로운 관계를 추가한다고 판단
➡️employees_projects(조인 테이블)에 새로운 매핑 데이터가 추가되어야 하므로, INSERT 쿼리가 발생
여기서 employee(1L)가 project(3L)와 관계가 없었다면, 새로운 관계를 생성해야 하므로
employees_projects 테이블에 INSERT 수행하는 것
따라서 update()를 수행하면 employees_projects(employee_id, project_id) 테이블에
(1L, 3L)을 추가하는 INSERT 쿼리가 발생하는 것
즉 이 코드를 수행하게되면 employee와 project 간의 새로운 관계를 저장하기 위해
employees_projects 테이블에 INSERT를 수행한다.
🚀 employee가 영속성 컨텍스트에서 영속상태로 관리하고 있는 것을 확인 및 분석
Employee employee = em.find(Employee.class, 1L);
➡️employee를 불러오고, employee.setName(”Sven”)처럼 값을 변경시켰음에도
em.persist(employee)로 해주지 않는 이유로
이미 employee가 영속상태이기때문이고 이러한 영속 상태에서는 setter를 호출해서 필드를 변경하면
자동으로 변경 사항이 감지(=Dirty Checking)되므로 명시적으로 persist()를 호출할 필요가 없음
데이터삭제 - delete()
Employee employee = em.find(Employee.class, 1L);
em.remove(employee);
em.getTransaction().commit();
- em.remove(employee)
➡️1번 사원을 삭제할때
조인 테이블(employees_projects 테이블의 employee_id = 1에 해당하는 값도 삭제되어야한다)
즉 관계테이블에 있는 사원(1L)이 삭제되어야한다는 것 - select * from employees_projects;
조인 테이블을 확인해보면


- employee_id = 1에 해당하는 row가 3개가 존재하는데 delete()를 실행하고나면
조인 테이블 자체는 엔티티가 없기때문에 관계테이블의 정보를 얻어낼수도, 알아낼 필요도 없다.
다만 연관된 엔티티가 있는 테이블(employees, projects)에 CRUD연산이 일어났을 경우
관계테이블에도 영향이 미친다는 것


- delete() 후 DB를 확인해보면 조인 테이블(employees_projects)의 employee_id = 1번 데이터가 삭제되었고,
employees 테이블의 id=1 데이터도 삭제된 것을 확인 가능
🚀실습 - 1:1관계의 테이블
CREATE TABLE persons (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE passports (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
passport_number VARCHAR(255) NOT NULL,
person_id BIGINT UNIQUE,
FOREIGN KEY (person_id) REFERENCES persons(id)
);
- passports 테이블에서 person_id라는 외래키를 갖고 있음
- 즉 DB에서 외래 키를 관리하는 쪽은 Passport 테이블이므로 “주된 엔티티는 Passport 엔티티”
Person
@OneToOne(mappedBy = "person")
private Passport passport;
public Person(String name) {
this.name = name;
}
- mappedBy 를 사용한 Person엔티티는 “주된 엔티티”가 아니며
외래키를 관리하는 반대쪽의 Passport 엔티티가 “주된 엔티티”이다.
Passport
@OneToOne
@JoinColumn(name = "person_id", unique = true)
private Person person;
public Passport(String passport_number) {
this.passport_number = passport_number;
}
- 즉 DB에서는 외래키를 관리하는 테이블이 JPA에서도 연관관계의 주인 이라는 것
- mappedBy를 사용한 Person 엔티티는 연관관계의 주인 엔티티가 아니므로,
Person엔티티에서 passport 필드에 값을 추가해도 JPA는 INSERT / UPDATE 쿼리를 실행하지 않는다.
그 예시)
Person person = new Person("Peter");
Passport passport = new Passport("A12345");
person.setPassport(passport); // 관계의 주인이 아님
- Setter로 값을 변경하려해도 JPA는 DB에 값을 변경하지 않는다.
⚠️passport 테이블의 person_id가 NULL로 남을 수도 있다. - ✅해결방법
passport.setPerson(person); // 연관관계의 주인(Passport)에서 관계 설정
- passport에서 Setter를 수행하면 passport 테이블의 person_id가 정상적으로 업데이트 된다.
Passport
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "person_id", unique = true)
private Person person;
- 이처럼 Passport 테이블이 person_id(외래 키)를 가지는 주된 엔티티
- unique = true
➡️해당 컬럼(person_id)가 유일한 값만 가질 수 있도록 제약조건을 걸어준다.
➡️각 passport는 반드시 하나의 person만 참조할 수 있으며,
동일한 person_id를 갖는 다른 passport는 존재할 수 없다는 것 (=person_id가 중복될 수 없음)
즉 unique = true 이면 한 사람은 하나의 여권만 가지도록 설정하는 것
unique = false 이면 한 사람이 여러 개의 여권을 가질 수 있게 되는 것
1:1관계를 강제할 수 있다는 특징이 있음
➡️@OneToOne 관계 설정으로도 JPA에서는 논리적인 1:1 관계를 형성할 수 있지만
DB 수준에서 강제하려면 unique=true를 추가하는 것이 좋음
데이터 조회 - find()
- 1. 여권으로 데이터 조회
Passport passport = em.find(Passport.class, 1L);
log.info("여권 번호 : {}", passport.getPassportNumber());
log.info("여권 주인 : {}", passport.getPerson().getName());

- 2. 여권 주인으로 데이터 조회
// 2. 여권 주인으로 데이터 조회
Person person = em.find(Person.class, 1L);
log.info("여권 주인 : {}", passport.getPerson().getName());
log.info("여권 번호 : {}", passport.getPassportNumber());

- 여권 데이터 조회와 여권 주인 데이터조회를 함께 출력한 예시
➡️순서를 바꿔 어느 쪽이 주체로 삼아져 데이터를 조회했는지 구분 - 만약 Person 엔티티에 @OneToOne(mappedBy = “person”) 만 선언하고 cascade 옵션은 지정하지 않았다면
Person person = new Person("Theo");
Passport passport = new Passport("Z5091312");
em.persist(passport); // 먼저 passport 엔티티를영속화시키고
person.setPassport(passport); // 영속화된 passport를 person 엔티티와 연결
em.persist(person); // passport가 담긴 person 엔티티를 영속화시킴
- 이처럼 순서에 따라 두 엔티티 모두 영속화 시켜야함
- @OneToOne(mappedBy = "person", cascade = CascadeType.*ALL*, orphanRemoval = true)
➡️이 옵션을 지정 시 Person을 persist()할때 자동으로 Passport도 영속상태로 변경될 수 있도록하므로
cascade 사용이 좀 더 코드
⚠️passports 테이블을 조회 시 새로 만든 passport 엔티티의 person_id가 NULL인 오류 해결
(person_id가 NULL로 저장되었다는 것 = Passport와 Person의 관계가 DB에 반영되지 않았다는 것)
✅해결 방법 :
Person과 Passport의 양방향 관계가 완전히 설정되지 않은 것이므로
Person person = new Person("Theo");
Passport passport = new Passport("Z5091312");
// 연관 관계 설정
person.setPassport(passport); // Person에 Passport 설정
passport.setPerson(person); // Passport에도 Person 설정
em.persist(person);
em.getTransaction().commit();
이처럼 각각의 엔티티에 연관 관계를 Setter로써 설정해주어야함
JPA의 연관관계에 따른 동작흐름을 보면
1. person.setPassport(passport); ➡️Person 객체에서 Passport를 참조하도록 설정 (JPA가 알지만 DB 반영 X)
2. passport.setPerson(person); ➡️"연관관계의 주인"인 Passport가 Person을 참조 (DB 반영 O)
3. em.persist(person); ➡️CascadeType.ALL 덕분에 Passport도 자동 저장
4. DB에서는 passports.person_id = persons.id로 정상 반영
데이터 생성 - create() : 양방향 연관관계 설정
try{
Person person = new Person("Theo");
Passport passport = new Passport("Z5091312");
// 연관 관계 설정
person.setPassport(passport);
passport.setPerson(person);
em.persist(person); // passport가 담긴 person 엔티티를 영속화시킴
em.getTransaction().commit();
}catch(Exception e){
if(em.getTransaction().isActive()){
em.getTransaction().rollback();
}
throw new RuntimeException("에러발생!", e);
}finally{
em.close();
}

- 연관 관계 설정을 제대로 해주면 person_id도 제대로 출력되는 것을 확인 가능
- 만약 em.persist(person) 이 아닌 em.persist(passport) 만 해주어도
person이 담긴 passport 엔티티를 영속화시키려면, Passport 엔티티에도 cascade 옵션을 추가
➡️양방향의 1:1 관계이기때문에 서로의 CRUD에 영향을 끼칠 수 있는 것 - CascadeType.ALL : 모든 생성, 수정, 삭제에도 영향이 생길 수 있기때문에
CascadeType.PERSIST 로 설정
Person
@OneToOne(mappedBy = "person", cascade = CascadeType.PERSIST, orphanRemoval = true)
private Passport passport;
데이터 수정 - update()
Person person = em.find(Person.class, 5L);
Passport passport = em.find(Passport.class, 4L);
String beforePNum = passport.getPassportNumber();
passport.setPassportNumber("D1111111");
person.setPassport(passport);
log.info("[UPDATE] {}의 여권 번호 변경 : {} -> {}", person.getName(), beforePNum, passport.getPassportNumber());
em.persist(person); // passport가 담긴 person 엔티티를 영속화시킴
em.getTransaction().commit();


- em.persist(person) 만해주어도 passport도 자동으로 영속을 따라가는데
그 이유는 위에서 CascadeType.PERSIST처럼 Person엔티티에 옵션을 주었기때문이다.
데이터 삭제 - delete()
- Person을 삭제
// 1. 삭제 가능
Person person = em.find(Person.class, 4L);
em.remove(person);
em.getTransaction().commit();

- Passport를 삭제
// 2. 삭제 가능하려면 추가 코드 필요
Passport passport = em.find(Passport.class, 3L);
if(passport != null){
passport.getPerson().setPassport(null);
}
em.remove(passport);
- setPassport()로 Passport를 먼저 null로 바꿔주어야한다.
- 🚀person_id가 NULL인 Passport 값 삭제
if (passport != null) {
if (passport.getPerson() != null) { // person이 null이 아닐 때만 setPassport(null)
passport.getPerson().setPassport(null);
}
em.remove(passport); // 바로 삭제
}

- 데이터가 불완전한 행(id = 3인 Passport의 person_id 값이 NULL)을 지우고자할때
- passport.getPerson()을 호출하면 null이 되어 NullPointerException이 발생한 것
- passport.getPerson()을 호출하기 전에 null인지 체크하고, setPassport(null)을 호출하지 않고 삭제
- if(passport ! = null) 블럭 안에서 passport.getPerson() ! = null 조건에 해당하지 않으면
바로 em.remove(passport)를 통해 삭제

🚀회고 결과 :
이번 회고에서는 JPA에서의 1:1, N:N 관계의 테이블 실습을 진행하였는데
실습에 오류가 많이 발생해서
(person_id 가 NULL인 경우와 예외처리 방법 등) 오류 해결을 위한 추가 공부가 필요했다.
또한 회고를 통해 양방향이나 주인 엔티티에 대한 개념을 이해하게 되었다.
- CascadeType.PERSIST 속성
- 양방향 관계 설정하는 방법
- unique = true로 1:1관계 강제하는법
- isActive(), rollback() 으로 예외처리하는 방법
느낀 점 :
다양한 관계의 테이블이 있을때 테이블 설계를 어떻게 진행해야할지를 조금이나마 배울 수 있었다.
비슷한 예제로 여러 번 실습을 진행하다보니 어노테이션 설정이나 테이블 간 관계를 설정해주는 것에 있어서
점점 이해할 수 있게된 회고였다.
향후 계획 :
- 1:1과 N:N을 나타내는 다른 테이블로 실습진행 (ex. 식당 : 가게번호, 군인 : 군번 등)
- update() 메소드 구현 시 관계형 테이블의 연관을 설정하는 부분에 대한 연습 필요
- 직접 CREATE TABLE 생성하는 연습
- persistence.xml 설정파일 공부
'Recording > 멋쟁이사자처럼 BE 13기' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_50일차_"Spring Data JPA" (0) | 2025.02.19 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_49일차_"JPA 상속 관계 매핑" (1) | 2025.02.18 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_47일차_"JPA 엔티티 매핑" (0) | 2025.02.13 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_46일차_"JPA" (0) | 2025.02.12 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_44일차_"페이징 처리" (0) | 2025.02.10 |