Stay Hungry Stay Foolish

TIL

[TIL] 2026년 01월 05일

dev스카이 2026. 1. 5. 19:09

JPA 매핑 정보는 XML이나 어노테이션 중에 선택해서 기술하면 된다. 그럼 XML은 무엇일까?

 

XML

XML(eXtensible Markup Language)은 데이터를 구조화하고 저장하며 전송하기 위한 확장 가능한 마크업 언어로, HTML과 비슷하지만 미리 정의된 태그 없이 사용자가 직접 태그를 정의하여 데이터의 의미와 구조를 설명한다.

 

사용 방식

JPA에서 XML로 매핑을 쓰는 방식은 표준적으로 orm.xml(JPA ORM 매핑 파일) 을 두고, 이를 스프링 부트/JPA 설정에 “매핑 리소스”로 등록하는 형태이다. (즉, 어노테이션 대신 또는 어노테이션을 “덮어쓰는” 용도로 XML을 쓴다.)

 

1) orm.xml 파일을 어디에 두나요?

가장 흔한 기본 위치는 아래입니다.

  • src/main/resources/META-INF/orm.xml

스프링(JPA 설정)은 별도 지정이 없으면 클래스패스에서 기본 META-INF/orm.xml을 찾아 매핑 리소스로 등록하려고 시도합니다. 또한 orm.xml의 스키마(XSD)는 Jakarta Persistence ORM 스키마를 따릅니다. 

 

2) orm.xml에는 무엇을 적나요?

orm.xml에는 “어떤 클래스가 어떤 테이블/컬럼과 매핑되는지”, “ID는 무엇인지”, “연관관계/속성 매핑” 같은 엔티티 매핑 정보를 XML로 기술합니다(어노테이션으로 하던 내용을 XML 태그로 옮기는 형태). 

 

또한 상황에 따라

  • 어노테이션 + XML 혼용(일부만 XML로 오버라이드) 또는
  • XML만 사용(어노테이션 무시)
    가 가능합니다. 이때 metadata-complete를 사용하면 “이 XML이 전부다(어노테이션은 보지 마라)”라는 의미로 동작합니다. 

 

3) 스프링 부트에서 orm.xml을 “읽게” 만드는 방법

스프링 부트에서는 보통 아래 둘 중 하나로 처리합니다.

 

방법 A) application.yml(또는 properties)에 매핑 리소스 등록

스프링 부트는 spring.jpa.mapping-resources로 persistence.xml의 <mapping-file>과 동일한 역할을 하도록 지원합니다.

spring:
  jpa:
    mapping-resources:
      - META-INF/orm.xml

 

방법 B) 기본 위치(META-INF/orm.xml)에 두고 자동 탐지에 맡기기

명시 설정을 안 해도, 스프링의 JPA 설정은 기본 META-INF/orm.xml을 찾아 등록하려는 동작이 있습니다. 다만 프로젝트 구성에 따라 중복 로딩/미로딩 이슈가 날 수 있어서, 운영 관점에서는 위의 방법 A처럼 명시하는 걸 더 권합니다.

 

4) (참고) persistence.xml을 쓰는 전통적인 JPA 방식이라면

persistence.xml을 두는 구성(순수 JPA 부트스트랩)이라면, 그 안에 <mapping-file>로 XML 매핑 파일 경로를 등록할 수 있습니다.

 

5) 어노테이션이랑 같이 쓰면 우선순위는?

일반적으로 어노테이션과 XML을 같이 쓰면 병합되고, 충돌 시에는 XML이 우선(오버라이드) 으로 동작하는 구성이 흔합니다. 그리고 metadata-complete를 켜면 어노테이션을 무시하는 쪽으로 갑니다.


도메인 모델

도메인 모델은 서비스가 다루는 현실 세계의 개념(회원, 주문, 결제 같은 것들)을 소프트웨어 안에서 객체와 관계로 표현한 모델입니다. 이때 중요한 점은 단순히 데이터 구조만 만드는 게 아니라, 그 데이터에 대한 비즈니스 규칙과 동작(행동)까지 함께 담는 “업무 중심 객체 모델”이라는 겁니다.

 

