JPA 3

j0yy00n0·2025년 4월 14일
post-thumbnail

2025.03.27

JPA

JPA 연관관계

JPA에서 테이블 간 연간관계

Entity 클래스 간의 관계를 정의하는 것

연관관계 종류

  • 단방향 : 참조에 의한 객체의 연관관계
  • 양방향 : 한 객체의 반대 쪽에도 필드를 추가하여 참조를 보관한다.(단반향 2개)
  • N:1(ManyToOne)
  • 1:N(OneToMany)
  • 1:1(OneToOne)
  • N:M(ManyToMany)

DB와 JPA 차이

DB에서는 연관관계를 한쪽에서만 관리

  • 외래키 있는 쪽이 많은 쪽이며 많은 쪽을 가리키게 된다.
    JPA는 객체 간 관계이기 때문에 방향성이 존재, 각 방향을 명확히 정의해줘야한다.
  • 어느 객체가 어느 객체를 참조하는지 기준
  • 단방향, 양방향이 존재하기 때문에 양쪽 모두 명시를 해줘야 한다.
  • 단방향 : 한 쪽만 참조/양방향 : 서로 참조, (List<엔티티> 등으로 컬렉션 매핑)
  • 연관 관계 정의는 하나의 테이블을 기준으로 생각하면 좋다.
  • Fk를 받고 있는 테이블이 사용빈도가 클 확률이 높다.(ManyToOne)
  • 다대다 관계는 교차 엔티티(중간 테이블)를 넣어서 다른 연관관계로 변경 해줘야 데이터 무결성을 높일 수 있다.

JPA 연관관계 생성

  • persistence.xml 설정
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.2">

    <!-- persistence-unit 이름은 JPA 설정 시 참조되는 식별자 -->
    <persistence-unit name="퍼시스턴스유닛이름">

        <!-- 관리할 엔티티 클래스 등록 -->
        <class>엔티티패키지경로.엔티티클래스1</class>
        <class>엔티티패키지경로.엔티티클래스2</class>

        <properties>
            <!-- JDBC DB 연결 설정 -->
            <property name="jakarta.persistence.jdbc.driver" value="Jcom.mysql.cj.jdbc.Driver"/>
            <property name="jakarta.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/DB이름"/>
            <property name="jakarta.persistence.jdbc.user" value="DB사용자ID"/>
            <property name="jakarta.persistence.jdbc.password" value="DB비밀번호"/>

            <!-- SQL 로그 출력 설정 -->
            <!-- 실행 SQL 출력 -->
            <property name="hibernate.show_sql" value="true"/>
            <!-- SQL 보기 좋게 정렬 출력 -->
            <property name="hibernate.format_sql" value="true"/>       

            <!-- DDL 생성 전략 설정 -->
            <property name="hibernate.hbm2ddl.auto" value="none"/>
            <!--
                개발 환경에서 주로 사용하는 값
                - create : 실행 시 기존 테이블 DROP 후 재생성
                - create-drop : 실행 시 생성, 종료 시 DROP
                - update : 변경된 부분만 반영 (보존된 데이터와 함께)

                운영 환경에서 주로 사용하는 값
                - validate : 테이블/컬럼 유효성 검사만 (DDL 생성 X)
                - none : 아무 작업도 하지 않음 (기본값), 
                		 DB 테이블과 일치하게 작성
            -->
        </properties>

    </persistence-unit>
</persistence>

ManyToOne

  • 상위 Entity 생성 (1 부분)
@Entity(name = "상위엔티티논리이름")
<!-- 실제 DB 테이블 이름 -->
@Table(name = "상위테이블명")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class 상위엔티티클래스명 {

    @Id
    <!-- 기본키(PK) 컬럼 지정 -->
    @Column(name = "기본키컬럼명")
    private 기본키자료형 기본키필드;

	<!-- 일반 정보 컬럼 -->
    @Column(name = "일반컬럼명1")
    private 자료형 필드1;

	<!-- 외래키 컬럼 (단순 필드로만 작성된 경우) -->
    <!-- 상위 엔티티는 외래키를 가질 필요 없이 그 자체로 참조 대상,
    	 일반적으로 하위 엔티티에서 외래키를 소유하지만,
         상위 엔티티 구조 이해를 돕기 위해 명시적으로 표현 -->
    @Column(name = "외래키컬럼명")
    private 자료형 외래키필드;
}
  • 외래키 가지고 있는 참조(하위) Entity 생성 (N 부분)
