본문 바로가기

개발기술/ORM

embedded key 와 joincolumn에 대하

정확히 보셨어요! 지금 이 AuthMenu 엔티티에서 auth나 menu 필드에 실제 값이 들어가지 않는다면, 그 이유는 대부분 아래 두 가지 중 하나 때문입니다:


✅ 원인 1. insertable = false, updatable = false 때문

java
복사편집
@ManyToOne(fetch = LAZY) @JoinColumn(name = "auth_id", insertable = false, updatable = false) private Auth auth;

이 설정은 JPA에게 다음과 같이 지시합니다:

🔒 이 auth_id는 DB에 insert/update할 때 사용하지 마라.
👉 즉, 읽기 전용 필드로만 사용하겠다는 뜻입니다.

결과적으로:

  • auth나 menu 필드는 persist() 시 아무 역할도 하지 않습니다.
  • AuthMenuKey 안의 authId, menuId만 DB에 insert됩니다.
  • 그리고 추후에 조회할 때도 fetch = LAZY이므로, auth나 menu는 명시적으로 접근하기 전까진 조회되지 않습니다.

✅ 원인 2. 연관된 auth, menu를 코드에서 설정하지 않았을 수도 있음

예를 들어, 아래처럼 생성했다면:

java
복사편집
AuthMenuKey key = new AuthMenuKey(1, 2); // authId = 1, menuId = 2 AuthMenu authMenu = new AuthMenu(key, YesNoType.Y, YesNoType.Y, YesNoType.N, YesNoType.N);

→ 이 경우 authMenu.getAuth()는 null입니다.
왜냐하면 auth 필드에 아무 객체도 직접 set하지 않았기 때문입니다.


🔁 해결 방법

① 조회 시 자동으로 연관 객체가 채워지길 원한다면:

java
복사편집
@JoinColumn(name = "auth_id") private Auth auth; // remove insertable=false, updatable=false

그리고 @MapsId("authId")을 함께 써서 id.authId와 연동시킬 수도 있습니다 (복합키 연동 구조에서는 권장).


② 그대로 두고 읽기 전용으로만 쓸 거라면

지금처럼:

java
복사편집
@JoinColumn(name = "auth_id", insertable = false, updatable = false)

을 유지하면 되고, auth나 menu에 직접 접근해서 lazy load 되도록:

java
복사편집
authMenu.getAuth().getName();

이렇게 접근을 시도해야 연관 객체가 lazy하게 로딩됩니다.


✅ 정리

현상원인
auth, menu에 값이 안 들어옴 insertable = false, updatable = false로 설정되어 있기 때문에 JPA가 자동 주입하지 않음
연관 객체 접근 시 null 또는 lazy load 안 됨 연관 필드를 명시적으로 set하지 않았고, 접근도 하지 않았기 때문
해결 방법 필요 시 @MapsId, insertable = true, updatable = true로 바꾸고 관계 설정 명시
 

필요하시면 이 구조를 MapsId 방식으로 리팩토링한 코드도 제공해드릴게요.

 

나의 말:

값이 실제로들어가는지 안들어가는지는 모르겠는데 구조적으로는 문제없어보이는거지? auth랑 menu 필드는 persist에서 아무역할 안하는게 당연한거아냐 ? 자식테이블에서 부모를 어떻게 할수없잖아
 

ChatGPT의 말:

맞습니다. 아주 정확한 이해입니다. 요약하면:

지금 구조는 설계상 전혀 문제 없습니다.
🔒 auth와 menu는 읽기 전용 연관관계로 설정된 것이고, persist() 시에 값을 넣지 않는 것도 의도된 동작입니다.


🔍 왜 이것이 맞는 구조인가?

1. @EmbeddedId로 복합키를 사용하고 있고,

java
복사편집
@EmbeddedId private AuthMenuKey id;

여기서 authId, menuId 값은 이미 명확하게 설정되고 있습니다.


2. 그에 따라 연관관계 필드들은 이렇게 선언됨:

java
복사편집
@ManyToOne(fetch = LAZY) @JoinColumn(name = "auth_id", insertable = false, updatable = false) private Auth auth; @ManyToOne(fetch = LAZY) @JoinColumn(name = "menu_id", insertable = false, updatable = false) private Menu menu;

