본문 바로가기
  • Where there is a will there is a way.
개발/spring

JPA 연관관계

by 소확행개발자 2020. 3. 16.

연관관계

 

연관관계 중에선 다대일 단방향 관계를 가장 먼저 이해해야 한다. 

 

우리가 흔히 디비에서 데이터를 연관시키려면 외래키를 사용한다. 

외래키를 사용하여 정보를 제공하게 되면 자연스럽게 양방향으로 연관관계가 맺어진다.

 

하지만 객체의 경우엔 주소를 참조로 단방향으로 연관관계가 맺어진다. 만약에 양방향으로 걸고싶으면 서로 참조를 해야한다.

 

즉,

  • 객체의 경우엔 참조를 통하여 양방향 관계가 아니라 서로 다른방향 관계 2개다.
  • 테이블은 왜래 키로 양방향 연관관계를 맺는다. ( 조인 사용 )

 

연관관계 사용

 

저장

JPA 에서 엔티티를 저장할 때  연관된 모든 엔티티는 영속 상태여야 한다.

JPA 는 참조한 엔티티의 식별자를 외래 키로 사용해서 적절한 등록 쿼리를 생성한다.

( 여기서 연관관계 주인에 대한 개념이 나오는데 우리가 흔히 생각하는 테이블에서 외래키를 가진쪽 -> 연관관계 주인 이라고 표현한다. )

 

조회

연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지다.

 

객체 그래프 탐색 ( 객체 연관관계를 사용한 조회 )

객체지향 쿼리 사용 ( JPQL )

 

수정

수정은 update 메소드 없이 엔티티의 값만 변경해두면 트랜잭션을 커밋할 때 플러시가 일어나면서 변경 감지 기능이 작동한다.

 

연관관계 제거

참조형 이므로 null 을 set 해주면 되고 

실제 날아가는 쿼리는 해당 외래키를 null  처리한다.

 

삭제

 

연관관계 예제

단방향 매핑이 존재하고 양방향 매핑이 존재하는데

 

일대다 단방향일 경우에 키를 연관관계 주인이 관리하지 않는다.

 

따라서 JPA 는 insert 를 할때 외래키에 null 을 삽입하고 update 쿼리문을 다시 날리게 된다.

 

따라서 조회를 목적으로 하는 객체가 아닐경우에는 양방향을 걸어주는게 좋다.

 

다음은 양방향에 대한 간단한 entity 예제이다.

 

agentOption -> agentGood 다대일 양방향이다.

 

@Getter
@Setter
@Entity
@Table(name = "agent_good_base")
public class AgentGoodBase {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private Long id;

    @Column(name = "good_id")
    private Long goodId;

    @Column(name = "agent_good_id")
    private String agentGoodId;

    @Convert(converter = AgentTypeConverter.class)
    @Column(name = "agent_type")
    private AgentType agentType;

    @Column(name = "agent_category")
    private String agentCategory;

    @Column(name = "good_status")
    private String agentGoodsStatus;

    @Column(name = "additional_info")
    private String additionalInfo;

    @CreationTimestamp
    @Column(name = "reg_date")
    private LocalDateTime regDate;

    @Column(name = "mod_date")
    private LocalDateTime modDate;
	
    // cascade default 는 설정되지 않으며 persist 하지 않으면 업데이트시 쿼리문이 날라가지 않는다.
    @OneToMany(mappedBy = "agentGoodBase", cascade = CascadeType.PERSIST)
    private List<AgentGoodOption> agentGoodOptions;


    public void addAgentGoodsOption(AgentGoodOption agentGoodOption) {

        this.agentGoodOptions.add(agentGoodOption);

        // 무한루프에 빠지지 않도록 체크
        if(agentGoodOption.getAgentGoodBase() != this) {
            agentGoodOption.setAgentGoodBase(this);
        }

    }

}

 

 

 

@Getter
@Setter
@Entity
@Table(name = "agent_good_option")
public class AgentGoodOption {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column
    private Long id;

    @Column(name = "good_option_id")
    private Long goodOptionId;

    @Column(name = "good_option_item_id")
    private Long goodOptionItemId;

    @Column(name = "agent_option_id")
    private String agentOptionId;

    @Column(name = "option_status")
    private String optionStatus;

    @Column(name = "additional_info")
    private String additionalInfo;

    @CreationTimestamp
    @Column(name = "reg_date")
    private LocalDateTime regDate;

    @Column(name = "mod_date")
    private LocalDateTime modDate;

    @ManyToOne
    @JoinColumn(name = "agent_good_id")
    private AgentGoodBase agentGoodBase;


    public void setAgentGoodBase(AgentGoodBase agentGoodBase) {

        this.agentGoodBase = agentGoodBase;

        // 무한루프에 빠지지 않도록 체크
        if(!agentGoodBase.getAgentGoodOptions().contains(this)) {
            agentGoodBase.getAgentGoodOptions().add(this);
        }

    }

}

댓글