Spring 숙련 230303 #2 Entity/연관관계/프록시/영속성전이

김춘복·2023년 3월 3일
0

Spring 공부

목록 보기
8/14

Entity 매핑 (심화)

기본 Entity 관련 어노테이션

@Entity 
@Table (name="USER") 
public class Member { 
	
	@Id 
	@Column (name = "user_id") 
	private String id; 
    
	private String username; 
	private Integer age; 

	@Enumerated (EnumType. STRING) 
	private RoleType userRole;

//	@Enumerated (EnumType. ORDINAL) 
//	private RoleType userRole;

	@Temporal (TemporalType. TIMESTAMP) 
	private Date createdDate;

	@Temporal (TemporalType. TIMESTAMP)  
	private Date modifiedDate;
 
}
  • @Entity
    @Entity가 붙으면 JPA가 관리한다. 기본생성자가 필수.
    생성자가 하나도 없으면 자바가 만들어주지만 파라미터가 있는 생성자가 있다면 기본생성자가 없기 때문에 하나를 꼭 만들어야한다. @NoArgsConstructor를 쓰면 된다.
    final 클래스, enum, interface 등에는 쓸 수 없다. 저장할 필드라면 final을 사용하면 안된다.

  • @Table
    Entity와 매핑할 테이블의 이름. 생략시 매핑한 Entity 이름을 테이블의 이름으로 사용.

  • @Column
    객체의 필드를 테이블 컬럼에 매핑. 생략가능. name, nullable, unique 말고는 주로 생략.

  • @Enumerated
    Java의 Enum을 테이블에서 사용한다고 보면 좋다. 속성으로는 EnumType. STRING과 ORDINAL.
    String인경우 해당 문자열 그대로 저장해서 비용은 많이 들지만 나중에 Enum이 변경되어도 위험할 일이 없어 일반적으로는 String을 사용. ORDINAL은 순서로 저장.

  • @Temporal
    날짜타입(java.util.Date, java.util.Calendar)을 매핑할 때 사용.
    java8이후 LocalDate, LocalDateTime을 쓸때는 생략 가능.


연관관계 관련 심화

참고사이트 : JPA 연관관계 한번에 정리

중요 요약

DB테이블은 외래키 하나로 양쪽 테이블 조인 가능. 단/양방향 나눌 필요없음
객체는 참조용 필드가 있는 객체만 다른 객체를 참조하는 것이 가능.
두 객체 사이에 하나의 객체만 참조용 필드를 갖고 참조하면 단방향 관계,
두 객체 모두가 각각 참조용 필드를 갖고 서로 참조하면 양방향 관계
양방향 관계일때는 연관관계의 주인을 설정해야 하는데 무조건
외래키가 있는 곳이 주인이고 DB에서는 'Many' 쪽이 외래키를 갖는다.
주인이 아닌 쪽(one)은 mappedby로 주인을 지정해줘야 한다.
주인이 아닌쪽은 읽기만 가능하고 연관관계의 주인 쪽에서 쓰기/수정을 해야한다.

객체와 테이블의 차이

객체에선 칼럼으로 list를 쓸 수 있지만 DB에서는 하나의 row에 하나의 칼럼이 여러 값을 넣는건 불가능해 list를 쓸 수 없다. 따라서 객체 one쪽에서 list를 가지고 있는 것은 물리적으로 DB에서는 불가능한 것이므로 객체에서 readonly로만 쓸 수 있다. 그래서 Many쪽이 항상 외래키를 가지고 연관관계의 주인인 것이다!

추가내용

  • @JoinColumn
    외래 키 매핑 시 사용. @ManyToOne 애노테이션과 주로 함께 쓰인다.
    name은 외래키의 컬럼 명을 지정해주는 것이고, 만약 일대다에서 이걸 써준다면 다 쪽에서 생기는 컬럼의 명칭을 지정해주는 것이다.(그래서 snake_case로 쓴다.), @를 생략해도 외래키 생성(생략시 기본전략)

  • mappedBy = " " : 이 테이블은 연관관계의 주인 테이블의 “ ” 필드에 해당한다!
    "여기" 사이에 들어가는건 camelCase로 객체의 필드 변수명을 써주면 된다.
    테이블은 FK하나로 두 테이블의 연관관계를 관리하지만, 객체는 양방향으로 사용하려할 때 각 객체가 서로를 참조한다.(참조가 2번). 그래서 JPA가 두 객체중 어떤 객체로 외래키를 관리해야할지 지정해주기 위해 FK를 관리하는 객체를 연관관계의 주인이라 한다.
    주인이 아닌 객체만 mappedBy를 쓴다!

연관관계의 종류

  • @OneToOne : 1대1. 굳이 2테이블로 나눠야 하는건지 고민해봐야. 같은 테이블에서 다뤄도 될 데이터일 가능성이 있다.

  • @OneToMany : 1대N. 속도를 위해 기본적으로 FetchType=Lazy. 지연로딩. mappedBy

  • @ManyToOne : N대1. 기본적으로 Fetchtype=Eager지만 Lazy를 기본으로 하는걸 추천.

  • @ManyToMany : N대N. 다대다 설정이면 중간테이블 JointTable이 자동으로 생성. 중간 매핑 테이블은 JPA상에서 숨겨져 Entity 정의 없이 관리된다. 매핑 테이블 관리가 안되서 실무에서는 X.

  • @ManyToMany 실무에서 해결 방식 : 아래처럼 중간 테이블을 직접 만들어서 사용
    TableA(@OneToMany) > MappingTable(@ManyToOne, @ManyToOne) > TableB(@OneToMany)

