연관관계의 주인
JPA(Java Persistence API) 에서 연관관계의 주인 (Owner)이란, 관례를 매핑할 때, 어느 쪽 엔티티가 외래 키를 관리할지를 결정하는 것을 말합니다. 이 개념은 양방향 연관관계에서 특히 중요합니다.
연관관계의 주인이란?
- 두 엔티티 간의 관계에서 데이터베이스의 외래 키를 관리하는 엔티티를 가르킵니다.
- JPA에서는 한쪽 엔티티만이 외래 키를 관리해야 하므로, 이느 쪽이 이 역할을 할지 지정해야 합니다.
양방향 관계의서의 중요성
양방향 관계란 두 엔티티가 서로 참조하는 관계이며, 두 엔티티가 모두 외래키를 관리할 수 없기 때문에 어느 한쪽을 주인으로 지정해야 합니다. 이때, 주인의 결정 방법은 주로 외래키가 있는 쪽을 주인으로 정합니다.
- @JoinColumn 어노테이션이 붙은 쪽이 주인 입니다.
- @ManyToOne, @OneToMany -> 보통 @ManyToOne 이 있는 쪽이 주인 입니다. ( 외래키 관리 )
- 주인이 아닌 쪽에서는 "mappedBy" 속성을 사용하여 관계의 주인을 지정해야 합니다.
- 주인이 아닌 쪽에서 값을 변경해도 외래 키가 변경되지 않습니다. 따라서, 양방향 연관관계를 유지할 때는 양쪽 모두에 값을 설정 해야 합니다.
연관관계 편의 메서드
연관관계 편의 메서드란 JPA에서 엔티티 간의 양방향 관계를 보다 쉽고 안전하게 관리하기 위해 사용하는 메서드 입니다.
이러한 메서드는 객체 간의 관계를 설정할 때 발생할 수 있는 실수나 불일치를 방지 하는데 중요한 역할을 합니다.
- 일관성 유지 : 양방향 연관관계에서 한 쪽만 업데이트 하고 다른 쪽을 업데이트 하지 않으면 데이터의 일관성이 깨질 수 있습니다.
- 코드 중복 방지 : 양방향 매핑 시 양쪽에서 서로를 참조하는 코드를 작성해야 하는데, 이를 편의 메서드로 관리하면 중복을 방지할 수 있습니다.
편의 메서드 사용 코드 예시
Person.java
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToOne
private Address address;
@Builder
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
// 연관관계 편의 메서드 (양방향 연관관계일 때 사용)
public void setAddress(Address address) {
this.address = address;
this.address.setPerson(this);
}
}
Address.java
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String street;
private String city;
@OneToOne(mappedBy = "address")
private Person person;
@Builder
private Address(String street, String city, Person person) {
this.street = street;
this.city = city;
this.person = person;
}
// 연관관계 편의 메서드 (양방향 연관관계일 때 사용)
public void setPerson(Person person) {
this.person = person;
}
}
PersonService.java
@Service
public class PersonService {
private final PersonRepository personRepository;
private final AddressRepository addressRepository;
@Autowired
public PersonService(PersonRepository personRepository, AddressRepository addressRepository) {
this.personRepository = personRepository;
this.addressRepository = addressRepository;
}
@Transactional
public Person savePerson() {
Person person = personRepository.save(Person.builder().name("test").build());
Address address = addressRepository.save(Address.builder().city("test").street("test").build());
person.setAddress(address);
System.out.println("address.getPerson() = " + address.getPerson().getName());
return person;
}
}
만약 위 코드에서 연관관계의 주인에서 편의 메서드를 만들지 않았다면 savePerson 을 호출하고 address.getPersion().getName() 가져오면 null 이 됩니다.
public void setAddress(Address address) {
this.address = address;
this.address.setPerson(this);
}
위 코드 처럼 연관관계 주인에서 address를 세팅할 때 맺은쪽 address entity에 값을 넣으므로써 데이터의 일관성 유지를 할 수 있습니다.
만약, 1:N 관계일 경우에 다음과 같이 하시면 됩니다.
예를 들어, member, order 엔티티 사이에 1:N 관계가 있다고 가정하고, 이 경우 member 에서 여러 order 객체를 관리할 수 있는 컬렉션을 가지고 있어야 하며 order 측에서는 해당 주문을 소유한 member 객체에 대한 참조를 가지고 있어야 합니다.
- 양방향 링크 설정 : 한 쪽에서 연관관계를 설정할 때 반대편에서도 연관관계가 설정되도록 해야 합니다.
- 무한 루프 방지 : 연관관계 편의 메서드를 호출할 때 무한 루프에 빠지지 않도록 주의해야 합니다. 이는 양쪽 엔티티에서 서로를 호출하게 되는 상황을 방지하기 위함입니다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
// 연관관계 편의 메서드
public void addOrder(Order order) {
orders.add(order);
if (order.getMember() != this) {
order.setMember(this);
}
}
}
@Entity
public class Order {
@Id
@GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "member_id")
private Member member;
// 연관관계 설정 메서드
public void setMember(Member member) {
this.member = member;
if (!member.getOrders().contains(this)) {
member.getOrders().add(this);
}
}
}
'JPA' 카테고리의 다른 글
Spring Data JPA (0) | 2024.03.08 |
---|---|
JPA 조회 전략 (0) | 2024.03.07 |
JPA Entity 설계시 베스트 프랙티스 (0) | 2024.02.22 |
JPA OSIV (0) | 2024.01.31 |
JPA Auditing (0) | 2024.01.30 |