~대체로 설계가 복잡해지면 각 도메인에 맞는 테이블을 설계하고 연관관계를 설정해 조인(Join) 등의 기능으로 활용합니다.
JPA를 사용하는 애플리케이션에서도 테이블의 연관관계를 엔티티 간의 연관관계로 표현할 수 있습니다.~
-스프링부트 핵심 가이드 中-
연관 관계의 주인
JoinColumn
주인
은 외래키
가 있는 곳으로 설정다
쪽이 외래 키를 갖는다.name
속성 사용 * 사용 가능한 속성
- name : 매핑할 외래키 설정
- referencedColumnName : 외래키가 참조할 상태 테이블 컬럼명 지정
- foreignKey : 제약 조건 설정
- unique, nullable, insertable, updatable..
mappedBy
@OneToOne(mappedBy="ssn")
private Person person;
> Persion 엔티티가 Ssn 엔티티의 주인이 됨.
로딩
연관관계 매핑이란 객체의 참조와 테이블의 외래 키를 매핑하는 것을 의미한다.
JPA에서는 연관 관계의 엔티티 객체 자체를 전체 참조한다.
종류
방향
단방향 : 두 엔티티에서 한쪽의 엔티티만 참조
양방향 : 두 엔티티에서 서로의 엔티티 참조
- 외래키를 갖는 테이블을 주 테이블이라 한다.
@Entity
@Table(name="person")
@Getter
@Setter
@NoArgsConstructor
public class Person{
@Id
@GeneratedValue(strategy = GenerationType.IDENTIFY)
private Long personId;
private String name;
@OneToOne
@JoinColumns(name="ssn") // SSN = social Security Number - 주민번호
private SSN ssn;
}
@SpringBootTest
class SsnRepository[
@Autowired
private SsnRepository ssnRepository
@Autowired
private PersonRepository personRepository
~
// 생성한 데이터 조회
System.out.println("SavePerson : "+ ssnRepository.findById(ssn.getId()).get().getPersion());
System.out.println("SaveSSN : "+ ssnRepository.findById(ssn.getId()).get());
양쪽에서 단방향으로 서로 매핑
left join 두 번 실행되므로 비효율적(1:1시)
테이블간 연관관계를 맺어 한쪽 테이블이 외래키를 갖자!(주인-mappedBy)
@Entity
@Table(name="person")
@Getter
@Setter
@NoArgsConstructor
public class Ssn{
@Id
@GeneratedValue(strategy = GenerationType.IDENTIFY)
private Long id;
private String addres;
@OneToOne
private Person person;
}
먼저 테이블에 매핑되는 엔티티(동물원-동물)클래스를 생성한다.
(앞서 생성한 Person은 사육사 엔티티로 가정한다.)
Zoo
@Entity
@Getter
@Setter
public class Zoo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Animal
@Entity
@Getter
@Setter
@ToString(exclude={"person","zoo"})
~
public class Animal{
@Id
@GenratedValue(strategy = GenerationType.IDENTIFY)
private Long id;
private Int code;
private String type;
private String name;
@OneToOne(mappedBy="animal")
private Person person;
// 다대일 연관관계
@ManyToOne
@JoinColumn(name="animal_id")
private Zoo zoo;
}
PersonRepository - Person, Animal 연관관계 테스트
@SpringBootTest
public class PersonRepositoryTests {
@Autowired
private PersonRepositroy personRepositroy;
@Autowired
private AnimalRepository animalRepository;
@Test
public void relationTest(){
Person person = new Person;
person.setName("김사육사");
personRepositroy.save(person);
Animal animal = new Animal();
animal.setCode(001);
animal.setType("사자");
animal.setName("이거사자");
animal.setPerson(animal);
personRepositroy.save(animal);
System.out.println(
"animal : " + personRepositroy.findById(1L)
.orElseThrow(RuntimeException::new));
System.out.println(
"Person : "+ personRepositroy.findById(1L)
.orElseThrow(RuntimeException::new).getPerson());
}
}
Animal 엔티티에서 Person 엔티티 연관관계를 맺으로 PersonRepository만으로도 Person 객체도 조회 가능
// Zoo 엔티티와 Animal 엔티티의 일대다 연관관계 설정
@Entity
@Getter
@Setter
~
@ToString(exclude="zoo") // 순환 참조 방지
public class Zoo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "zoo", fetch = FetchType.EAGER)
private List<Animal> animalList = new ArrayList<>();
}
@OneToMany
가 붙은 쪽(animal
)에서 @JoinColumn
을 사용시 상대 엔티티(zoo
)에 외래키가 설정된다.@OneToMany
fetch = Lazy(default)zoo.getAnimalList().add(animal); // 무시된다.
일대다 양방향 관계는 어느 엔티티 클래스도 연관관계의 주인이 될 수 없으므로 다루지 않는다.
(새로운 엔티티 생성) 동물 종류(Category:1) - 동물(Animal:N)
@Entity
@Getter
@Setter
@ToString
~
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String code;
private String name;
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name="category_id")
private List<Animal> animals = new ArrayList<>();
}
동물 종류 엔티티에서 @OneToMany
, @JoinColumn
사용시 별도 설정 없이도 일대다 단방향 연관관계가 매핑된다. 지금같은 일대다 단방향 관계의 단점은 매핑 주체가 아닌 반대 테이블에 외래키가 추가되며, 이 방식은 다대일 구조와 다르게 외래키 설정위해 다른 테이블에 대한 update 쿼리를 발생시킨다. 이러한 문제 해결을 위해서는 다대일 연관관계 사용이 권장된다.
실무에서 거의 사용되지 않음
중간 테이블이 숨겨져 있어 사용자도 모르는 복잡한 조인의 쿼리 발생 우려
한 종류의 A가 여러 B와 연결될 수 있고, 한 종류의 B가 여러 A와 연결될 수 있다.
각 엔티티가 서로를 리스트로 갖는 구조 생성되므로 교차 엔티티(중간 테이블) 생성해 다대다 or 일대다 관계로 해소한다.
@Entity
@Getter
@Setter
@ToString(exclude = "products")
~
public class Zoo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany
private List<Animal> animals = new ArrayList<>();
public void addAnimal(Animal animal){
animals.add(animal);
}
}
위에서 다대다 연관관계는 @ManyToMany
로 설정하며, 리스트를 필드로 갖는 객체에서는 외래키를 갖지 않으므로 별도 @JoinColumn
은 설정하지 않아도 된다.
별도 설정이 없는 경우 테이블은 "zoo_animal"라는 이름으로 설정되며, 설정 변경시에는 @JoinColumn(name="이름")
으로 변경이 가능하다.
zoo_animal 테이블은 zoo와 animal 테이블에서 id 값을 가져와 두 개의 외래키가 생성된다.
@Entity
@Getter
@Setter
@ToString(exclude = {"person", "animal", "animals"})
public class Animal {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int code;
privaet String name;
@OneToMany(mappedBy = "animal")
private Person person;
@ManyToOne
@JoinColumn(name = "zoo_id")
private Zoo zoo;
@ManyToMany
private List<Zoo>zooList = new ArrayList<>();
public void addZoo(Zoo zoo){
this.zooList.add(zoo);
}
}
필요에 따라 mappedBy 속성을 사용해 두 엔티티 간 연관관계 주인 설정이 가능하다. 애플리케이션 실행시 데이터베이스의 테이블 구조는 변경되지 않는데, 이는 중간 테이블이 연관관계를 설정하기 때문이다.