단방향 연관관계

@Entity
@Getter
@Setter
public class Member {
	@Id
	@Column(name = "member_id")
	private String id;
	private String username;
	
	@ManyToOne	// 여러명의 Member(현재 클래스)가 하나의 Team으로(join된 클래스) 되므로 ManyToOne
	@JoinColumn(name="team_id")
	private Team team;

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

@Entity
@Getter
@Setter
public class Team {
	@Id
	@Column (name = "TEAM_ID")
	private String id;
	
	private String name;
}
  • 위의 코드에서 여러명의 Member가 하나의 Team으로 들어가므로 @ManyToOne관계를 썼다.

  • @ManyToOne
    다대일(N:1)관계. 속성으로 Optional, fetch, cascade. Optional은 false로 설정하면 항상 연관된 Entity가 있어야 생성할 수 있다는 뜻.

  • @JoinColumn
    외래키 매핑할 때 사용. 실제 DB의 객체 필드에는 해당 객체 테이블의 외래키가 들어간다.

양방향 연관관계

public class Orders {	// orders가 연관관계의 주인
    @ManyToOne		// 여러 orders를 멤버 1명이. 
    @JoinColumn(name = "member_id")
    private Member member;
--------------------------------------------------------------------------
public class Member { 		// 멤버 1명이 여러 orders
@OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
    private List<Orders> orders = new ArrayList<>();
  • Many쪽은 단방향 연관관계와 차이가 없다.
    기본적으로 DB의 외래키는 양방향에서 조회가 가능해서 DB상에서도 차이가 없다.

  • many의 반대쪽은 단방향에는 없었던 @OneToMany가 있다.

  • 객체에는 사실 양방향 연관관계라는 것이 없다. 서로 다른 단방향으로 조회하는 로직 2개를 잘 묵어서 양방향인 것 처럼 보이게 한 것 뿐. 하지만 DB테이블은 외래키 하나로 양쪽이 서로 조인할 수 있다.

  • 외래키는 연관관계가 있는 두 테이블 중에서 하나의 테이블에만 있으면 충분하다.
    두 객체 연관관계중 하나를 정해서 테이블의 외래키를 관리해야 하는데 이것을 연관관계의 주인이라 한다. 연관관계의 주인만이 DB 연관관계와 매핑되고 외래키를 관리(등록, 수정, 삭제)할 수 있다. 주인이 아닌쪽은 읽기만 할 수 있다.

  • 주인은 mappedBy속성을 사용하지 않는다. 주인이 아니면 mappedBy로 주인을 지정해야한다.
    DB 테이블의 다대일, 일대다 관계에서는 항상 쪽이 외래키를 가지고 주인이 된다.
    다라서 @ManyToOne에는 mappedBy가 없다.

  • mappedBy = "반대편 매핑(order)의 필드 이름(member)"

양방향 연관관계의 주의점

  • 연관관계의 주인에는 값을 입력하지 않고 주인이 아닌 곳에만 값을 입력하면 문제가 발생
    DB에 외래키 값이 정상적으로 저장되지 않으면 이것부터 의심해봐야.

  • 해결책 : 객체 관점에서 양쪽 방향에 모두 값을 입력해주는 것이 가장 안전.
    하지만 매번 그러면 깜빡할 수 있으므로

private Order order;
  public void setMember(Member member) {
    this.member = member;
    member.getOrders().add(this);	//this는 order
  }
  ...
}

order안에서 member를 넣어줄때 그때 member의 order의 값도 넣어준다.


프록시

  • Entity를 조회할 때 연관관계의 Entity들이 항상 사용되는 것은 아니다. JPA는 Entity가 실제 사용될 때 까지 DB 조회를 지연하는 방법을 제공하는데 이를 지연로딩이라 한다. 그런데 지연로딩을 사용하려면 실제 Entity 객체 대상에 DB조회를 지연할 수 있는 가짜 객체가 필요하다.
    이 가짜 객체를 프록시 객체라고 한다.

  • 즉시로딩 : Entity를 조회할 때 연관관계의 Entity도 함께 조회. join해서 다 긁어옴.
    @ManyToOne(fetch = FetchType.EAGER)

  • 지연로딩 : 연관된 Entity를 실제로 사용할때 조회 한다. 실제로 쓸 때 별도의 쿼리를 보냄.
    @ManyToOne(fetch = FetchType.LAZY)

  • 기본값 : @ManyToOne, @OneToOne은 즉시로딩. @OneToMany, @ManyToMany은 지연로딩.
    일반적으로는 다 지연로딩을 쓰긴 함.


영속성 전이

  • 특정 Entity를 영속상태로 만들 때 연관관계의 Entity도 함께 영속상태로 만들고 싶으면
    영속성 전이기능을 사용하면 된다.

  • JPA는 cascade 옵션으로 영속성 전이를 제공한다. 아래는 사용 예시

@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private List<Address> addresses;
profile
Backend Dev / Data Engineer

0개의 댓글