[JPA] 연관관계를 이용하여 객체를 조회해보자

Hocaron·2023년 9월 18일
0

Spring

목록 보기
31/44
post-custom-banner

Person (사람)과 Animal(동물)이라는 객체가 있다.

사람은 동물을 가지고 있을 수도 있고, 가지고 있다면 1마리만 가지고 있을 수 있다.
Animal(동물)의 ownerId(주인 아이디) 필드에는 personId(사람 아이디)가 들어간다.

create table animal
(
    animal_id bigint auto_increment
        primary key,
    owner_id  bigint null,
    
    constraint owner_id_uk
        unique (owner_id)
);

create table person
(
    person_id bigint auto_increment
        primary key,
    name      varchar(255) null
);

실제로 DB 컬럼에 FK 를 걸지는 않았다. 후에 JPA 연관관계를 추가한 경우에도 FK 는 추가하지 않았다.
FK 에 대한 이슈는 여기..🫠

public class Animal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "animal_id")
    private Long animalId;

    @Column(name = "owner_id")
    private Long ownerId;
}

public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "person_id")
    private Long personId;

    @Column(name = "name")
    private String name;
}

Join 쿼리문 대신 JPA 연관관계를 사용해서 조회해보자

비즈니스 로직에서 하나의 객체를 조회할 때 항상 따라다녀야 하는 객체가 존재한다고 가정해보자. 동물을 조회할 때, 항상 주인도 같이 조회해야 한다면?!

아래 3가지가 떠오른다.

  • Join 쿼리문을 이용한다
  • 2번 조회한다(동물 조회 -> 동물의 존재하는 주인의 아이디로 사람 조회)
  • JPA 연관관계를 이용한다.

항상 조회해야 한다는 요구사항이 있다면, 연관성이 높은 관계이다. 이 연관관계에 많은 메서드도 존재할 수 있다. 이를 JPA 연관관계 사용해서 둘의 관계를 나타내고, 더욱 객체지향적인 코드를 짤 수 있지 않을까?!

Animal(동물)의 주인을 찾을 때 객체로 찾기

public class Animal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "animal_id")
    private Long animalId;

    @OneToOne
    @JoinColumn(name = "owner_id")
    private Person owner;
}


Person 이 존재한다는 것을 확인 후에(데이터 정합성 통과) Person 의 Id를 가지고 있는 Animal 을 조회를 하기 때문에 animal 컬럼에 존재하는 owner_id로 찾는다.

Animal(동물)의 주인을 찾을 때 객체의 아이디로 찾기


만약 Person 의 Id로 바로 조회하는 경우는 left outer join 이 발생한다. 데이터 정합성이 맞지 않을 수 있지 않아서 Person 과 Join 하지 않을까??🤔

저장도 해보자

객체로 저장하기

public class Animal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "animal_id")
    private Long animalId;

    @OneToOne
    @JoinColumn(name = "owner_id", referencedColumnName = "person_id")
    private Person owner;

    public static Animal of(Person owner) {
        return new Animal(owner);
    }

    protected Animal(Person owner) {
        this.owner = owner;
    }
}

위의처럼 Person 이 존재한다는 것을 확인 후에 저장할 수 있다.

아이디로 저장하기

public class Animal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "animal_id")
    private Long animalId;

    @Column(name = "owner_id")
    private Long ownerId;

    @OneToOne
    @JoinColumn(name = "owner_id", referencedColumnName = "person_id", insertable = false, updatable = false)
    private Person owner;

    public static Animal of(Long ownerId) {
        return new Animal(ownerId);
    }

    protected Animal(Long ownerId) {
        this.ownerId = ownerId;
    }
}

Person(사람)의 아이디만 있으면 저장할 수 있다. 이런 경우, 데이터 정합성이 틀어질 수 있다.

양방향 매핑은?

Person(사람)의 반려동물을 찾는다면?

public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "person_id")
    private Long personId;

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

    @OneToOne(mappedBy = "owner")
    private Animal animal;

    public boolean hasAnimal() {
        return animal != null;
    }
}

사람에게 hasAnimal(반려동물 유무)를 물어볼 수 있다.
💡 Join 문을 사용할 때보다 더욱 객체지향적으로 Person 을 사용할 수 있다.

양방향 연관관계로 인한 순환참조는 다음과 같이 해결하자

DTO 클래스를 반환하자.

public class AnimalDto {

    private Long animalId;

    private String ownerName;

    public AnimalDto(Animal animal) {
        this.animalId = animal.animalId();
        this.ownerName = animal.owner().name();
    }
}

보통 Entity 클래스를 반환하는 경우는 없으므로, 위 방법으로 해결될 것이다.

어노테이션을 사용하자.

public class Animal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "animal_id")
    private Long animalId;

    @Column(name = "owner_id")
    private Long ownerId;

    @OneToOne
    @JoinColumn(name = "owner_id", referencedColumnName = "person_id", insertable = false, updatable = false)
    @JsonBackReference // ✨ 여기
    private Person owner;
}

public class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "person_id")
    private Long personId;

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

    @OneToOne(mappedBy = "owner")
    @JsonManagedReference // ✨ 여기
    private Animal animal;

    public boolean hasAnimal() {
        return animal != null;
    }
}

정리

  • 조회시 연관관계를 이용하여 객체를 더욱 생동감있게 구현할 수 있다.
  • @OneToMany 뿐만 아니라, @ManyToOne 에도 사용할 수 있다!(@OneToMany 는 쿼리 성능상 지향하는 게 좋을 것 같다)
  • 양방향 매핑시 순환참조에는 Dto 를 사용하자.

위의 예제 코드는 여기

profile
기록을 통한 성장을
post-custom-banner

0개의 댓글