@Entity(name = "하위엔티티논리이름")
@Table(name = "하위테이블명")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class 하위엔티티클래스명 {

    @Id
    @Column(name = "기본키컬럼명")
    private 기본키자료형 기본키필드;

    @Column(name = "일반컬럼1")
    private 자료형 일반필드1;

    @Column(name = "일반컬럼2")
    private 자료형 일반필드2;

	<!-- CascadeType.PERSIST : 하위 엔티티를 persist()할 때,
    	 연관된 상위 엔티티도 자동으로 persist() 되도록 하는 설정 -->
    @ManyToOne(cascade = CascadeType.PERSIST)
    <!-- 상위 테이블의 PK를 참조하는 외래키 -->
    @JoinColumn(name = "외래키컬럼명")
    private 상위엔티티클래스명 상위엔티티변수;

    @Column(name = "일반컬럼3")
    private 자료형 일반필드3;
}
  • @JoinColumn : 외래키를 매핑하기 위해 사용되는 어노테이션
JoinColumn 어노테이션에서 사용할 수 있는 속성
- 'name' : 참조하는 테이블의 컬럼명을 지정한다. <!-- 이 부분만 많이 쓰고 아래는 잘 쓰지 않는다. -->
- 'referencedColumnName' : 참조되는 테이블의 컬럼명을 지정한다.
- 'nullable' : 참조하는 테이블의 컬럼에 null 값을 허용할지 지정한다.
- 'unique' : 참조하는 테이블의 컬럼에 유일성 제약조건을 추가할지 지정한다.
- 'insertable' : 새로운 엔티티가 저장될 때, 이 참조 컬럼이 SQL INSERT에 포함될지 지정한다.
- 'updatable' : 엔티티가 업데이트될때, 이 참조 컬럼이 SQL UPDATE에 포함될지 지정한다.
- 'columnDefinition' : 이 참조 컬럼에 대한 SQL DDL을 직접 지정한다.
- 'table' : 참조하는 테이블의 이름을 지정한다.
- 'foreginKey' : 참조하는 테이블에 생성될 외래 키에 대한 추가 정보를 지정한다.
  • @ManyToOne : 다대일 연관관계에서 사용되는 어노테이션
ManyToOne 어노테이션에서 사용할 수 있는 속성
<!-- cascade, fetch 중요 -->
- 'cascade' : 연관된 엔티티에 대한 영속성 전이를 설정한다. 
	트랜젝션 내에서 하위/상위 엔티티를 함께 처리할 때 필요
- 'fetch' : 연관된 엔티티를 로딩하는 전략 설정(EAGER : 즉시 로딩, LAZY : 지연 로딩) 
	DB에서 데이터를 가져오는 시점을 제어
			@OneToMany(fetch = FetchType.LAZY)
        	@ManyToMany         -- 지연로딩이 default
    		@ManyToOne(fetch = FetchType.EAGER)
    		@OneToOne           -- 이른로딩이 default
- 'optional' : 연관된 엔티티가 필수인지 선택인지를 설정한다.
  • 테스트 코드 작성
public class ManyToOneAssociationTests {

    private static EntityManagerFactory entityManagerFactory;
    private static EntityManager entityManager;

    @BeforeAll
    public static void initFactory() {
        <!-- persistence.xml의 persistence-unit 이름 사용 -->
        entityManagerFactory = Persistence.createEntityManagerFactory("퍼시스턴스유닛이름");
    }

    @BeforeEach
    public void initManager() {
        entityManager = entityManagerFactory.createEntityManager();
    }

    @AfterAll
    public static void closeFactory() {
        entityManagerFactory.close();
    }

    @AfterEach
    public void closeManager() {
        entityManager.close();
    }
    
