JPA 스터디, #2

박주진·2021년 9월 27일
0

JPA 스터디

목록 보기
2/3

아래 내용은 김영한 님의 자바 ORM 표준 JPA 프로그래밍의 내용에 기반하여 정리한 글입니다.

엔티티 매핑

매핑 정보는 XML/어노테이션 중에 선택 가능하다. 아래는 좀더 쉽고 직관적인 어노테이션 매핑기반으로 설명하겠다.

@Entity

  • 테이블과 매핑할 클래스에 붙이는 필수 어노테이션
  • 기본 생성자 필수(public or protected 생성자)
  • final 클래스, enum, interface, inner 클래스에 사용 불가
  • 저장할 필드에 final 사용불가
@Entity
public class Member {
...
}

@Table

  • 엔티티와 매핑할 테이블을 지정하는 어노테이션
  • 생략 가능 (생략시 엔티티 이름으로 사용)
  • name, catalog, schema, uniqueConstraints(ddl용) 와 같은 속성이 있다.
@Entity
@Table(name="MEMBER")
public class Member {
...
}

데이터베이스 스키마 자동 생성

  • 매핑정보와 데이터베이스 방언을 사용해 데이터베이스 스키마를 자동 생성하는 기능 지원
  • hibernate.hbm2ddl.auto 값은
    • create (drop 후 create)
    • create-drop (위에 create + 애플리케이션 종료시 drop)
    • update (테이블과 매핑정보를 비교해 변경사항만 수정)
    • validate (테이블과 매핑정보를 비교해 차이가 있으면 경고후 애플리케이션 실행 종료)
    • none (관례상 쓰는 값으로 실제로는 존재하지 않는 값이다. 즉 자동 생성 기능 사용 안하겠다는 의미)
  • JPA 공식 스펙은 update,validate를 지원하지 않음
  • 권장하는 방법
    • 로컬 개발환경 자유
    • 공용 테스트 서버 update or validate (가급적 쓰지말자)
    • 스테이징 및 운영 validate or none (가급적 쓰지말자)
    • 그래도 사고를 막기위해서는 웹 어플리케이션의 디비 접근 계정은 ddl를 못하게 막아야 한다.
  • hibernate.ejb.naming_strategy 속성 값을 org.hibernate.cfg.ImprovedNamingStrategy로 설정하면 @Column name속성 생략시 자바의 카멜 표기법을 테이블의 언더스코어 표기법으로 자동 매핑해준다.

DDL 생성 기능

  • @Column의 nullable,length,unique 등
  • @Table의 uniqueConstraints 속성
  • 위에 속성들은 DDL자동 생성시에만 영향을 주는것들 이다.
  • DDL 자동생성을 사용하지 않는다면 굳이 필요가 없으나 개발자가 코드만 보고 제약조건을 파악할 수 있다는 장점은 있다.

필드 매핑 어노테이션 간단한 정리

  • @Column (기본 필드 매핑)
  • @Temporal (날짜 매핑)
    • Date객체를 매핑시에만 필요 요즘은 LocalDateTime, LocaleDate를 사용하기 때문에 필요없음
  • @Enumerated (Enum 매핑)
    • value 속성중 ORDINAL은 enum 순서로(1,2..) 저장 됨으로 이름을 저장하기 위해서는 STRING을 사용!
  • @Lob
    • 큰문자열을 위해 사용
    • 문자면 CLOB, 나머지는 BLOB으로 자동 매핑
  • @Transient (필드 매핑 무시)
  • @Access
    • AccessType.FIELD (테이블과 객체 매핑시 필드값 기준)
    • AccessType.PROPERTY (테이블과 객체 값 매핑시 메서드값 기준 ex)getter setter )
    • @Access를 설정하지 않으면 @Id가 필드에 있냐 메서드에 있냐를 기준으로 삼어 적용됨

