연관관계 매핑 기초

born_a·2022년 8월 31일
0

단방향 연관관계

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try {
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            member.setTeamId(team.getId());
            em.persist(member);

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }

        emf.close();
    }
}

단방향 연관관계

객제 지향 모델링


에러가 나는 이유 : jpa한테 Member와 Team의 관계가 무슨 관계인지 알려줘야 한다. 일대다인지 다대알인지

@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

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


    //@Column(name = "TEAM_ID")
    //private Long teamId;
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Team getTeam() {
        return team;
    }

    public void setTeam(Team team) {
        this.team = team;
    }
}

@Id, @Column과 같은 어노테이션은 데이터베이스와 매핑하는 어노테이션이다.
Member입장에선 Many이고, Team입장에선 One이므로, @ManyToOne으로 매핑해야한다.
TEAM_ID(FK)와 매핑해야한다.

@JoinColumn : 조인해야하는 컬럼이 뭐냐

try {
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            member.setTeam(team); //jpa가 알아서 team에사 값을 꺼내서 foreign값을 insert할 때, foreign값으로 사용함

            em.persist(member);

            Member findMember = em.find(Member.class, member.getId());
            Team findTeam = findMember.getTeam();
            System.out.println("findTeam = " + findTeam.getName());

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        }finally {
            em.close();
        }

객체 지향적으로 레퍼렌스를 가지고 온 것을 확인 가능.


insert 두개가 제대로 나감

영속성 컨텍스트 말고 디비에서 가져오는 쿼리를 보고 싶은데?
->flush를 해서 현재 영속성 컨텍스트에 있는걸 db로 쿼리를 다 날려버려서 싱크를 맞춘다음에 em.clear를 하면 영속성 컨텍스트를 초기화 시키게 됨.
그 이후부터는 깔끔한 데서 가져옴.

try {
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            member.setTeam(team); //jpa가 알아서 team에사 값을 꺼내서 foreign값을 insert할 때, foreign값으로 사용함

            em.persist(member);
            
            em.flush();
            em.clear();

            Member findMember = em.find(Member.class, member.getId());
            Team findTeam = findMember.getTeam();
            System.out.println("findTeam = " + findTeam.getName());

            tx.commit();
        }

연관관계 수정

팀 바꾸고 싶을 때!

Team newTeam = em.find(Team.class, 100L);
findMember.setTeam(newTeam);

업데이트 쿼리가 날라감

양방향 연관관계와 연관관계의 주인

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

(mappedBy = "team") : 나는 team에 연결되어 있어. = 나는 team으로 매핑이 되어있는 애야. 나는 team에 의해서 관리가 돼.

여기서 team은 Member.java의 private Team team; 에서의 team이다.

객체는 테이블과 달리 참조를 각각 하나씩 넣어놔야한다.

연관 관계의 주인(Owner)

양방향 매핑 규칙

mappedBy가 적힌 데는 읽기만 된다.

try {
            //저장
            Member member = new Member();
            member.setUsername("member1");
            em.persist(member);

            Team team = new Team();
            team.setName("TeamA");
            team.getMembers().add(member);
            em.persist(team);

            em.flush();
            em.clear();
            
            tx.commit();
        }


외래키 값이 null이 되었다.

이제 연관관계 주인에 값을 넣어보자.

try {
            //저장
            Team team = new Team();
            team.setName("TeamA");
            //team.getMembers().add(member);
            em.persist(team);
            
            Member member = new Member();
            member.setUsername("member1");
            member.setTeam(team);
            em.persist(member);
            
            em.flush();
            em.clear();

            tx.commit();
        }


TEAM_ID에 1이 제대로 들어간 것을 확인할 수 있다.

try {
            //저장
            Team team = new Team();
            team.setName("TeamA");
            //team.getMembers().add(member);
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            member.setTeam(team);
            em.persist(member);

            em.flush();
            em.clear();

            Team findTeam = em.find(Team.class, team.getId());
            List<Member> members = findTeam.getMembers();

            for (Member m : members) {
                System.out.println("m = " + m.getUsername());
                
            }
            tx.commit();
        }  


출력이 m=member1으로 잘나온다.

team.getMembers().add(member); 를 안해줘도 된다 왜일까?