    <!-- 여러 테스트 -->
}
    @Test
    @DisplayName("다대일 연관관계 객체 그래프 탐색을 이용한 조회 테스트")
    public void 연관관계조회테스트() {

        <!-- given : 조회할 엔티티의 기본키 값 -->
        기본키자료형 기본키변수 = 기본키값;

        <!-- when : 연관관계가 설정된 엔티티 조회 후, 연관된 상위 엔티티 접근 -->
        하위엔티티클래스명 조회된하위엔티티변수 = entityManager.find(하위엔티티클래스명.class, 기본키변수);
        상위엔티티클래스명 연관된상위엔티티변수 = 조회된하위엔티티변수.get상위엔티티변수();

        <!-- then : 연관 엔티티가 정상적으로 조회되었는지 확인 -->
        assertNotNull(연관된상위엔티티변수);
        System.out.println("연관된상위엔티티변수 = " + 연관된상위엔티티변수);
	}

	@Test
    @DisplayName("다대일 연관관계 객체지향쿼리(JPQL) 사용한 연관 엔티티 필드 조회 테스트")
    public void 연관관계JPQL조회테스트() {

        <!-- given : JPQL 작성 (엔티티 기준으로 작성) -->
        String jpql변수 = "select 상위엔티티별칭.조회필드 from 하위엔티티명 하위엔티티별칭 " +
                      "join 하위엔티티별칭.상위엔티티필드명 상위엔티티별칭 " +
                      "where 하위엔티티별칭.기본키필드명 = :값";

        <!-- when : 쿼리 실행 후 단일 결과 조회 -->
        반환자료형 결과변수 = entityManager
                                .createQuery(jpql, 반환자료형.class)
                                .getSingleResult();

        <!-- then : 결과 검증 -->
        assertNotNull(결과변수);
        System.out.println("조회된결과 = " + 결과변수);
    }

	@Test
    @DisplayName("다대일 연관관계 엔티티 삽입 테스트")
    public void manyToOneInsertTest() {

        <!-- given : 하위 엔티티 및 상위 엔티티 객체 생성 및 설정 -->
        하위엔티티클래스 하위엔티티변수 = new 하위엔티티클래스();
        하위엔티티변수.set기본키(기본키값);
        하위엔티티변수.set필드1("특정값1");
        하위엔티티변수.set필드2(특정값2);

        상위엔티티클래스 상위엔티티변수 = new 상위엔티티클래스();
        상위엔티티변수.set기본키(상위기본키값);
        상위엔티티변수.set필드1("특정값3");
        상위엔티티변수.set필드2(참조코드값);

		<!-- 연관관계 설정 -->
        하위엔티티변수.set하위의상위연관관계필드(상위엔티티변수);  

        하위엔티티변수.set추가필드("추가값");

        <!-- when : 트랜잭션 시작 → 영속화 → 커밋 -->
        EntityTransaction transaction변수 = entityManager.getTransaction();
        transaction변수.begin();

		<!-- CascadeType.PERSIST 설정 시 상위도 함께 저장됨 -->
        entityManager.persist(하위엔티티변수);
        transaction변수.commit();

        <!-- then : 저장된 객체 조회 및 검증 -->
        하위엔티티클래스 조회결과변수 = entityManager.find(하위엔티티클래스.class, 기본키값);

        assertEquals(기본키값, 조회결과변수.get기본키());
        assertEquals(상위기본키값, 조회결과변수.get하위의상위연관관계필드().get기본키());
    }

OneToMany

  • 상위 Entity 클래스 생성 (1 부분)
@Entity(name = "상위엔티티논리이름")
<!-- 실제 DB 테이블 이름 -->
@Table(name = "상위테이블명")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class 상위엔티티클래스명 {

    @Id
    @Column(name = "기본키_컬럼명")
    private 기본키자료형 기본키필드;

    @Column(name = "일반컬럼명1")
    private 자료형 필드1;

    @Column(name = "일반컬럼명2")
    private 자료형 필드2;

    @OneToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
    @JoinColumn(name = "외래키컬럼명") // 하위 엔티티에서 외래키로 참조
    private List<하위엔티티클래스명> 하위엔티티컬렉션변수;
}
  • 하위 Entity 클래스 생성 (N 부분)