도메인 모델 예시

  • 회원과 주문의 관계: 회원은 여러 번 주문할 수 있다. (일대다)
  • 주문과 상품의 관계: 주문할 때 여러 상품을 선택할 수 있다. 반 대로 같은 상품도 여러 번 주문될 수 있다. 주문상품 이라는 모델을 만들어서 다대다 관계를 일다대, 다대일 관계로 풀어냄.


"객체지향스럽지 않다"는 무슨 의미일까?

객체 사이의 관계를 객체(참조)로 표현하지 않고 숫자 ID 같은 값으로만 다뤄서, 모델이 '객체 협력'이 아니라 '값 조작' 중심으로 흘러간다는 뜻입니다. 

 

객체지향스럽지 않은 코드 예시

Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");
member.setTeamId(team.getId());
em.persist(member);
  • 관계를 '객체 참조'가 아니라 '외래키 값'으로 표현합니다.
    • 자바/객체 관점에서 "Member는 Team에 속한다."는 관계는 보통 Member → Team 참조(필드)로 표현하는 게 자연스럽습니다. JPA도 이런 "단일 값 연관(association)"을 @ManyToOne으로 표현하도록 정의합니다. 즉, JPA는 원래 "코드에서는 객체를 참조하고, DB에는 그 결과로 FK가 저장되게" 만드는 ORM입니다.
  • 도메인 모델이 DB 구조를 그대로 끌어안게 됩니다. 
    • teamId는 사실상 DB의 FK 표현인데, 이걸 엔티티에 직접 들고 있으면 모델이 "객체 관계"보다 "테이블 조인/키 값" 중심이 됩니다. 그러면 결국 서비스 코드에서 매번 teamId로 다시 조회해서 붙이는 식이 되고, 객체가 객체답게 협력하기보다 절찾거으로 흘러가기 쉽습니다. (JPA가 제공하려는 이점이 줄어듭니다.)
  • 불일치/무결성 문제를 만들기 쉽습니다.
    • teamId는 숫자일 뿐이라, "존재하지 않는 팀 ID"를 넣어도 객체 자체는 막지 못합니다. 반면 "Team 객체를 참조"하면 관계가 더 명확해지고, 변경 지점도 줄어 모델의 일관성을 유지하기가 더 쉽습니다. (특히 양방향이면 한쪽만 바꿔서 관계가 깨지는 문제를 줄이려면, 관계 변경을 한 메서드로 캡슐화하는 식이 자연스럽습니다.)

그래서 JPA/ORM 관점에서 “객체지향스럽게”는 보통 Member가 teamId를 갖는 대신 Team을 참조하게 하고, JPA가 그 참조를 외래키로 매핑하게 두는 걸 말합니다. Hibernate 문서에서도 FK 관계는 @ManyToOne 같은 표준 연관 매핑을 우선하라고 안내합니다.

 

참고로, DTO(요청/응답)나 API 경계에서는 ID로 주고받는 게 자연스럽습니다. 다만 “엔티티/도메인 모델 내부”에서는 관계를 객체 참조로 모델링하는 쪽이 보통 더 객체지향적이라고 평가됩니다.


코드 분석(100L)

Team newTeam = em.find(Team.class, 100L);
  • 100L은 조회하려는 Team 엔티티의 "기본 키(PK)"입니다. 즉, @Id로 지정된 값이 100인 팀을 찾아오겠다는 뜻입니다.
  • 그리고 뒤의 L은 자바에서 "long 타입 리터럴" 표시라서, 100L은 long(보통 Long으로 사용) 값 100을 의미합니다.
  • 참고로 find()는 기본 키로 엔티티를 찾는 메서드이고, 해당 엔티티가 없으면 보통 null을 반환합니다.