@Entity @Getter @Setter public class Item { @Id @GeneratedValue @Column(name = "ITEM_ID") private Long id; private String name; private int price; private int stockQuantity; }
- 스프링부트에서는 엔티티(테이블)들을 domain 패키지에 생성한다.
- @Entity - JPA에서 관리한다. 테이블 생성 / 테이블 == 엔티티
- @GeneratedValue - 제약조건을 걸지 않으면, PK 값을 각 DBMS에 맞는 자동증가 컬럼으로 만들어준다.
- @Column(name = "ITEM_ID") - 보통 PK컬럼의 컬럼명은 변수 명이 아니라,
ITEM_ID
처럼 따로 만들어 준다.
@Entity @Getter @Setter public class Member { @Id @GeneratedValue @Column(name = "MEMBER_ID") private Long id; private String name; private String city; private String street; private String zipcode; }
@Entity @Table(name = "ORDERS") @Getter @Setter public class Order { @Id @GeneratedValue @Column(name = "ORDER_ID") private Long id; @Column(name = "MEMBER_ID") private Long memberId; private LocalDateTime orderDate; private String status; }
- Member.java의 컬럼명과 일치, FK 관계를 나타낸다.
@Entity @Setter @Getter public class OrderItem { @Id @GeneratedValue @Column(name = "ORDER_ITEM_ID") private Long id; @Column(name = "ORDER_ID") private Long orderId; @Column(name = "ITEM_ID") private Long itemId; private int orderprice; private int count; }
Order.java 의 컬럼명과 일치, FK관계를 나타낸다.
Item.java 의 컬럼명과 일치, FK관계를 나타낸다.
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 { tx.commit(); } catch (Exception e) { tx.rollback(); }finally { em.close(); emf.close(); } }
클래스에서 화면 우클릭 -> Run As -> Java Application 으로 실행
테이블 생성
- 객체와 테이블 연관관계의 차이를 이해해야 한다.
- 객체의 참조와 테이블의 외래 키 매핑을 해줘야 한다.
연관관계 주인(Owner)
개념 도입테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
객체는 참조를 사용해서 연관된 객체를 찾는다.
- 테이블과 객체 사이에는 이런 큰 간격이 있다.
객체에서는 Team 클래스의 id
와 Member클래스의 teamId
가 연결되어 있는것이다.
Member.java
@Entity @Setter @Getter public class Member { @Id @GeneratedValue @Column(name = "MEMBER_ID") private Long id; @Column(name = "USERNAME") private String name; //기존 컬럼 생성 방법 // @Column(name = "TEAM_ID") // private Long teamid; @ManyToOne @JoinColumn(name = "TEAM_ID") private Team team; }
Team 클래스와 Member클래스 간의 FK 관계를 맺어야한다.
일반적인 변수선언이 아니라, Team클래스를 "TEAM_ID"에 참조시킨다.
@ManyToOne : Team(부모) 클래스는 1개이다. -> Member(자식) 여러명이 Team 한개를 가진다.
@JoinColumn(name = "TEAM_ID") : 관계를 맺을 컬럼을 적어둔다. 여기서는 TEAM_ID와 조인해야한다. 네임값이 없으면 전역변수 명으로 매핑된다.
@Entity @Getter @Setter public class Team { @Id @GeneratedValue @Column(name = "TEAM_ID") private Long id; private String name; }
public class JpaMain { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); Team team = new Team(); team.setName("TeamA"); // -> persist하면 영속상태가 된다. // 영속상태가 될 때, PK의 값이 세팅이 된 후에 영속상태가 된다. // Team을 먼저 생성해서 PK를 생성해야 FK를 생성할 수 있다. em.persist(team); Member member = new Member(); member.setName("member1"); // FK인 Member 에서 PK 객체인 Team을 통째로 가져온다. member.setTeam(team); em.persist(member); //강제 db 쿼리를 보고 싶을 때 em.flush(); em.clear(); //select // find시에 1차캐시에서 가지고 와서 select문이 없다. Member findmember = em.find(Member.class, member.getId()); // Member에서 Team 객체를 통째로 가져온다. Team findTeam = findmember.getTeam(); System.out.println("findTeam : " + findTeam.getName()); tx.commit(); // commit하면 영속성 컨텍스트에 들어가기때문에 여기서 테이블 생성 } catch (Exception e) { tx.rollback(); }finally { em.close(); emf.close(); } } }
테이블
에서는 FK
만 있으면 Member와 Team의 양쪽의 연관관계
를 알 수 있다..객체 참조
와, 외래키
의 차이점
이다.양방향 매핑
이다.
- ✨
테이블 연관관계
- 관계 1개
- Member 테이블 입장에서 Team 테이블 조인 가능
- Team 테이블 입장에서 Member 테이블 조인 가능
- ✨
객체 연관관계
- 관계 2개
- Member객체에서 Team 객체로 연관관계 1개 (단방향)
- Team 객체에서 Member 객체로 연관관계 1개 (단방향)
- 사실은 단방향 연관관계가 2개 있는 것이다.
- ✨
관리의 딜레마
- 둘 중 하나도 외래키를 관리해야 한다.
- Member에서 Team으로 가는 team참조 값과, Team에서 Member로 가는 members 참조값이 있다.
- 두 곳에서 모두 접근할 수 있기 때문에, 어느 엔티티에서 수정을 해줄 것인지 모호
- DB 입장에서는 Member table에 있는 TEAM_ID만 update되면 된다.
- 그렇기 때문에, 객체입장에서
주인(Owner)
개념이 적용된다.
- ✨
연관관계의 주인(Owner) - 양방향 매핑 규칙
- 객체의 두 관계 중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리 (등록, 수정)
- 주인이 아닌쪽은 읽기만 가능
-주인은 mappedBy 속성 사용X
->mappedBy
: 내가 누군가에 의해서 mapping 되었다 라는 뜻
- 주인이 아니면 mappedBy 속성으로 주인 지정
Member(FK) -> Team
: N -> 1
=> @ManyToOne
Team -> Member
: 1 -> N
=> @OneToMany
List
로 받아야한다.@Entity @Getter @Setter public class Team { @Id @GeneratedValue @Column(name = "TEAM_ID") private Long id; private String name; /* team에 의해서 관리가 된다. mappedBy가 적힌 곳은 읽기만 가능하다. 값을 넣어봐야 아무일도 벌어지지 않는다. 대신 조회는 가능 */ @OneToMany(mappedBy = "team") private List<Member> member = new ArrayList<Member>(); public void addMember(Member member) { member.setTeam(this); this.member.add(member); } }
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"); // -> persist하면 영속상태가 된다. // 영속상태가 될 때, PK의 값이 세팅이 된 후에 영속상태가 된다. // Team을 먼저 생성해서 PK를 생성해야 FK를 생성할 수 있다. em.persist(team); Member member = new Member(); member.setName("member1"); // FK인 Member 에서 PK 객체인 Team을 통째로 가져온다. //member.setTeam(team); em.persist(member); //강제 db 쿼리를 보고 싶을 때 em.flush(); // DB에 넣어준다. em.clear(); // 영속성 컨텍스트에 가지고 있던 crud구문을 비워준다. // 양방향 매핑 Member findSideMember = em.find(Member.class, member.getId()); List<Member> members = findmember.getTeam().getMember(); for(Member m : members) { System.out.println("result1 = " + m.getName()); } tx.commit(); // commit하면 영속성 컨텍스트에 들어가기때문에 여기서 테이블 생성 } catch (Exception e) { tx.rollback(); }finally { em.close(); emf.close(); } } }
- Team객체가 먼저 만들어 진다.
- Member객체가 생성될 때, 이미 Team객체는 존재하기 때문에, Member객체에 Team을 참조할 수 있다.
- 영속성 컨텍스트의 Team 객체에는 Member객체가 참조되지 않은 상태이다.
- 영속성 컨텍스트를 비우지 않고, select를 한다면 Team의 Member가 비어있기 때문에,
List<Member> members = findmember.getTeam().getMember();
이 부분에서getTeam().getMember()
getMember()로 값을 가져오지 못한다.- 그렇기 때문에
em.flush();, em.clear();
로 영속성 컨텍스트의 crud를 비워줌으로서, DB를 통해 select를 하며 , 그 때 Team 객체에도 Member객체가 참조될 수 있게 되어 , Member에 접근할 수 있다.
FK를 가진 객체가 주인(Owner)가 된다.
주인이 아닌 객체는 읽기
만 가능하다.
현재 예시에서는 Member가 주인이다.
JpaMain2.java
public class JpaMain2 { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); EntityManager em = emf.createEntityManager(); EntityTransaction tx = em.getTransaction(); tx.begin(); try { //양방향 매핑 시 가장 많이 하는 실수 Member member = new Member(); member.setName("member1"); em.persist(member); Team team = new Team(); team.setName("TeamA"); team.getMember().add(member); em.persist(team); em.flush(); em.clear(); tx.commit(); } catch (Exception e) { tx.rollback(); }finally { em.close(); emf.close(); } } }
```java public class JpaMain2 { 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.setName("member1"); member.setTeam(team); em.persist(member); em.flush(); em.clear(); // 팀으로 멤버를 읽어오는것만 가능하다. Team findTeam = em.find(Team.class, team.getId()); List<Member> members = findTeam.getMember(); for(Member m : members) { System.out.println("m = " + m.getName()); } System.out.println("============================="); tx.commit(); } catch (Exception e) { tx.rollback(); }finally { em.close(); emf.close(); } } }
- 현재는 flush, clear로 영속성 컨텍스트를 비워줬기 때문에, Team에 Member를 참조시키지 않았지만, DB를 통해서 Team을 통해서 Member를 가지고 올 수 있는 것이다.
하지만 DB를 가지 못하게 될 경우, Team에도 Member를 넣어주어야 DB를 가지 않고도 값을 가져올 수 있다.
- 객체 지향적인 입장에서 양쪽에 모두 값을 넣어 주어야 한다.
team.addMember(member)
양방향 매핑시에는 양쪽에 값을 모두 입력해 주어야 한다.
DB를 다시 다녀오지 않고 객체 상태로만 사용 할 수 있어야 한다.
@Entity @Setter @Getter public class Member { @Id @GeneratedValue @Column(name = "MEMBER_ID") private Long id; @Column(name = "USERNAME") private String name; @ManyToOne @JoinColumn(name = "TEAM_ID") @Setter(value = AccessLevel.NONE) //lombok에서 자동 setter 생성을 막아준다. private Team team; // 일반적으로 setter의 형태가 아니면 메서드 이름을 바꿔준다. // 추후 소스코드를 봤을 때 단순 setter의 작업이 아닌 중요한 작업을 진행하는지를 파악할 수 있다. public void changeTeam(Team team) { this.team = team; // this : 나 자신의 인스턴스를 넣어준다. team.getMember().add(this); } }
- lombok의 setter 자동생성을 막아준 후, setter를 만든다.
- JpaMain2.java
member.changeTeam(team) 한 줄로 Team과 Member에 모두 값을 넣었다.
@Entity @Getter @Setter public class Team { @Id @GeneratedValue @Column(name = "TEAM_ID") private Long id; private String name; @OneToMany(mappedBy = "team") private List<Member> member = new ArrayList<Member>(); public void addMember(Member member) { member.setTeam(this); this.member.add(member); } }
- JpaMain2.java
team.addMember(member)
로 값을 모두 넣었다.
@Entity @Getter @Setter public class Item { @Id @GeneratedValue @Column(name = "ITEM_ID") private Long id; @OneToMany(mappedBy = "item") private List<OrderItem> orderItem = new ArrayList<OrderItem>(); public void addOrderItem(OrderItem orderItem) { orderItem.setItem(this); this.orderItem.add(orderItem); } private String name; private int price; private int stockQuantity; }
@Entity @Getter @Setter public class Member { @Id @GeneratedValue @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> order = new ArrayList<Order>(); public void addMember(Order order) { order.setMember(this); this.order.add(order); } }
@Entity @Table(name = "ORDERS") @Getter @Setter public class Order { @Id @GeneratedValue @Column(name = "ORDER_ID") private Long id; @OneToMany(mappedBy = "order") private List<OrderItem> orderItem = new ArrayList<OrderItem>(); public void addOrderItem(OrderItem orderItem) { orderItem.setOrder(this); this.orderItem.add(orderItem); } // @Column(name = "MEMBER_ID") // private Long memberId; @ManyToOne @JoinColumn(name = "MEMBER_ID") private Member member; private LocalDateTime orderDate; private String status; }
@Entity @Setter @Getter public class OrderItem { @Id @GeneratedValue @Column(name = "ORDER_ITEM_ID") private Long id; @ManyToOne @JoinColumn(name = "ORDER_ID") private Order order; // @Column(name = "ORDER_ID") // private Long orderId; // @Column(name = "ITEM_ID") // private Long itemId; @ManyToOne @JoinColumn(name ="ITEM_ID") private Item item; private int orderprice; private int count; }