@Entity(name = "하위엔티티논리이름") 
@Table(name = "하위테이블명")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class 하위엔티티클래스명 {

    @Id
    @Column(name = "기본키_컬럼명")
    private 기본키자료형 기본키필드;

    @Column(name = "일반컬럼명1")
    private 자료형 필드1;

    @Column(name = "일반컬럼명2")
    private 자료형 필드2;

	<!-- 상위 엔티티의 PK를 참조하는 외래키 -->
    @Column(name = "외래키컬럼명")
    private 자료형 외래키필드;

    @Column(name = "일반컬럼명3")
    private 자료형 필드3;
}
  • 테스트 코드 작성
public class OneToManyAssociationTests {

    private static EntityManagerFactory entityManagerFactory;
    private static EntityManager entityManager;

    @BeforeAll
    public static void initFactory() {
        <!-- persistence.xml의 persistence-unit 이름 사용 -->
        entityManagerFactory = Persistence.createEntityManagerFactory("퍼시스턴스유닛이름");
    }

    @BeforeEach
    public void initManager() {
        entityManager = entityManagerFactory.createEntityManager();
    }

    @AfterAll
    public static void closeFactory() {
        entityManagerFactory.close();
    }

    @AfterEach
    public void closeManager() {
        entityManager.close();
    }
    
    <!-- 여러 테스트 -->
}
    @Test
    @DisplayName("일대다 연관관계 객체 그래프 탐색 조회 테스트")
    public void oneToManyLazyLoadTest() {

        <!-- given : 조회할 상위 엔티티의 기본키 값 설정 -->
        기본키자료형 상위기본키 = 특정값;

        <!-- when : 상위 엔티티 조회 
        		    (연관된 하위 엔티티는 아직 조회되지 않음 - 지연로딩) -->
        상위엔티티클래스 상위엔티티변수 = entityManager.find(상위엔티티클래스.class, 상위기본키);

        <!-- then : 상위 엔티티가 null이 아님을 검증 -->
        assertNotNull(상위엔티티변수);

        <!-- 연관된 하위 엔티티 출력 -->
        	 → 이 시점에 하위 테이블을 조회하는 SQL 쿼리가 실행됨
        System.out.println(상위엔티티변수);
    }

	@Test
    @DisplayName("일대다 연관관계 객체 삽입 테스트")
    public void oneToManyInsertTest() {

        <!-- given : 상위 엔티티 및 하위 엔티티 리스트 생성 -->
        상위엔티티클래스 상위엔티티변수 = new 상위엔티티클래스();
        상위엔티티변수.set기본키(상위기본키값);
        상위엔티티변수.set필드1("상위값1");
        <!-- 참조값이 없는 경우 -->
        상위엔티티변수.set필드2(null);

        List<하위엔티티클래스> 하위엔티티리스트변수 = new ArrayList<>();
        
        하위엔티티클래스 하위엔티티변수 = new 하위엔티티클래스();
        하위엔티티변수.set기본키(하위기본키값);
        하위엔티티변수.set필드1("하위값1");
        하위엔티티변수.set필드2(하위값2);
        하위엔티티변수.set필드3("하위값3");

        <!-- 외래키 필드 수동 설정 -->
        하위엔티티변수.set외래키필드(상위엔티티변수.get기본키());

		<!-- 하위엔티티변수들을 리스트안에 넣는다 -->
        하위엔티티리스트변수.add(하위엔티티변수);
        상위엔티티변수.set하위엔티티리스트변수(하위엔티티리스트변수);

        <!-- when : 트랜잭션 시작 
        	 → 상위 엔티티만 영속화 
             (CascadeType.PERSIST 적용 시 하위도 자동 영속화) -->
        EntityTransaction transaction변수 = entityManager.getTransaction();
        transaction변수.begin();

        entityManager.persist(상위엔티티변수);

        transaction변수.commit();

        <!-- then : 저장 확인 -->
        상위엔티티클래스 조회결과변수 = entityManager.find(상위엔티티클래스.class, 상위기본키값);
        System.out.println(조회결과변수);
    }

Bidirection 양방향

양방향 연관 관계는 반대 방향으로도 접근하여 객체 그래프 탐색을 할 일이 많은 경우에만 사용
DB -> 외래키 하나로 양방향 조회 가능(JOIN으로 자동처리 가능)
객체 -> 서로 다른 두 단방향 참조를 합쳐서 양방향
주인 : 외래키 가진 엔티티 클래스(@JoinColumn), DB에 외래키 반영됨
비주인 : mappedBy로 주인을 참조만 함 (읽기 전용)

  • 주인 엔티티 생성
