본문 바로가기

Database & ORM/MongoDB

MongoDB에서 JPA 상속 관계 설정 - @Inheritance, @DiscriminatorColumn, @DiscriminatorValue

결론부터 말하자면,

 

- MongoDB에서 JPA의 상속관계 설정인 @Inheritance, @DiscriminatorColumn, @DiscriminatorValue는 적용되지 않는다.

 

- JAVA로 MongoDB에서 데이터 조회할 때, 자식 클래스 중 어떤 클래스와 매핑할 지 결정하는 것은  Document 내 저장되어 있는 _class이다. _class는 JAVA에서 데이터 저장 시, Document에 자동으로 같이 저장된다.

 

- _class로 구분하기 때문에, 자식 클래스의 클래스명은 변경하면 안된다. 변경 시, 기존 저장된 데이터를 매핑해서 가지고오지 못한다.

 

- @TypeAlias를 이용해 _class에 들어갈 값을 정해줄 수 있다.

 

JPA 상속관계 설정

JPA에서 상속 관계는 부모 엔터티에 @Inheritance로 조인 전략을 설정해주고, 어떤 자식 클래스와 매핑할 것인지 구분하기 위한 구분자 컬럼으로 @DiscriminatorColumn을 설정해준다. 그리고 자식 클래스에 @DiscriminatorValue를 넣어 DiscriminatorColumn에 들어갈 값을 지정해 준다.

 

자세한 내용은 아래글 참조.

https://developer-minji.tistory.com/85?category=1038052 

 

5. JPA 상속 관계 매핑, Enum 사용

도메인 모델과 테이블 Entity에선 Item을 Album, Book, Movie가 상속받는다. 상속 전략으로 하나의 테이블 전략을 사용하여 Album, Book, Movie 정보 모두 Item 테이블에 저장된다. DTYPE으로 Album, Book, Movi..

developer-minji.tistory.com

이게 왜 되지?

클래스 구조

기능을 구현할 때 아래와 같은 클래스 구조로 데이터를 저장할 일이 있었다.

부모 클래스는 Item이고, @DiscriminatorColumn을 type으로 줬다.

자식 클래스는 Novel과 Movie가 있는데, @DiscriminatorValue를 각각 Book과 Moive로 줬다.

 

 

 

 

Essay라는 자식 클래스를 추가할 일이 생겼다. 근데, type은 Book으로 분류하고 싶다. 

그래서 아래와 같이 Essay의 @DiscriminatorValue를 Novel과 같이 Book으로 지정했다.

 

 

 

 

@DiscriminatorValue는 자식 클래스를 구분하는 값이므로 값이 중복되면 안된다고 생각했는데, 데이터가 클래스 구분대로 저장되고 조회되었다. 

 

왜 @DiscriminatorValue가 중복이 되었는데도 잘 되지? 라는 의문증이 생겼다.

소스

Controller

@RestController
@RequiredArgsConstructor
@RequestMapping("/inheritance")
public class InheritanceTestController {
	
	private final InheritanceTestService inheritanceTestService;
	
	@PostMapping
	public ResponseEntity<Void> insertData(){
		inheritanceTestService.insertData();
		return ResponseEntity.ok().build();
	}
	
	@GetMapping
	public ResponseEntity<List<Item>> getData(){
		return ResponseEntity.ok().body(inheritanceTestService.getData());
	}
}

 

Service

@Service
@RequiredArgsConstructor
public class InheritanceTestService {

	private final ItemRepository itemRepository;
	
	public void insertData() {
		Novel novel = new Novel("소설 제목", "저자", "로맨스");
		Essay essay = new Essay("에세이 제목", "저자", "일상");
		Movie movie = new Movie("영화", "감독", "배우");
		
		itemRepository.insert(novel);
		itemRepository.insert(essay);
		itemRepository.insert(movie);
		
	}
	
	public List<Item> getData() {
		return itemRepository.findAll();
	}
	
}

 

Repository

Item 클래스로 조회

@Repository
public interface ItemRepository extends MongoRepository<Item, String>{

}

 

Document 클래스 모델

부모 클래스

@Getter
@Setter
@Document(collection = "item")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE) //상속 설정
@DiscriminatorColumn(name = "type")                   //type으로 자식 클래스 구분
public class Item {

    @Id
    private String id;
    
    private String name;
    
    public Item(String name) {
    	this.name = name;
    }
    
}

 

자식 클래스

@Getter
@Setter
@Document(collection = "item")
@DiscriminatorValue("Book")
public class Novel extends Item{

	private String author;
	
	private String novelType;
	
