스프링데이터 + JPA/스프링 데이터 JPA

4. 예제 도메인 모델 구현

sdafdq 2023. 11. 18. 22:08

우선 간단하게 이런 거 구현 할 거임.

다대일 관계로,

DB에서는 당연히 단일방향.

 

뭐 그러면 엔티티는 일단은 자기가 가지고 있는 원시타입? 은 다 가지고 있을테고,

그럼 

memberId,

username

age

 

기본적으로 이렇게 있을거고,

팀도 

teamId,

name

 

이렇게 있을건데,

연관관계를 어떻게 가져갈까.

일단은 기본적으로 단방향 연관관계로 짜놓는게 훨씬 좋음.

주인도 (mappedBy가 아닌 실제 DB에서 값을 읽어오고 얘의 값이 바뀌면 실제 DB의 값도 바뀌는 곳)

다 쪽이, 그러니까 외래키를 가지고 있는 쪽이 주인이 되는 게 좋음(왜냐하면 엔티티 상에서 외래키를 가지고 있는 쪽이 주인이 아니게 되면 쿼리가 나갈 때 한번 거쳐서 가게 되므로, team이 주인이 되면 팀 -> 멤버, 외래키를 멤버가 가지고 있어서 수정하거나 읽어오려면 이렇게 거쳐서 쿼리가 나가야 해서 성능 상 불리한 점이 있음.)

 

그래서 보니까, 이번 예제는 그냥 간단하게 양방향임..

그냥 둘다 연관관계 가지고 있음.. (기본은 단방향으로 하고, 필요할 때 양방향으로 구현하는 게 좋음)

 

그러면 주인쪽은 당연히 멤버쪽..

 

 

구현 해 보겠다.

 

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
    @Id @GeneratedValue
    @Column(name="member_id")
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;

    public Member(String username, int age) {
        this.username = username;
        this.age = age;
    }

}

클래스 애노테이션 중 NoArgs 말 그대로 인자 없는 생성자는 접근레벨을 protected로,(jpa에서 내부적으로 엔티티로 만들기 위해? 프록시로 만들기 위해, 빈 인자 생성자를 열어둬야 하는데, public은 좀 그렇긴 한데 protected까지는 허용하도록 만들어 줬음.)

 

거기서 이제 id는

@Column 해서 테이블 열 중 member_id라는 걸 id로.

이렇게 객체는 사실 member.id 같은 느낌으로 누구의 것인지 확실한 데, 테이블은 예를 들어 조회해올때 id가 다 같으면 좀 그럼.. 그래서 보통 테이블은 관례적으로 member_id 이렇게 붙임.

 

그래서 얘도 엔티티의 필드는 id지만, 컬럼명은 member_id라는 걸 제대로 명시해 줌.

 

컬럼 관련된 거는 name 붙여야 하는 듯. 기본이 컬럼명 관련이 아닌 듯.

 

연관관계, 다대일, @JoinColumn은 member 테이블이 가지고 있는 열 중 team_id.

ToOne은 JPA내부에서도 하나정도 더 가져오는 것은 무리가 없다고 판단해서 기본이 fetch=FetchType.EAGAR, 즉 즉시 로딩이다. 그래서 fetch = FetchType.LAZY 이렇게 해 줘서 지연 로딩으로 바꿔줘야 한다.

즉시로딩, 이게 JPA기본값이라는 건 권장이라는 건데, 저걸로 성능최적화 하는데 여러 핀트가 안 맞는 부분이 있다. 그래서 그냥 지연로딩으로 다 만드는 거다. 모두가 다 지연로딩이여야 한번에 뭘 쫙 처리하기가 쉽다.

 

@Entity
public class Team {
    @Id @GeneratedValue
    @Column(name="team_id")
    private Long id;
    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

다음은 Team의 엔티티.

id의 @Column 컬럼 명도 지정해 주고, (기본값은 필드명임. 대문자는 스네이크식으로 변환됨.)

 

연관관계도 저게 일대다라, 다쪽 중 하나의 엔티티의 "team"이라는 필드로부터 매핑 되겠단 거임.

그러니까 다중, List<Member>중 하나의, 즉 Member의.

여기에서 이제 mappedBy로 team. Member의 "team"이라는 필드명을 가진 필드에 의하여 매핑되겠단 거임.

그래서 얘는 그냥 매핑? 읽기전용이라, 얘의 값을 바꿔도 DB에 반영되는 것은 없음.

근데 Member가 team을 바꾸는 것은 얘가 mappedBy가 아니라, 즉 연관관계의 주인이라서 DB에 반영됨. 트랜잭션 안쪽 이면.

 

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "username", "age"})
public class Member {
    @Id @GeneratedValue
    @Column(name="member_id")
    private Long id;
    private String username;
    private int age;

    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;

    public Member(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

여기 그냥 보면 애노테이션 하나 설명하려고..

@ToString 애노테이션

of={"필드명", "필드명", "필드명"}

하면은 저 객체 toString하면 저 내용으로 나오게끔 해줌.

 

근데 참고로, 저기에 team 적으면 큰일 남.

왜냐하면 team도 members를 가지고 있기 때문.

그렇게 되면

team가서 team에 관한거 쭉 읽다가 

team에 있는 members가서 member에 대한 거 하나씩 읽기 시작하는데 또 거기서

team에 가서 members에 가서 member에 가서

또 team에 가서 members에 가서 member에 가서

무한루프임.

도돌이 표.

 

그냥 연관관계 필드는 ToString 안하는 게 좋음.

Member(id=2, username=member2, age=11)

이런 식으로 출력됨.

 

그리고, @Getter는 아무래도 그냥 있는게 좋긴 한데,

@Setter는 왠만하면 막아두셈.

필요할 때만 그 필드 열지 말지 고민하셈.

 

@Setter(AccessLevel.PRIVATE)

이렇게 클래스 레벨에 붙이면 모든 setter의 접근레벨을 private로 할 수 있음.

개개별 필드에 접근 레벨을 다르게 하고 싶다면,

@Setter(AccessLevel.PUBLIC)
private String username;

이렇게. 자세할 수록 우선 반영이니 저렇게 따로 넣어주면 됨.

 

 

그리고 Member에 연관관계 편의 메소드 하나 추가했다.

연관관계 편의 메소드란 말 그대로 연관관계에 관련한 메소드이다. 편의를 주기 위한.

public void changeTeam(Team team){
    this.setTeam(team);
    team.getMembers().add(this);
}

이렇게.

팀을 바꾸면 양쪽 다 영향이 가게.

아니면 member 커밋, 다시 team 조회 전까지 객체 member에게만 team이 바뀐 상태로 존재할테니까.