개요
프로젝트를 진행하던 도중 Spring JPA를 사용해서 데이터베이스 테이블을 일대다 관계를 만들어야 했지만 방법을 몰라 알아보고 만든 것을 작성하겠다.
먼저 Spring JPA는 자바에서 사용하는 객체의 개념과 데이터베이스에서 사용하는 테이블간의 다른 차이점을 해소시켜서 매핑해주는 API이다.
즉, JPA가 하고자하는 방향은 객체지향적인 방법으로 데이터베이스를 만드는 것이다.
따라서 데이터베이스 테이블의 관계도 객체지향적인 개념으로 접근해야 했지만 이해하기가 정말 어려웠다.
연관관계
단방향과 양방향
먼저 JPA에서 테이블의 연관관계를 만들어주는 방법은 단방향과 양방향으로 나뉜다.
단방향
class A{
private B b;
}
class B{
private int number;
}
단방향
: 객체지향적 관점에서 보면 애초에 객체는 양방향이라는 개념이 없고 단방향만 존재한다. 즉, A클래스의 필드로 B클래스의 객체를 가지고 있다고 생각하면 된다.
양방향
class A{
private B b;
}
class B{
private A a;
}
양방향
: 서로가 서로를 참조하고 있다고 생각하면 된다.
이렇게 단방향, 양방향으로 나뉘는 이유는 기본적으로 데이터베이스에서는 외래키 참조를 통해서 JOIN하면 어느곳에서든지 서로 참조가 가능하기 때문에 이렇게 접근할 필요 조차 없다.
일대다 관계에서 단방향 연관관계는 B가 A를 가지고 있는 것이고 양방향 연관관계가 되면 A도 B를 List로 가지게 된다.
JPA에서 단방향과 양방향
단방향
@Entity
class A{
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
}
@Entity
class B{
private int id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id")
private A a;
}
객체지향적인 관점에서 보았을 때는 데이터베이스처럼 B가 A의 id를 가지고 있어서 JOIN하는 방법은 없기 때문에 직접적으로 참조할 수 있는 A를 가지게된다.
@ManyToOne
: B의 입장에서 바라보았을 때 자신은 일대다에서 ‘다’쪽에 속하기 때문에 ManyToOne관계를 가지게 된다.
FetchType.LAZY
: B가 A를 참조하여 수정하기 전까지는 실제 데이터베이스로부터 값을 읽어오지 않는다. (좀더 효율적으로 작동)
@JoinColumn
: 데이터베이스에서 외래키로 가져올 컬럼의 이름을 지정해준다.
이렇게 하면 실제 데이터베이스에서는 A 테이블과 B테이블간의 관계가 외래키를 통해서 생기게된다.
데이터베이스와 자바가 서로 다른점은 데이터베이스는 언제든지 조인을 통해서 A와 B모두를 참조할 수 있지만 위의 코드에서 B는 A를 참조하여 알아낼 수 있지만 A는 B를 참조하지 않기 때문에 B를 가져올 수 없다.
따라서 단방향 연관관계는 B가 A를 조회하는 일은 발생하지만 A가 B를 참조하는 일이 없는 경우 사용하게된다.
→ 다시한번 말하지만 실제 데이터베이스에는 외래키 제약조건을 통한 관계가 생긴다.
양방향
@Entity
class A{
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
@OneToMany(mappedBy = "A", cascade = CascadeType.REMOVE, orphanRemoval = true)
private List<B> bList = new ArrayList<>();
}
@Entity
class B{
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "id")
private A a;
}
양방향 연관관계가 되면 A도 B를 참조하여 조회할 수 있게 된다.
@OneToMany
: A의 입장에서 바라보았을 때 A가 일대다에서 ‘일’에 해당하기 때문에 OneToMany가 된다.
mappedBy
: 누가 연관관계의 주인이 아닌지 설정해주는 옵션이다.- 자바에서는 연관관계에 대한 정보를 설정할 수 있는 클래스가 어디인지 나눠지게 된다.
- 여기서 설정해주는 값은 연관관계의 주인에 의해 맵핑되어지는 클래스를 말한다.
- 따라서 B 객체 필드에 있는 A를 수정해주는 것으로 연관관계가 설정되고 A에 있는 bList의 수정으로는 변경되지 않고 오직 조회를 목적으로 사용되어진다.
cascade
: 연관관계의 제약조건을 설정한다. 여러 옵션이 있기 때문에 찾아보길 바란다.- 여기서 사용한 CascadeType.REMOVE 옵션은 연관관계에서 부모 테이블(A)의 튜플이 삭제될때 자식 테이블(B)에서 참조하고 있던 튜플들도 모두 삭제된다는 의미이다.
orphanRemoval
: 고아 튜플 즉, B가 아무런 A도 참조하지 않는다면 삭제한다는 의미이다.- CascadeType.REMOVE와 뭐가다른지는 이해를 정확히 하지못해 찾아봐야 할 것 같다.
이렇게 양방향으로 연관관계를 설정하면 실제로 데이터베이스에는 단방향과 같은 스키마를 가지고 있지만 자바에서는 A와 B모두 서로를 조회할 수 있게 된다.
결론
단방향과 양방향 연관관계에 대하여 살펴보았다.
- 단방향 연관관계는 자바에서 연관된 두 클래스 중 한쪽에서 다른 쪽으로 참조하겠다는 의미이고 반대쪽 클래스에서는 조회를 할 일이 없는 경우 사용한다.
- 양방향 연관관계는 양쪽 클래스 모두가 서로를 참조하겠다는 의미이고 서로가 서로를 조회할 일이 있는 경우 사용된다.
객체지향적인 관점과 데이터베이스의 관점에서 데이터라는 형식과 의미가 다른 것을 해결해주기위해 나온 것이 JPA였지만 계속 데이터베이스를 생각하면서 JPA를 이해하려고해서 어려웠던 것 같다.
좀더 생각을 유연하게 가지고 나갈 필요가 있는 것 같다.
'Spring' 카테고리의 다른 글
[Spring Test] MockMvc Response로 검증하기 (0) | 2024.02.05 |
---|---|
[Spring Boot] 환경변수로 .jar와 docker run 프로파일 결정 (0) | 2024.02.05 |
[Spring] ControllerAdvice, RestControllerAdvice를 통한 예외 처리 (0) | 2024.02.02 |
[Spring Test] Spring Boot Controller 단위 테스트 (1) | 2024.01.31 |
[Spring Test] Mockito when()과 given() 차이 (0) | 2024.01.29 |