이건 JPA에게 다음과 같이 말하는 겁니다:

📢 “auth_id와 menu_id는 이미 id라는 복합키 객체 안에서 설정됐으니,
auth와 menu는 읽기 전용 참조로만 유지하겠다.”


3. 자식 테이블(권한별 메뉴)이 부모(Auth, Menu)를 직접 insert/update하는 것은 위험

  • 자식이 부모 엔티티를 insertable=true로 변경해서 직접 조작하는 건 위험합니다.
  • 그것은 설계상 Cascade를 설정한 경우에만 예외적으로 허용됩니다.
  • 지금처럼 중간 테이블(M:N 매핑)에서는 보통 그렇게 하지 않고,
    👉 부모는 이미 존재한다는 전제로 ID만 연결하는 게 자연스럽습니다.

✅ 결론: 구조적으로 완전히 정상적입니다

항목설명
auth, menu 필드 persist 시 동작 ❌ insert/update 대상이 아님 → 정상
authId, menuId는 어디서 insert됨? ✅ @EmbeddedId의 필드로서 DB에 저장됨
연관관계 필요한 이유? ✅ 조회용 (Lazy fetch, join 등), 도메인 표현력 향상
부모를 자식이 insert하는가? ❌ No. 자식 테이블은 부모를 참조만 해야 하며, Cascade가 없는 한 직접 조작하지 않음
 

필요하면, 이 구조가 DB DDL에서 어떻게 반영되는지 (foreign key 제약조건 포함)도 설명드릴 수 있어요.

 

나의 말:

아니지금 논점이 흐려지는데 @ maps Id가 왜 필요하고 여기에는 왜안필요하냑는거지
 

ChatGPT의 말:

좋습니다, 핵심 논점에 정확히 집중해 보겠습니다:


🔍 핵심 질문

✅ 지금 구조(@EmbeddedId + @JoinColumn(insertable = false, updatable = false))에는 @MapsId가 왜 필요 없는가?
✅ 그런데 어떤 경우에는 @MapsId가 꼭 필요한가?


1️⃣ @MapsId가 필요한 경우

✅ 사용 예

java
복사편집
@EmbeddedId private StatsBySecondsId id; @MapsId("areaId") @ManyToOne private Area area;

🔹 이 구조의 의미:

  • 복합 키 안에 이미 areaId라는 FK 필드가 있고,
  • area라는 연관 필드를 통해 해당 FK를 연결하고 싶을 때,
  • @MapsId("areaId")를 붙이면 JPA는 두 값을 하나로 매핑합니다.

즉,

  • id.areaId = area.getId() 자동 매핑됨
  • persist() 시 area만 세팅해도 JPA가 알아서 areaId 값을 id에 세팅함

👉 이건 연관관계를 양방향적으로 관리하고 싶을 때 필수적입니다.


2️⃣ 지금 구조에 @MapsId가 필요 없는 이유

java
복사편집
@EmbeddedId private AuthMenuKey id; @ManyToOne(fetch = LAZY) @JoinColumn(name = "auth_id", insertable = false, updatable = false) private Auth auth; @ManyToOne(fetch = LAZY) @JoinColumn(name = "menu_id", insertable = false, updatable = false) private Menu menu;

🔹 이 구조의 의미:

  • authId, menuId는 복합 키로 이미 명시적으로 설정됨
  • auth, menu는 **조회(read-only)**용으로만 사용함

→ @MapsId가 필요하지 않음, 왜냐하면:

  • id.authId, id.menuId는 직접 세팅됨
  • auth, menu는 insert/update 대상이 아님
  • 따라서 JPA는 연관관계를 동기화할 필요가 없음

🎯 핵심 비교 정리

상황@MapsId 필요 여부설명
연관 필드를 통해 ID를 채우고 싶을 때 ✅ 필요 areaId를 자동으로 id에 매핑
복합키에 직접 ID를 세팅하고 연관 필드는 읽기 전용일 때 ❌ 불필요 지금 구조 (auth/menu는 읽기 전용)
 

✅ 결론

@MapsId는 **"연관 필드를 통해 복합키 ID를 채우고 싶은 경우"**에만 필요합니다.
지금처럼 복합키를 직접 세팅하고, 연관 필드를 단지 조회용으로 쓸 경우에는 필요 없습니다.


