[부록 2] 연관관계 효율성[작성중]

[부록 2] 연관관계 효율성[작성중]

JPA의 기본 사항을 정리한다.

OneToOne

효율적인 방법 : @MapsId를 사용하는 단방향/양방향 @OneToOne

  • 이유 : @MapsId를 사용하면 관계가 있는 두 엔티티가 같은 식별자 값을 공유하게 된다.
  • 이는 별도의 외래 키 칼럼을 생성하지 않고 기존의 식별자 필드를 재사용함으로써 데이터베이스 설계가 단순화되고, 조인 처리 시 성능이 향상된다.
@Entity
public class User {
    @Id
    private Long id;

    @OneToOne
    @MapsId
    private UserProfile profile;
}

@Entity
public class UserProfile {
    @Id
    private Long id;

    @OneToOne(mappedBy = "profile")
    private User user;
}

User 엔티티는 자신의 id를 기본 키로 가진다.
UserProfile 엔티티는 @MapsId를 사용하여 User의 id를 자신의 id로 매핑한다.
즉, UserProfile의 id는 User의 id와 동일한 값을 갖게 되며, 데이터베이스에서는 이 두 테이블이 같은 기본 키 값을 공유하게 된다. 데이터베이스 설계가 간결해지고, 조인을 수행할 때 효율성이 향상되는 장점이 있다.

User 테이블: id (기본 키) = UserProfile 테이블: id (기본 키이자 외래 키)

비효율적인 방법 : @MapsId를 사용하지 않는 양방향 OneToOne

  • 각 엔티티는 자체적인 식별자를 가지며 추가적으로 상대방 엔티티의 식별자를 외래 키로 관리해야 한다.
  • 이로 인해 데이터 중복과 추가적인 조인 처리가 필요하게 되어 성능 저하의 원인이 된다.

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

    @OneToOne(mappedBy = "user")
    private UserProfile profile;
}

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

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}

User 테이블: id (기본 키)
UserProfile 테이블: id (기본 키), user_id (외래 키)


OneToMany

효율적인 양방향 OneToMany와 단방향 ManyToOne

  • 주로 ManyToOne 측에 있는 엔티티가 외래 키를 관리하게 된다.
  • 이는 데이터베이스에서 외래 키를 한 쪽 테이블에만 유지하므로 업데이트가 쉬워지고 데이터 무결성을 효율적으로 관리할 수 있게 된다.
  • 또한, OneToMany 측에서는 단순히 ManyToOne 필드를 참조하여 관계를 설정하기 때문에 처리가 간결해진다.

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

    @OneToMany(mappedBy = "user")
    private List<Order> orders;
}

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

    @ManyToOne
    @JoinColumn(name = "user_id")
    private User user;
}

덜 효율적인 @JoinColumn(name = “foo_id”, insertable = false, updatable = false)

위의 @JoinColumn(name = “user_id”)를 insertable = false, updatable = false로 설정하는 경우이다.
@ManyToOne에서 이미 생성된 User 객체만 Setting 할 수 있다.

//file: `효율적으로 JoinColumn 사용시`

User newUser = new User(); // 새 User 객체
        newUser.setName("Jane Doe");

        Order newOrder = new Order(); // 새 Order 객체
        newOrder.setItem("Book");
        newOrder.setUser(newUser); // Order 객체에 User 객체 연결

        entityManager.persist(newOrder); // Order와 User 함께 저장

이 경우, newOrder와 함께 newUser도 데이터베이스에 저장되고,
newOrder의 user_id 외래 키는 newUser의 ID로 자동 설정된다.

//file: `insertable = false, updatable = false 사용시`
User existingUser = entityManager.find(User.class, existingId); // 기존 User 검색

        Order newOrder = new Order(); // 새 Order 객체
        newOrder.setItem("Laptop");
        newOrder.setUser(existingUser); // 기존 User 객체를 Order에 연결

        entityManager.persist(newOrder); // Order 저장

newOrder는 existingUser의 정보를 참조하되, newOrder를 통해 existingUser의 user_id를 변경할 수는 없다.

비효율적인 Set + 단방향 @OneToMany + (@JoinColumn or @OrderColumn)

  • JoinColumn의 비효율성의 원인
    • 관계 에 대한 변경이 있을 때마다 해당 컬렉션의 모든 엔티티를 업데이트해야 할 필요가 있다
  • OrderColumn의 비효율성의 원인
    • 컬렉션 내의 엔티티 순서가 변경되면, JPA 구현체는 관련된 모든 엔티티의 순서 인덱스를 업데이트해야 한다.
    • 엔티티를 추가하거나 제거할 때, 나머지 엔티티의 순서 인덱스를 재조정해야 하므로 데이터베이스에서 많은 업데이트 연산이 필요해진다.

최악인 List + 단방향 @OneToMany

  • 인덱스 컬럼 관리: List와 @OrderColumn 사용 시, JPA는 컬렉션의 각 엔티티에 대해 순서를 유지하기 위한 인덱스 컬럼을 추가한다.
  • 동작의 영향범위가 복잡해진다. (Member - Team의 경우, Member를 지우면 Team에서는 지워지지 않는다 같이)

ManyToMany