토이프로젝트를 진행하던 도중 User와 Fund라는 엔티티에서 사용되는 이미지, 비디오, 음악들을 하나의 Content라는 엔티티를 통해 관리하려고 아래와 같이 엔티티들을 만들었다.
@Entity
public class User {
@Id
@Column(name = "USER_ID")
private String id;
@OneToOne
@JoinColumn(name = "USER_CONTENT_ID")
private Content content;
...
}
@Entity
public class Fund {
@Id @Column(name = "FUND_ID")
@GeneratedValue(strategy = GenerationType.AUTO)
private int fundNo;
@ManyToOne
@JoinColumn(name = "USER_ID")
private User user;
@OneToMany(mappedBy = "fund")
private List<Content> imgList;
@OneToMany(mappedBy = "fund")
private List<Content> videoList;
@OneToOne(mappedBy = "fund")
private Content music;
...
}
@Entity
public class Content {
@Id @GeneratedValue(strategy = GenerationType.UUID)
@Column(name = "CONTENT_ID")
private UUID id;
@OneToOne(mappedBy = "content")
private User user;
@ManyToOne
@JoinColumn(name = "CONTENT_FUND_ID")
private Fund fund;
}
이후 JPA의 EntityManager를 통해 Fund 엔티티를 불러오다 "More than one row with the given identifier was found" 에러가 발생하였다.
Content엔티티에는 하나의 Fund만 연관관계로 맺어져있다 하지만 Fund에서는 3개의 Content 필드를 OneToMany로 Mapping하였기에 실제 DB에는 여러개의 같은 Fund를 외래키로 가지고있는 Content 객체들이 존재하였다.
Fund를 불러오는 과정에서 연관관계인 Content도 불러오게되는데 이 과정에서 하나의 Fund에 대한 여러개의 Content가 조회되어 위와 같은 에러가 발생하였다.
나의 원래 의도는 프로젝트에서 사용되는 이미지, 동영상, 음악들을 하나의 Content 엔티티로 관리하는것이였다. 하지만 위 코드처럼 작성한다면 Content가 이미지인지 동영상인지 혹은 음악인지 판단하기 어렵고 Content 엔티티가 Fund의 어떤 필드에 속하는지 매핑하는것이 불가능하다.
위 문제를 해결하기 위해 JPA에서 객체지향의 상속관계를 가능하게 해주는 @Inheritance를 사용하였다.
Content를 Id만 속성으로 가지는 abstract class로 변경한 뒤 Image, Video, Music 엔티티를 생성한 후 각각의 엔티티에 Fund와의 연관관계를 설정하였다.
이렇게 상속관계로 구현하여 각각 Image, Video, Music에 대한 Repository를 생성하는것이 아닌 Content에 대한 Repository만 생성하여도 된다는 장점이 생겼다.
@Entity
@Getter
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE")
public abstract class Content {
@Id
@Column(name = "CONTENT_ID")
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
}
@Entity
@DiscriminatorValue("I")
public class Image extends Content{
@ManyToOne
@JoinColumn(name = "IMAGE_FUND_ID")
private Fund fund;
@OneToOne
@JoinColumn(name = "IMAGE_USER_ID")
private User user;
...
}
@Entity
@DiscriminatorValue("M")
public class Music extends Content{
@OneToOne
@JoinColumn(name = "MUSIC_FUND_ID")
private Fund fund;
...
}
@Entity
@DiscriminatorValue("V")
public class Video extends Content{
@ManyToOne
@JoinColumn(name = "VIDEO_FUND_ID")
private Fund fund;
...
}
@Entity
public class Fund {
@Id @Column(name = "FUND_ID")
@GeneratedValue(strategy = GenerationType.AUTO)
private int fundNo;
@OneToMany(mappedBy = "fund")
private List<Image> imgList = new ArrayList<>();
@OneToMany(mappedBy = "fund")
private List<Video> videoList = new ArrayList<>();
@OneToOne(mappedBy = "fund")
private Music music;
...
}
JPA는 상속관계 구현 시 JOINED, SINGLE_TABLE, TABLE_PER_CLASS 전략을 사용하며 내가 설계한 엔티티는 데이터들을 많이 가지지 않기에 SINGLE_TABLE 전략을 사용하였다. 각각의 전략에 따른 테이블 구조는 아래와 같다.