JPA 연관관계

XingXi·2024년 1월 2일
0

JPA

목록 보기
12/23
post-thumbnail

🍕 Reference

자바 ORM 표준 JPA 프로그래밍 : 교보문고
자바 ORM 표준 JPA 프로그래밍 - 기본편 : 인프런

N:1 - @ManyToOne

단방향

많이 사용하는 연관관계 중 하나이다.
보통 단방향으로 설정한 후 필요에 의해 양방향으로 변환하는 경우가 대다수이다.

Member ( N )

@Entity
public class MemberManyToOne
{
    @Id @GeneratedValue
    private Long id;

    @Column(name = "USER_NAME")
    private String userName;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private TeamManyToOne team;

Team ( 1 )

@Entity
public class TeamManyToOne {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

    private String teamName;

DB 설계에 맞게 외래키가 존재하는 Table 에
@ManyToOne, @JoinColumn을 추가하고
@JoinColumnname 를 사용해서 외래키 값을 설정한다

@JoinColumn 의 name 속성에 대해서

사실 @JoinColumn 의 name 속성은 외래키의 이름을 설정하는 속성이다.
name 값 설정에 따른 결과를 확인해 보자.

1. name 값에 객체의 PID 값이 아닌 값을 넣을 경우

    @ManyToOne
    @JoinColumn(name = "TEAM_IDs")
    private TeamManyToOne team;

외래키 이름이 사용자가 지정한 값 TEAM_IDs 으로 변경된 것을 확인할 수 있다.

2. name Parameter 를 사용하지 않는 경우

	@ManyToOne
    @JoinColumn
    private TeamManyToOne team;

team_TEAM_ID 값으로 외래키가 생성된 것을 확인할 수 있다.

3. 연관관계인 객체의 필드값을 다르게 설정하고 @JoinColumn 의 name 값에 아무것도 할당하지 않은 경우

Member

	@ManyToOne
    @JoinColumn
    private TeamManyToOne teamInfo;

Team

@Entity
public class TeamManyToOne {
    @Id @GeneratedValue
    @Column(name = "TEAM_Ids")
    private Long id;


연관관계 주인인 필드의 변수이름과 연관관계 대상의 ID 컬럼 값을 조합하여 외래키가 형성 된 것을 볼 수 있다.

@JoinColumn 의 name 속성은 테이블에 외래키 이름을 지정하기 위한 속성이며
name 속성을 사용하지 않을 경우
@JoinColumn 이 선언된 필드의 변수이름+_+대상 객체의 PK 변수의 컬럼값 으로 자동 생성 된다. 실질적으로 연관관계를 맺어주는 속성은 referencedColumnName 이다.
외래키가 참조하는 대상 테이블의 컬럼명을 지정해주며 기본 값이 대상 테이블의 PK 이다

양방향

DB 에 적용되기 이전에 양쪽 객체에서 조회되도록 설정
연관 관계 주인 은 수정 권한을 가지지만
연관 관계 대상 객체는 읽기 전용이 된다.

Member

@Entity
public class MemberManyToOne2Side
{
...

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private TeamManyToOne2Side team;

Team

@Entity
public class TeamManyToOne2Side {
...


    @OneToMany(mappedBy = "team")
    private List<MemberManyToOne2Side> members = new ArrayList<>();

연관관계 차수 Annotation 을 선언한 뒤 mappedby 속성을 사용

mappedby

  • 객체 연관관계 Mapping 시 연관관계의 주인이 아님을 알리기 위해 주로 사용한다.
  • 연관관계 대상인 객체에서 연관관계 주인 객체 변수 값을 할당한다.
  1. mappedby 에 잘못된 값을 넣으면 어떻게 될까?
@Entity
public class MemberManyToOne2Side
{
...

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private TeamManyToOne2Side team;


------

@Entity
public class TeamManyToOne2Side {
...


@OneToMany(mappedBy = "teams")
    private List<MemberManyToOne2Side> members = new ArrayList<>();

MemberManyToOne2Side에서 TeamManyToOne2Side 타입의 변수로 team을 사용했지만
TeamManyToOne2Side 객체에서 mappedBy값을 teams로 사용하였다.

Exception in thread "main" org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: org.example.javamain.study.manytoone2side.MemberManyToOne2Side.teams in org.example.javamain.study.manytoone2side.TeamManyToOne2Side.members
	at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:785)
	at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:736)
	at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:54)
	at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1696)
	at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1664)
	at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:287)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:904)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:935)
	at org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:56)
	at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79)
	at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54)

mappedby 에 할당된 대상 엔티티를 찾을 수 없다는 예외가 발생한다

  1. 생략해보자.

Member

@Entity
public class MemberManyToOne2Side
{
...
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private TeamManyToOne2Side team;

Team

@Entity
public class TeamManyToOne2Side {
...
@OneToMany
    private List<MemberManyToOne2Side> members = new ArrayList<>();

연관관계 Mapping 위한 테이블이 하나더 추가가 된다.

양방향 Mapping 시 toString 이나 JSON Library 를 사용하는 경우

무한찹조가 일어나지 않게 조심해야한다.


1:N - @OneToMany

Table
Object

class 다이어그램 과 ER 다이어그램에 외래키가 존재하는 테이블(객체) 가 다르다.
Table에는 Member 테이블에 외래키가 존재하지만
객체에는 Team 에 외래키가 존재한다.

단방향

Member

@Entity
public class MemberOneToMany1Side
{
    @Id @GeneratedValue
    private Long id;