@Entity(name = "주인엔티티논리이름")
@Table(name = "주인테이블이름")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
<!-- 순환참조 방지(양방향일 때 필수) -->
@ToString(exclude = "비주인을참조하는필드명")
public class 주인엔티티클래스 {

    @Id
    @Column(name = "기본키컬럼명")
    private 자료형 기본키필드명;

    @Column(name = "일반컬럼명1")
    private 자료형 필드명1;

    @Column(name = "일반컬럼명2")
    private 자료형 필드명2;

    <!-- 연관관계 주인 쪽
     	 외래키를 직접 소유하는 Many 쪽이 연관관계의 주인
     	 JoinColumn을 통해 외래키 지정 -->
    @ManyToOne
    @JoinColumn(name = "외래키컬럼명")  // 실제 DB 컬럼명
    private 비주인엔티티클래스 비주인필드명;

    @Column(name = "일반컬럼명3")
    private 자료형 일반컬럼명3;

}
  • 비주인 엔티티 생성
@Entity(name = "비주인엔티티논리이름")
@Table(name = "비주인테이블이름")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@ToString
public class 비주인엔티티클래스 {

    @Id
    @Column(name = "기본키컬럼명")
    private 기본키자료형 기본키필드명;

    @Column(name = "일반컬럼명1")
    private 자료형 필드명1;

    @Column(name = "일반컬럼명2")
    private 자료형 필드명2;

    <!-- mappedBy = "주인엔티티에서 비주인을 참조하고 있는 필드명"
       	 연관관계의 주인이 아님 (읽기 전용)
   		 실제 외래키는 주인 엔티티에 존재
     	 주인이 아닌쪽은 mappedBy로 소유자 지정만 한다 -->
    @OneToMany(mappedBy = "비주인을참조하는필드명")
    private List<주인엔티티클래스> 주인엔티티리스트;

}
  • 테스트 코드 작성
public class BiDirectionTests {

    private static EntityManagerFactory entityManagerFactory;
    private static EntityManager entityManager;

    @BeforeAll
    public static void initFactory() {
        <!-- persistence.xml의 persistence-unit 이름 사용 -->
        entityManagerFactory = Persistence.createEntityManagerFactory("퍼시스턴스유닛이름");
    }

    @BeforeEach
    public void initManager() {
        entityManager = entityManagerFactory.createEntityManager();
    }

    @AfterAll
    public static void closeFactory() {
        entityManagerFactory.close();
    }

    @AfterEach
    public void closeManager() {
        entityManager.close();
    }
    
    <!-- 여러 테스트 -->
}
    @Test
    @DisplayName("양방향 연관관계 매핑 조회 테스트")
    public void bidirectionMappingTest() {

        <!-- given : 조회할 주인/비주인 엔티티의 식별자 값 -->
        기본키자료형 주인엔티티기본키 = 특정값;
        기본키자료형 비주인엔티티기본키 = 특정값;

        <!-- when -->
        <!-- 주인 쪽(@ManyToOne)은 처음 조회 시 JOIN 쿼리로 연관 객체까지 함께 로딩됨 (즉시 로딩) -->
        주인엔티티클래스 주인엔티티변수 = entityManager.find(주인엔티티클래스.class, 주인엔티티기본키);

        <!-- 비주인 쪽(@OneToMany)은 처음에는 연관된 엔티티를 불러오지 않음 (지연 로딩) -->
        비주인엔티티클래스 비주인엔티티변수 = entityManager.find(비주인엔티티클래스.class, 비주인엔티티기본키);

        <!-- then -->
        <!-- 양방향 연관관계에서 toString()은 순환참조 StackOverflow 위험 → exclude 설정 권장 -->
        System.out.println(주인엔티티변수);
        System.out.println(비주인엔티티변수);

        <!-- 필요 시 연관 엔티티를 사용하는 순간, 지연로딩 쿼리 발생 -->
        비주인엔티티변수.get주인엔티티리스트().forEach(System.out::println);
    }