필요하시면 @MapsId 사용 구조도 AuthMenu에 맞게 재작성해드릴 수 있어요.

 

나의 말:

@MapsId가 필요하지 않음, 왜냐하면: id.authId, id.menuId는 직접 세팅됨 auth, menu는 insert/update 대상이 아님 따라서 JPA는 연관관계를 동기화할 필요가 없음 이게무슨말인데? authmenu에서는 auth나 menu를 조회할수없다는건가 ?
 

ChatGPT의 말:

좋습니다, 아주 중요한 질문이에요. 핵심은:

🔍 **"연관관계를 동기화할 필요가 없다"**는 말은
"JPA가 auth 객체를 보고 id.authId를 자동으로 채워줄 필요가 없다"는 뜻입니다.
절대 "auth, menu를 조회할 수 없다"는 뜻이 아닙니다.


✅ 지금 구조에서 가능한 일과 불가능한 일

java
복사편집
@EmbeddedId private AuthMenuKey id; @ManyToOne(fetch = LAZY) @JoinColumn(name = "auth_id", insertable = false, updatable = false) private Auth auth;

가능한 것 ✅

질문가능 여부이유
authMenu.getAuth()로 Auth를 조회할 수 있나요? ✅ 가능 연관관계가 정의되어 있고, auth_id가 DB에 있기 때문
authMenu.getAuth().getName() 같은 사용 가능? ✅ 가능 lazy이긴 하지만 접근하면 JPA가 자동으로 join해서 조회함
JPQL에서 JOIN FETCH authMenu.auth 가능? ✅ 가능 표준적인 연관관계로 작동함
 

불가능한 것 ❌ (또는 자동으로 안 되는 것)

질문가능 여부이유
authMenu.setAuth(auth)만 하고 persist 하면 id.authId가 자동 세팅되나요? ❌ 불가능 @MapsId가 없기 때문에 JPA는 auth 필드로부터 id.authId를 유추하지 않음
연관관계를 통해 복합키의 값을 자동 채워주나요? ❌ 안 됨 @MapsId가 있어야만 JPA가 auth.getId()를 id.authId로 세팅함
 

📌 정리: @MapsId의 실질적 의미

항목설명
authMenu.setAuth(auth) 한 후 @MapsId가 없으면 id.authId는 비어 있음 (null)
authMenu.setAuth(auth) + @MapsId("authId") id.authId 자동으로 auth.getId()로 채워짐
연관관계로부터 ID를 자동 유도할 수 있느냐 👉 그게 @MapsId의 핵심 기능
조회용으로만 쓰는 auth 필드엔 @MapsId 필요 없음 맞습니다, 조회는 잘 됩니다
 

✅ 결론

지금 구조는 auth, menu 연관관계를 조회용(Read-Only) 으로 사용하는 것이며,
@MapsId가 없다고 해서 조회가 안 되는 건 절대 아닙니다.
다만, authMenu.setAuth(auth) 만으로는 id.authId가 자동으로 세팅되지 않습니다.


원하신다면 같은 구조를 @MapsId 기반으로 리팩토링한 코드도 비교해서 보여드릴게요.

 

나의 말:

그러니간 embedded key 외에도 auth라는 객체의 필드를 만든다는건가 ?
 

ChatGPT의 말:

네, 맞습니다. 정확하게 말씀드리자면:

Embedded key (AuthMenuKey)를 사용하더라도,
연관관계를 표현하려면 auth 같은 객체 필드가 별도로 필요합니다.

즉,

java
복사편집
@EmbeddedId private AuthMenuKey id; @ManyToOne(fetch = LAZY) @JoinColumn(name = "auth_id", insertable = false, updatable = false) private Auth auth;

이런 식으로 id.authId는 DB의 외래 키 값이고,
auth는 실제로 JPA가 로딩할 수 있게 하는 객체 연관관계입니다.


🔍 왜 둘 다 있어야 할까?

1. @EmbeddedId는 단지 값(기본 키 값)일 뿐

java
복사편집
@Embeddable public class AuthMenuKey { private Integer authId; private Integer menuId; }
  • 이건 단순히 auth_id, menu_id라는 숫자 값을 묶어 표현한 겁니다.
  • JPA 입장에서는 Auth라는 객체가 어떤 건지 모릅니다.

