[Spring Boot] JPA로 엔티티, 테이블 생성하기 / PK, FK 연결하기

seulki·2022년 12월 7일
0

[springboot]

목록 보기
20/27

🎈 JPA 로 테이블 만들기

  • Item.java
@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 처럼 따로 만들어 준다.


  • Member.java
@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;
}


  • Order.java
@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 관계를 나타낸다.


  • OrderItem.java
@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관계를 나타낸다.



🗝️ 엔티티설계와 테이블 설계 비교

  • Java에서의 엔티티 설계


  • DB에서의 테이블 설계


✨ JPA 실행을 위한 기본 셋팅

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 으로 실행

  • 테이블 생성



🎈 연관관계 매핑 -> JPA 로 FK 조건 생성하기 - 단방향

  • 객체와 테이블 연관관계의 차이를 이해해야 한다.
  • 객체의 참조와 테이블의 외래 키 매핑을 해줘야 한다.
  • 연관관계 주인(Owner) 개념 도입
  • 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다.
  • 객체는 참조를 사용해서 연관된 객체를 찾는다.
  • 테이블과 객체 사이에는 이런 큰 간격이 있다.

  • 예제
    -> 회원 / 팀
    • 회원은 하나의 팀에만 소속될 수 있다.
    • 회원과 팀은 다대일 관계이다.

  • 객체에서는 Team 클래스의 idMember클래스의 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와 조인해야한다. 네임값이 없으면 전역변수 명으로 매핑된다.

  • Team.java
@Entity
@Getter @Setter
public class Team {

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

}


  • JpaMain.java
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의 양쪽의 연관관계를 알 수 있다..
  • 하지만, 위의 Member와 Team에서는 Member는 Team을 가져
    Team에 접근할 수 있지만, Team은 Member에 접근할 수 없다.
  • 이것이 객체 참조와, 외래키차이점이다.
  • 객체에서도 RDBMS와 같이 양방향으로서 접근이 가능하도록 하는 것이
    양방향 매핑이다.

  • 테이블 연관관계
    - 관계 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
  • Member입장에서 Team은 하나지만, Team입장에서는 Member가 여러 개 이기 때문에, Team(부모)에서 Member(자식,FK)를 참조할 때는 List로 받아야한다.

  • Team.java
@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);
	}

}



  • JpaMain.java
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();
		}
	}
}
  • Team 객체는 주인(Owner)이 아니기 때문에, Member객체를 add(insert)할 수 없다.


  • Team을 통해서는 Member를 읽어오는 것 만 가능하고, Owner가 insert를 해야한다.
```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를 다시 다녀오지 않고 객체 상태로만 사용 할 수 있어야 한다.



🎈 양방향 연관관계 주의 - 연관관계 메서드

  • 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
  • 연관관계 편의 메서드를 생성하자
  • 위와 같이 따로 Team에 add하는 것이 아닌, 생성자에서 바로 값을 할당하자.
  • Member 클래스에서도 가능하고, Team 클래스에서도 가능하다.
  • 양방향 매핑시에 무한 루프를 조심하자
    -> toString(), lombok lib 조심할 것

  • Member.java 생성자에서 값 넣기
@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에 모두 값을 넣었다.


  • Team.java 값 넣기
@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) 로 값을 모두 넣었다.


📝 양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료
  • 양방향 매핑은 반대 방향으로 조회기능이 추가 된 것 뿐
  • 양방향 사용 이유 : JPQL에서 역방향으로 탐색할 일이 많음
  • 단방향 매핑을 잘 하고 양방향은 필요할때 추가해도 됨(테이블에 영향을 주지 않음)
  • 결론 : 객체입장에서 양방향 매핑은 필수는 아님, 필요 시에 그때 생성해도 늦지 않다.


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

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안됨
  • 연관관계의 주인은 외래 키의 위치를 기준으로 정해야 함.


🎈Item, Member, Order, OrderItem 클래스로 양방향 매핑하기

  • Item.java
@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;
}

  • Member.java
@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);
	}
}

  • Order.java
@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;
}

  • OrderItem.java
    @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;
    }  
profile
웹 개발자 공부 중

0개의 댓글