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;
}
비즈니스 로직에서 하나의 객체를 조회할 때 항상 따라다녀야 하는 객체가 존재한다고 가정해보자. 동물을 조회할 때, 항상 주인도 같이 조회해야 한다면?!
아래 3가지가 떠오른다.
항상 조회해야 한다는 요구사항이 있다면, 연관성이 높은 관계이다. 이 연관관계에 많은 메서드도 존재할 수 있다. 이를 JPA 연관관계 사용해서 둘의 관계를 나타내고, 더욱 객체지향적인 코드를 짤 수 있지 않을까?!
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로 찾는다.
만약 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(사람)의 아이디만 있으면 저장할 수 있다. 이런 경우, 데이터 정합성이 틀어질 수 있다.
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
을 사용할 수 있다.
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
는 쿼리 성능상 지향하는 게 좋을 것 같다)