2. JPA는 객체 모델을 따르므로, 연관관계 필드(auth)가 따로 필요

java
복사편집
@ManyToOne(fetch = LAZY) @JoinColumn(name = "auth_id", insertable = false, updatable = false) private Auth auth;
  • 이걸 통해 authMenu.getAuth()로 실제 Auth 객체를 불러올 수 있음
  • @JoinColumn으로 auth_id와 연결됨
  • insertable = false는 "내가 직접 관리하지는 않겠다"는 뜻

🧠 쉽게 이해하는 비유

구성 요소비유
AuthMenuKey.authId 주민등록번호 (숫자 값)
Auth auth 주민등록증 실물 (객체 참조)
 

주민등록번호를 가지고 있더라도, 그걸로 사람에 대해 뭘 알고 싶으면 사람 객체가 따로 있어야 하죠.
즉, authId는 키, auth는 실제 참조입니다.


✅ 결론

질문답변
Embedded key에 authId가 있는데, auth 객체 필드를 또 만들어야 하나요? ✅ 네, 그래야 JPA가 Auth 객체를 조회할 수 있습니다.
단순히 ID만 있으면 안 되나요? 🔶 가능은 하지만, 연관관계 기능 (fetch, join, cascade 등)을 못 씁니다.
auth 필드를 만들어도 insertable=false면 persist 시 값은 안 들어가죠? ✅ 맞습니다. 단지 조회용 참조일 뿐입니다.
 

필요하다면 이걸 @MapsId로 연계해서 auth만 설정해도 id.authId가 자동 채워지도록 만드는 방법도 설명드릴게요.

 

나의 말:

근데 setter로 auth 필드를 통해서 cud는 불가하도록 만든다는거네. mapsid는 그걸 auth 필드를 통해서 embdedded key 객체의 값도 변화하도록 하는거고
 

ChatGPT의 말:

네! 지금 말씀하신 이해가 정확합니다.
핵심 포인트를 정리해 보면 다음과 같습니다:


✅ 지금 구조 (@JoinColumn(... insertable = false, updatable = false))

java
복사편집
@ManyToOne(fetch = LAZY) @JoinColumn(name = "auth_id", insertable = false, updatable = false) private Auth auth;
  • 이 auth 필드는 조회 전용(Read-only) 입니다.
  • 즉, authMenu.setAuth(auth) 해도 DB에는 아무 영향도 주지 않습니다.
  • JPA는 persist/update 시 이 auth 필드를 무시합니다.
  • 결국 실제 저장되는 값은 @EmbeddedId.id.authId에 직접 넣은 값입니다.

✅ @MapsId("authId")를 쓰는 구조

java
복사편집
@EmbeddedId private AuthMenuKey id; @MapsId("authId") @ManyToOne @JoinColumn(name = "auth_id") private Auth auth;
  • 이 구조에서는 auth 필드를 통해 id.authId가 자동으로 채워집니다.
  • 즉, authMenu.setAuth(auth)만 해도 JPA가 내부적으로 id.setAuthId(auth.getId())를 해주는 것과 같습니다.
  • 또한 persist할 때 auth_id도 DB에 들어가게 됩니다.

🔁 비교 정리

항목조회 가능 여부CUD 가능 여부ID와 자동 동기화설명
auth + insertable=false, updatable=false ✅ 가능 ❌ 불가능 ❌ 직접 id.authId 지정해야 조회용 전용
auth + @MapsId("authId") ✅ 가능 ✅ 가능 ✅ auth.getId()로 자동 설정 실전에서 연관관계로 ID 관리
 

🎯 결론

✔️ 지금 구조는 안전하게 FK만 ID로 쓰고 연관 객체는 조회용으로만 쓰는 방식이고,
✔️ @MapsId를 쓰면 연관관계를 통해 ID를 간접적으로 조작할 수 있도록 허용하는 방식입니다.


필요하시면 둘을 예제로 비교하는 insert 코드도 만들어드릴게요.

 

나의 말:

package dains.platform.anantipoc.entity; import dains.platform.anantipoc.constant.AlarmStatus; import jakarta.persistence.*; import lombok.*; @Entity @Builder @RequiredArgsConstructor @AllArgsConstructor @Setter @Getter public class AlarmLevel { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) long id; double peopleCount; @Enumerated(EnumType.ORDINAL) AlarmStatus alarmStatus; @OneToOne Area area; } 여기서 area 는 왜 joincolumn이런 annotation이 왜 필요가없을까
 

ChatGPT의 말:

좋은 질문이에요. 결론부터 말하면:

✅ @JoinColumn은 생략 가능하며, 생략해도 JPA가 기본 이름으로 FK 컬럼을 생성해줍니다.
🎯 하지만 명시하지 않으면 의도를 명확하게 드러내지 못하고,
컬럼명이 예상과 다를 수 있어 유지보수에 불리할 수 있습니다.


🔍 기본 규칙: @JoinColumn 생략 시 JPA의 기본 동작

java
복사편집
@OneToOne Area area;

이렇게만 적으면 JPA는 다음과 같이 기본 설정으로 처리합니다:

  • FK 컬럼 이름: area_id (관례: 필드명 + _id)
  • 참조 대상: Area 엔티티의 @Id 필드

즉, 다음과 동일하게 해석됩니다:

java
복사편집
@OneToOne @JoinColumn(name = "area_id") // 자동 추론 private Area area;

🔍 그런데 왜 @JoinColumn을 명시적으로 쓰는 게 좋을까?

✅ 이유 1: 명확한 의도 표현

java
복사편집
@JoinColumn(name = "area_id", nullable = false)
  • 명확하게 어떤 컬럼에 매핑될지 보여주므로 다른 개발자가 이해하기 쉬움
  • FK 제약조건(nullable, unique 등)도 설정 가능

✅ 이유 2: 컬럼명이 예상과 다를 수 있음

예를 들어, 이런 경우:

java
복사편집
@OneToOne @JoinColumn(name = "area_fk") // 명시한 경우

을 하지 않으면, JPA가 자동으로 생성하는 area_id 컬럼을 쓰게 되며,
DB 컬럼 이름과 실제 설계 의도가 어긋날 수 있음


🔍 참고: OneToOne은 기본적으로 unique 제약이 걸림

@OneToOne은 기본적으로 참조하는 FK에 UNIQUE 제약조건이 생깁니다 (일대일이기 때문에).
이건 @JoinColumn이 있든 없든 적용됩니다.


✅ 결론

질문답변
@OneToOne에서 @JoinColumn이 꼭 필요한가요? ❌ 생략 가능하지만,
왜 명시적으로 쓰는 게 좋나요? ✔️ 컬럼명을 지정할 수 있고, ✔️ 의도를 명확히 표현할 수 있으며, ✔️ DDL 자동 생성 시 예측 가능성이 높아집니다.
 

필요하시면 @JoinColumn을 명시했을 때와 생략했을 때 schema.sql 비교도 보여드릴 수 있어요.

 

 

아, 정확히 질문 주신 부분은 이거죠:

Entity에 @ManyToOne Area area 필드를 제거하고, @EmbeddedId 안에 areaId만 있으면, FK 관계가 실제로 유지되느냐?


✅ 간단한 정답

❌ 아니요. @ManyToOne area를 제거하면 JPA는 FK 연관관계를 관리하지 않습니다.


🔍 상세 설명

1. @EmbeddedId에 있는 areaId는 그냥 숫자 필드일 뿐입니다.

java
복사편집
@Embeddable public class StatsBySecondsId { private String yyyymmdd; private String hhmmss; private Long areaId; // 그냥 값(Long)이지, Area와의 관계가 아님 }
  • 이건 **단순 숫자(Long)**입니다.
  • JPA는 이걸 외래키로 인식하지 않습니다.

2. 외래 키(FK) 관계를 인식시키려면?

JPA에게 “이건 Area 테이블의 외래키야”라고 객체 연관관계로 알려줘야 합니다.

즉, 아래와 같은 코드가 반드시 필요합니다:

java
복사편집
@MapsId("areaId") @ManyToOne private Area area;

이게 있어야만:

  • StatsBySeconds.area_id는 Area.id를 참조하는 FK가 되고,
  • EntityGraph, fetch join, cascade, lazy loading 등 객체 연관관계 기능을 사용할 수 있게 됩니다.