기본 키 매핑

  • 직접할당 (@Id만 사용)
  • 자동 생성 (@Id & @GeneratedValue )
    • IDENTITY
      • 데이터베이스에 엔티티를 저장해서 식별자 값 획득
      • 기본 키 생성을 데이터베이스에 위임하는 전략으로 주로 MySQL,PostgreSQL, SQL Server, DB2 사용
      • 엔티티를 저장해야 아이디를 가져올 수 있기 때문에 쓰기 지연이 작동하지 않고 em.persist()를 호출하는 즉시 INSERT SQL이 데이터베이스에 전달된다. (사실상 쓰기지연이 안된다고 성능에 크게 영향을 주지 않는다)
    • SEQUENCE
      • 데이터베이스 시퀀스에서 식별자 값 획득
      • 시퀀스를 지원하는 오라클, PostgreSQL, DB2, H2 사용가능
      • SequenceGenerator.allocationSize로 동시성 이슈없이 시퀀스 조회 작업을 줄여 성능을 최적화 가능 (50-100 권장 너무 많으면 문제는 없지만 웹서버 다운시 중간에 시퀀스 구멍이 생길수 있음)
    • TABLE
      • 시퀀스 생성용 테이블에서 식별자 값 획득
      • 테이블로 시퀀스 기능을 흉내낸 것으로 시퀀스 대신에 테이블을 사용한다는 것만 제외하면 SEQUENCE 전략과 내부 동작방식이 같다.
      • @TableGenerator.allocationSize로 SequenceGenerator.allocationSize와 동일하게 성능 최적화가 가능하다.
    • AUTO
      • 데이터베이스 방언에 따라 자동 선택
      • Mysql-IDENTIY, oracle-SEQUENCE

권장하는 식별자 전략 선택

  • 3가지(not null, 유일, 변경 x)조건을 만족하는 대리키 사용 (auto increate, sequnce, uuid 등)
  • 주민등록과 같은 자연키 즉 비지니스 적으로 의미있는 키는 변경에 여지가 있기 때문에 피하자.
  • 기본키의 형 타입은 Long을 권장!
    • 기본형(int, long)은 0이라는 값이 있어 피하자
    • Integer의 숫자 범위를 기본키의 값이 넘어 간다면 중간에 바꿀때 비용이 발생하기 때문에 피하자 (바꾸는 비용이 Long을 사용함으로써 낭비되는 공간의 비용보다 비싸다)

연관관계 매핑 기초

객체와 연관관계와 테이블 연관관계의 차이

  • 객체는 참조로 연관관계를 맺고 단방향이다. 양향방으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
    예) A -> B (a.getB())
  • 테이블은 외래키로 연관관계를 맺고 양방향이다.
    예) A JOIN B 가 가능하면 반대로 B JOIN A도 가능

단방향 연관관계

매핑예시

@Entity
public class Member {

  @Id
  @Column(name = "MEMBER_ID")
  private String id;
  private String username;
  
  //연관관계 매핑. @OneToManY
  @ManyToOne
  @JoinColumn(name="TEAM_ID")
  private Team team;
  
  //연관관계 설정
  public void setTeam(Team team) {
  this.team = team;
  }
  
  //Getter, Setter ...
}

사용법

  • 저장 (이미 영속상태인 엔티티만 연관관계로 저장 가능하다.)
Team team1 = new Team("team1", "팀1");
entityManger.persist(team1);

Member member1 = new Member("member1", "회원1");
member1.setTeam(team1); 
entityManger.persist(member1);
  • 조회
    • 객체 그래프 탐색
    Member member = entityManger.find(Member.class, "member1");
    member.getTeam();
    • 객체지향 쿼리 (JPQL) 사용
    List<Member> members = entityManger.createQuery("select m from Member m join m.team t, Member.class)
    members.get(0).getTeam();
  • 수정 (자연스럽게 변경하면 트랜잭션 커밋시 변경사항이 반영됨)
  • 연관관계 제거 (연관관계를 null로 설정하면 외래키에 null이 들어감)
  • 연관된 엔티티 삭제 (외래키 제약조건이 있을 경우 연관관계를 먼저 제거한 후에 연관된 엔티티를 삭제해야함)
    member.setTeam(null)
    entityManger.remove(team);

양방향 연관관계

매핑예시

@Entity
public class Member {

  @Id
  @Column(name = "MEMBER_ID")
  private String id;
  private String username;
  
  //연관관계 매핑. @OneToManY
  @ManyToOne
  @JoinColumn(name="TEAM_ID")
  private Team team;
  
  //연관관계 설정
  public void setTeam(Team team) {
  this.team = team;
  }
  
  //Getter, Setter ...
}

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


  @OneToMany(mappedBy = "team")
  private List<Member> members = new ArrayList<Member>();
  //Getter, Setter ...
}