	public Novel(String name, String author, String novelType) {
		super(name);
		this.author = author;
		this.novelType = novelType;
	}
}
@Getter
@Setter
@Document(collection = "item")
@DiscriminatorValue("Book")
public class Essay extends Item{

	private String author;
	
	private String essayType;

	public Essay(String name, String author, String essayType) {
		super(name);
		this.author = author;
		this.essayType = essayType;
	}
	
}
@Getter
@Setter
@Document(collection = "item")
@DiscriminatorValue("Movie")
public class Movie extends Item {
	private String director;
	private String actor;
	
	public Movie(String name, String director, String actor) {
		super(name);
		this.director = director;
		this.actor = actor;
	}
}

 

GET 요청 결과

[GET] http://localhost:8080/inheritance

== Response Body
[
    {
        "id": "6281d8913a68c819b6634242",
        "name": "소설 제목",
        "author": "저자",
        "novelType": "로맨스"
    },
    {
        "id": "6281d8923a68c819b6634243",
        "name": "에세이 제목",
        "author": "저자",
        "essayType": "일상"
    },
    {
        "id": "6281d8923a68c819b6634244",
        "name": "영화",
        "director": "감독",
        "actor": "배우"
    }
]

 

실험

왜 @DiscriminatorValue가 중복되어도 각 자식 클래스에 맞게 매핑되어 결과가 나올까?

MongoDB의 데이터를 살펴봤는데, @DiscriminatorColumn으로 지정한 type 값이 저장되지 않고있었다.

 

type이 저장되지 않기 때문에, @DiscriminatorColumn을 가지고 자식 클래스를 구분하지 않는다는 생각이 들었다.

Item클래스와 자식 클래스 모두에서, @Inheritance와 @DiscriminatorColumn, @DiscriminatorValue를 제거했다.

그리고 데이터를 다시 저장하고 조회해보니, 제거 전이랑 동일한 결과가 나왔다.

 

@Inheritance와 @DiscriminatorColumn으로 구분하지 않는 것이 확실했다.

그럼 MongoDB는 자식 클래스를 무엇으로 구분하는 걸까?

MonboDB에 저장되는 _class라는 생각이 들었다.

 

그래서 MongoDB에서 _class를 자식 클래스와 다르게 변경해보았다.

클래스가 Novel인 것을 ChangeClass로 변경하였다.

 

다시 Get으로 결과를 조회해보니 Novel의 author와 novelType이 조회되지 않고, Item 클래스에 있는 정보만 매핑되어 나왔다. 이로써 _class가 어떤 자식 클래스와 매핑할지 결정해준다는 것을 알 수 있었다.

[GET] http://localhost:8080/inheritance

== Response Body
[
    {
        "id": "6281df4d568c887607932135",
        "name": "소설 제목"  // author와 novelType이 나오지 않음.
    },
    {
        "id": "6281df4d568c887607932136",
        "name": "에세이 제목",
        "author": "저자",
        "essayType": "일상"
    },
    {
        "id": "6281df4d568c887607932137",
        "name": "영화",
        "director": "감독",
        "actor": "배우"
    }
]

 

_class로 구분하므로, 자식 클래스의 이름을 변경하면 데이터를 가지고 오지 못한다.

 

아래와 같이 데이터가 들어있을 때, Novel 클래스 이름을 Novel2로 변경하였다.

Novel에 해당하는 데이터가 나오지 않음을 확인할 수 있다.

따라서, 자식 클래스명을 변경하면 기존 저장된 _class 정보와 맞지 않으므로 데이터를 가져오지 못하게 된다.

운영 서버라면 자식 클래스명을 변경하지 않도록 주의해야한다.

[GET] http://localhost:8080/inheritance

== Response Body
[
    {
        "id": "6281e98b568c88760793213e",
        "name": "소설 제목"  // 자식 클래스의 이름 변경으로 인해 author와 novelType이 나오지 않음
    },
    {
        "id": "6281e98b568c88760793213f",
        "name": "에세이 제목",
        "author": "저자",
        "essayType": "일상"
    },
    {
        "id": "6281e98b568c887607932140",
        "name": "영화",
        "director": "감독",
        "actor": "배우"
    }
]

 

@TypeAlias

Class 명이 변경되면 기존에 MongoDB에 저장된 데이터를 가지고 오지 못한다는 것에 위험성을 느꼈다.

나중에 누군가가 클래스명을 모르고 변경할 수 있기 때문이다.

 

@TypeAlias를 사용함으로써 _class에 어떤 정보를 넣어줄 지 정할 수 있다.

 

아래와 같이 클래스에 @TypeAlias를 지정해줌으로써

 

MongoDB의 _class가 변경된 것을 확인할 수 있었다.

조회 또한 UploadDocDocument클래스로 잘 가지고 오는 것을 확인했다.