양방향 연관관계 toString() 오버라이딩 / lombok 사용 시 주의점 정리

  • 양방향 연관관계는 서로의 엔티티를 참조하고 있다.
  • 그렇게 되면 toString() 호출 시 내부적으로 무한 순환 발생 가능성이 있다.
  • Lombok의 @ToString 자동 생성 기능도 이런 순환 호출에 영향을 주기 때문에 주인 엔티티에 @ToString(exclude = "category")를 명시적으로 써서 순환 참조를 방지해야 한다.
  • 보통 주인 쪽(@ManyToOne)에 exclude를 적용한다.

@OneToMany 연관관계에서 지연로딩 쿼리 발생 이유

  • @OneToMany 연관관계는 기본적으로 지연 로딩(LAZY) 전략이 적용된다.
  • 처음 엔티티를 조회할 때는 연관된 컬렉션의 데이터를 가져오지 않는다.
  • 하지만 실제 컬렉션을 사용하는 순간, 연관된 데이터를 조회하기 위한 추가 쿼리가 실행된다
    @Test
    @DisplayName("주인 엔티티를 통한 연관 객체 삽입 테스트")
    public void 양방향연관관계_주인엔티티삽입_테스트() {

        <!-- given : 주인 엔티티(하위 엔티티) 생성 및 연관된 비주인 엔티티(상위 엔티티) 설정 -->
        주인엔티티클래스 주인엔티티변수 = new 주인엔티티클래스();
        주인엔티티변수.set기본키(주인기본키값);
        주인엔티티변수.set필드명1("특정값1");
        주인엔티티변수.set필드명2(특정값2);

        <!-- 연관관계 주입 (연관된 비주인 엔티티는 DB에서 조회해오는 방식) -->
        주인엔티티변수.set비주인엔티티변수(
            entityManager.find(비주인엔티티클래스.class, 비주인기본키값)
        );

        <!-- when : 트랜잭션 시작 및 주인 엔티티 영속화 -->
        EntityTransaction transaction변수 = entityManager.getTransaction();
        transaction변수.begin();
        entityManager.persist(주인엔티티변수);
        transaction변수.commit();

        <!-- then : 저장된 엔티티 조회 및 검증 -->
        주인엔티티클래스 조회결과변수 = entityManager.find(주인엔티티클래스.class, 주인엔티티.get기본키());
        assertEquals(주인엔티티변수.get기본키(), 조회결과변수.get기본키());
        System.out.println(조회결과변수);

    }
    @Test
    @DisplayName("양방향 연관관계 비주인 엔티티 삽입 테스트")
    public void insertWithNonOwningEntity() {

        <!-- given : 비주인 엔티티 인스턴스 생성 및 설정 -->
        비주인엔티티클래스 비주인엔티티변수 = new 비주인엔티티클래스();
        비주인엔티티변수.set기본키(기본키값);
        비주인엔티티변수.set필드1("특정값1");
        비주인엔티티변수.set필드2(null); // 상위 참조값이 없을 경우

        <!-- when : 트랜잭션 시작 → 비주인 엔티티만 영속화 -->
        EntityTransaction transaction변수 = entityManager.getTransaction();
        transaction변수.begin();
        entityManager.persist(비주인엔티티변수);
        transaction변수.commit();

        <!-- then : 저장된 비주인 엔티티를 다시 조회하여 검증 -->
        비주인엔티티클래스 조회결과변수 = entityManager.find(비주인엔티티클래스.class, 기본키값);
        assertEquals(비주인엔티티변수.get기본키(), 조회결과변수.get기본키());
        System.out.println(조회결과변수);
    }

참고

기본 원칙 OneToMany vs ManyToOne

  • 외래키는 항상 다수(Many) 쪽 엔티티가 소유, 이 쪽에 @JoinColumn이 선언
    -> @ManyToOne 쪽에서 외래키 관리
  • @OneToMany는 보통 읽기전용(조회 목적)으로 사용, @JoinColumn 없이 mappedBy를 사용
  • 만약 외래키를 @OneToMany 쪽에 정의하면, 하나의 컬럼에 여러 값을 넣어야 한다.
    -> DB 설계상 불가능하거나 비효율적이며 에러 발생 가능성 있음
profile
잔디 속 새싹 하나

0개의 댓글