연관관계 주인 (mappedBy 속성)

  • 객체를 양방향 연관관계를 설정하면 외래키는 하나인데 객체 참조가 둘로 늘어난다. 그렇게 되면 두 객체중 하나가 테이블의 외래키를 관리해야 하는데 이것을 연관관계 주인이라고 한다.
  • 주인이 아닌쪽에 mappedBy 속성을 설정하면 된다.
  • 연관관계 주인은 비지니스 중요도가 아닌 외래키가 있는곳으로 설정해야 한다.

양방향 연관관계 설정시 주의점

  • 연관관계 주인쪽에 값을 저장해야지만 데이터베이스에 반영된다.
  • 객체관점에서는 연관관계 주인은 물론 양쪽뱡향 모두에 값을 추가,수정,삭제 해주는 편이 안전하다.
  • 연관관계 편의 메소드를 활용해 메소드 하나로 양방향 관계를 모두 설정하도록 하자.
public void setTeam(Team team) {
if (this.team != null) {
this.team.getMembers().remove(this);
}
this.team = team;
team.getMembers().add(this);
}
  • 서로가 서로를 호출하여 생기는 무한 루프를 조심하자.

다양한 연관관계 매핑

다대일

  • 단방향 양방향 모두 연관관계 주인이 다쪽이다.(외래키가 있는 쪽)

일대다

  • 단방향
    • 매핑한 객체가 관리하는 외래 키가 다른테이블에 있어 성능에 문제가 있고 관리도 부담스럽다.
    • 항상 @JoinColumn을 명시해야 한다. 그렇지 않으면 조인 테이블 전략을 사용해서 매핑된다.
    • 이런 상황에서는 객체지향적으로 조금 손해를 보더라도 다대일 양방향 매핑을 사용하는 편이 좋다.
  • 양방향
    • 일대다 단뱡향 매핑과 반대편에 다대일 단방향 매핑(insertable = false, updatable =false)를 추가해 구현할 수 있다.
    • 일대다 단방향의 단점이 그대로 존재함으로 다대일 양방향을 사용하자.

일대일

자신의 외래키는 무조건 자신이 관리해야 하고 양방향시에는 반대쪽에 mapped by를 사용

  • 주 테이블에 외래키

    • 다대일 단방향, 양방향과 똑같고 @OneToOne 에노테이션만 붙혀주면된다.
    • 장점
      • JPA 매핑이 편리해 객체지향 개발자가 선호
      • 주테이블만 조회해도 대상 테이블에 데이터가 있는지 확인가능
    • 단점
      • 연관관계가 없을때를 대비해 외래키에 널을 허용해야 한다.
  • 대상 테이블에 외래키

    • 장점
      • DBA선호
      • 일대다 관계로 (대상 테이블이 다) 변경되어도 테이블 구조는 유지
    • 단점
      • 단방향 불가 양방향만 가능
      • 프록시의 한계로 연관관계가 무조건 즉시로딩됨

개인적으로 관계가 변경되는 경우와 같이 너무 먼미래는 생각하지 않고 주로 주 테이블에 외래키 구조 선택

다대다

  • @ManyToMany로 설정하면 자동으로 중간에 연결 테이블를 가지고 매핑한다.
  • 실무에서는 연결테이블에는 보통 다양한 컬럼들이 추가 되기 때문에 @ManyToMany보다는 연결테이블을 생성 후 일대다, 다대일 관계로 풀어낸다.
  • 연결된 테이블과의 관계는 식별 관계(받아온 식별자를 기본 키 + 외래 키) 또는 비식별 관계(받아온 식별자는 외래 키로만 사용)로 풀어낼수 있다.

비식별 관계를 가져가도록 추천한다. 그 이유는 복합키를 사용하지 않아 편리하게 ORM 매핑이 가능하고 id가 다른테이블에 종속되지 않아 시스템을 유연하게 유지할 수 있기 때문이다.

0개의 댓글