    @Column(name = "USER_NAME")
    private String userName;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}

Team

@Entity
public class TeamOneToMany1Side {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

    private String teamName;

    @OneToMany
    @JoinColumn(name = "TEAM_PID")
    private List<MemberOneToMany1Side> oneToMany1Sides = new ArrayList<>();
  • JoinColumn 또한 외래키의 이름을 설정하는 속성이기 때문에
  • Member 객체에 외래키가 생성됨으로
  • MEMBER_PID 가 아닌 TEAM_PID 를 기재해야한다.

Team가 연관관계 주인이지만 테이블에서 외래키는 Member가 가지고 있기 때문에
Team 에 속해있는 Member테이블의 외래키를 업데이트하는 쿼리가 필요하다.
엔티티가 관리하는 외래키가 다른테이블에 존재하기 때문에
JPA 에서 추가적인 UPDATE QUERY 가 발생한다.

양방향

Member

@Entity
public class MemberOneToMany2Side
{
    @Id @GeneratedValue
    private Long id;

    @Column(name = "USER_NAME")
    private String userName;

    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    private TeamOneToMany2Side team;

Team

@Entity
public class Team
{
	@OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<>();
  • mappedby 를 사용한 것이 아닌 @JoinColumn 을 차수가 N 인 쪽에서 건다
  • insertable, updateable 값을 false 로 두어 읽기 전용 필드로 사용한다.

1:1 OnetoOne

Table
Object

단방향

Member

@Entity
public class MemberOneToOne1Side
{
    @Id @GeneratedValue
    private Long id;

    @Column(name = "USER_NAME")
    private String userName;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private LockerOneToOne1Side locker;

Locker

@Entity
public class LockerOneToOne1Side {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;

대상 테이블에 외래키가 존재하는 경우

객체에서는 관리가 되지 않는다

앙방향

Member

@Entity
public class MemberOneToOne1Side
{
    @Id @GeneratedValue
    private Long id;

    @Column(name = "USER_NAME")
    private String userName;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private LockerOneToOne1Side locker;

Locker

@Entity
public class LockerOneToOne2Side {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;


    @OneToOne(mappedBy = "locker")
    private MemberOneToOne2Side member;

N : M @ManyToMany

Table
Object

RDB 에서는 테이블 2 개로 N : M 을 구현하기 힘들다.

중간에 연결해주는 Table 없이 테이블 2개만으로 N:M 연관관계 하는 것이 DB 설계 원칙에
어긋나기 때문에 권장하지 않는다.

학생과 수강신청 관계를 예로 들어보자..

  • 학생은 여러가지의 강의를 수강할 수 있음
  • 강의는 여러명의 학생이 들을 수 있다 .

    만약 중간테이블이 존재하지 않는 경우 양쪽 테이블에 외래키를 두어야하거나
    특정 테이블의 한 컬럼에 여러개의 데이터가 들어갈 수 있다 ( 제1 정규화 대상 )
    이러한 문제 때문에 관계형 데이터 베이스에서는 중간 테이블을 생성하여 사용한다.

ManyToMany 사용

Member

@Entity
public class MemberManytoMany1Side
{
    @Id @GeneratedValue
    private Long id;

    private String userName;

    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT")
    private List<ProductManytoMany1Side> products = new ArrayList<>();

Product

@Entity
public class ProductManytoMany1Side {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;

    private String name;


업로드중..

ManyToMany 를 사용한 결과 JPA 에서 연결을 위한 테이블이 자동으로 생성 되었다.
@JoinTable Annotation 의 name속성을 사용하여 JPA 가 자동으로 생성할 연결 테이블의 이름을 설정했다.
하지만 JPA 가 생성하는 숨겨진 테이블이기 때문에
1.쿼리하기 힘들고
Mapping 정보만 자동으로 생성하기 때문에 집계함수나
2.추후에 두 테이블에 관한 다른 필드들이 추가되기 어렵다.
그래서 @ManyToMany는 잘 사용하지 않는다.

해결방법 : 연결 테이블을 직접 작성

Member

@Entity
public class MemberManytoMany1Side
{
    @Id @GeneratedValue
    private Long id;

    private String userName;

    @OneToMany(mappedBy = "member")
    private List<MemberProduct> memberProductList = new ArrayList<>();

Product

@Entity
public class ProductManytoMany1Side {
    @Id @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;


    @OneToMany(mappedBy = "product")
    private List<MemberProduct> memberProductList = new ArrayList<>();

MemberProduct

@Entity
public class MemberProduct
{
    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private MemberManytoMany1Side member;

    @ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private ProductManytoMany1Side product;

여러가지 Mapping 관계에 대해 알아보았다. 객체와 테이블의 다른객체를 불러오는 방식의 차이로 인해 객체에서 단방향,양방향 개념이 생기며 성능에 따라 외래키가 존재하지 않은 테이블에 외래키 설정을 하는등 많은 것을 고려해야한다는 것을 다시 한번 배울 수 있었다.

0개의 댓글