select 쿼리가 두번 나가는데, 첫번째는 Team findTeam = em.find(Team.class, team.getId());에서, 두번째는 List members = findTeam.getMembers();
for (Member m : members) {System.out.println("m = " + m.getUsername());에서 나간다.

where members0_.TEAM_ID=? 쿼리 나갈때, foreign key값으로 나와 연관된 멤버를 가지고 오므로 값을 세팅안해줘도 출력되는 것이다.
이는 em.flush();em.clear(); 해줘서 가능한 것이다.
em.flush();em.clear(); 하면 1차 캐시에 아무것도 없어서 db에서 다시 조회해온다.이 경우에는 db에서 다시 조회 해왔기 때문에 jpa가 foreign key가 있다는 것도 알고, members도 다시 조회해야겠다는 매커니즘이 jpa에서 동작한다.

순수하게 1차캐시에 넣어놓은 상태에서는(em.persist만 해둔 상태) team.getMembers().add(member)없이는 최악의 경우, 버그가 발생할 수 있다.
최악의 상태를 고려해서 양쪽에 값을 세팅해주자.

team.getMembers().add(member); em.flush();em.clear();를 빼면

try {
            //저장
            Team team = new Team();
            team.setName("TeamA");
            //team.getMembers().add(member);
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            member.setTeam(team);
            em.persist(member);

            //team.getMembers().add(member);

            //em.flush();
            //em.clear();

            Team findTeam = em.find(Team.class, team.getId()); //1차 캐시에 그대로 남아있음. team 그자체.
            //team 안에 아무것도 없음. 따라서 아래에서 출력되는게 없음.
            List<Member> members = findTeam.getMembers();

            for (Member m : members) {
                System.out.println("m = " + m.getUsername());
            }
            tx.commit();
        }  


m이 출력되지 않는다.

양쪽에 값을 세팅하는걸 깜빡할 수 있기 때문에 연관관계 편의 메소드를 생성하자.

public void setTeam(Team team) {
        this.team = team;
        team.getMembers().add(this); //나 자신(member)의 인스턴스를 넣어준다
     }  

member.setTeam(team); 에서 자동으로 세팅된다. 따라서 team.getMembers().add(member); 코드는 빼도 된다.
이런 경우 로직이 들어가면, set을 쓰지 않고, 메서드 명을 바꾼다.

public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this); //나 자신의 인스턴스를 넣어준다
     }  

어떤 메소드를 통해 값을 세팅하는건 내 자유이므로,
둘 중 하나 선택하면 된다.
예를 들어 team을 이용해서 세팅할 수 있다.

public void addMember(Member member){
member.setTeam(this);
member.add(member);
} 

lombok에선 toString 쓰지말자.
실무에선 컨트롤러에서 엔티티를 반환하면 안된다. 무한루프에 빠질수 있다.
엔티티는 dto(값만 있는)로 반환하도록.!

양방향 매핑 정리

jpa설계시, 단방향 매핑으로 끝내야한다.

연관관계의 주인을 정하는 기준

연관관계의 주인은 외래 키의 위치를 기준으로 정해야한다.

실전 예제 2 - 연관관계 매핑 시작

회원은 거의 회원만 가지고 있으면 된다.
Member가 Orders를 알 필요는 거의 없다.
Order부터 시작하면 된다.
이번 예제는 예시라서 Member에 orders를 넣었다.

OneToMany 대상은 컬렉션이 되어야!
jpa에선 관례상, 초기 세팅할 때, ArrayList를 사용

Member.java

@Entity
public class Member {
  @Id @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "MEMBER_ID")
  private Long id;
  private String name;
  private String city;
  private String street;
  private String zipcode;

  @OneToMany(mappedBy = "member")
  private List<Order> orders = new ArrayList<>();

Order.java

@Entity
@Table(name = "ORDERS")
public class Order {

  @Id
  @GeneratedValue
  @Column(name = "ORDER_ID")
  private Long id;

  //@Column(name = "MEMBER_ID")
  //private Long memberId;

  @ManyToOne
  @JoinColumn(name = "MEMBER_ID")
  private Member member;

  @OneToMany(mappedBy = "order")
  private List<OrderItem> orderItems = new ArrayList<>();
  private LocalDateTime orderDate;

  @Enumerated(EnumType.STRING)
  private OrderStatus status;

OrderItem.java

@Entity
public class OrderItem {

  @Id
  @GeneratedValue
  @Column(name = "ORDER_ITEM_ID")
  private Long id;

  //@Column(name = "ORDER_ID")
  //private Long orderId;

  @ManyToOne
  @JoinColumn(name = "ORDER_ID")
  private Order order;

  //@Column(name = "ITEM_ID")
  //private Long itemId;

  @ManyToOne
  @JoinColumn(name = "ITEM_ID")
  private Item item;

  private int orderPrice;
  private int count;

0개의 댓글

관련 채용 정보