본문 바로가기

Spring/JPA

JPA Basic - 엔티티 매핑

1. 객체와 테이블 매핑: @Entity, @Table

  ◍  @Entity

      ○ JPA를 사용해서 테이블과 매핑할 클래스는 @Entity 필수 

      ○ 주의

           ◼  기본생성자 필수(파라미터가 없는 public 또는 protected 생성자)

           ◼  final 클래스, enum, interface, innner 클래스에는 사용X

           ◼  저장할 필드에 final 사용 X

        속성: name 

           ◼    기본값: 클래스의 이름을 그대로 사용.

           ◼     같은 클래스의 이름이 없으면 가급적 기본값 사용.

 

  ◍  @Table

       엔티티와 매핑할 테이블 지정

           ◼ name: 매핑할 테이블 이름(엔티티 이름을 사용)

           ◼ catalog: 데이터베이스 catalog 매핑

           ◼ schema: 데이터베이스 schema 매핑

           ◼ uniqueConstraints(DDL): DDL 생성시 유니크 제약조건 생성

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

       DDL을 애플리케이션 실행 시점에 자동 생성

       테이블 중심 -> 객체 중심

       데이터베이스 방언을 활용해서 데이터 베이스에 맞는 적절한 DDL 생성 -> 개발 장비에서만 사용

       생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용

      ○ 옵션 

           ◼ create: 기존 테이블 삭제후 다시 생성(drop + create)

           ◼  create-drop: create와 같으나 종료시점에 테이블 drop

           ◼  update: 변경분만 반영(운영 DB에는 사용하면 안됨.)

           ◼  validate: 엔티티와 테이블이 정상 매핑되었는지만 확인

           ◼  none: 사용하지 않음.

       주의할 점.

           ◼  운영장비에는 절대 create, create-drop,update 안 됨

           ◼  개발 초기단계는 create 또는 update

           ◼  테스트 서버는 update 또는 validate

           ◼  스테이징과 운영 서버는 validate 또는 none

  ◍  DDL 생성기능

       DDL 생성 기능은 DDL을 자동 생성 할때만 사용되고 JPA실행 로직에는 영향을 주지 않는다. 

           ◼  예시1:  @Column(nullable=false, length =10)

           ◼  예시2: Table(uniqueConstraints = {@UniqueConstraint(name="NAME_AGE_UNIQUE",

            columnNames={"NAME", "AGE"} )}}

2. 필드와 컬럼 매핑

  ◍  종류

        @Column: 컬럼매핑

           ◼  name: 필드와 매핑할 테이블의 컬럼 이름 (기본: 객체의 필드이름)

           ◼  insertable, updatable: 등록,변경 가능여부( 기본: TRUE)

           ◼  nullable(DDL): null값의 허용 여부. false로 설정하면 DDL 생성시 not null 제약 조건

           ◼  unique(DDL): @Table의 uniqueConstraints와 같지만 한 컬럼에 간단히 유니크 제약 조건 걸 때 사용

           ◼  columnDefinition: 데이터 베이스 컬럼 정보. ex) varchar(100) default 'EMPTY' 

                 (기본: 필드의 자바 타입과 방언 정보를 사용)

           ◼  length(DDL): 문자의 길이 제약조건, String 타입에만 사용(기본: 255)

           ◼  precision, scale(DDL): BigDecimal 타입에서 사용(BigInteger도).

              ◻ precision 소수점을 포함한 전체 자릿수를, scale은 소수의 자릿수다.

              ◻  참고로 double, float타입에는 적요ㅕㅇ 되지 않는다. 아주 큰 숫자나 정밀한 소수를 다루어야 할 떄만 사용. 

              ◻  (기본 precision = 19, scale = 2)

        @Temporal: 날짜 타입 매핑

           ◼  참고: LocalDate, LocalDateTime을 사용할 때는 생략가능

           ◼  날짜타입(java.util.Date, java.util.Calendar)을 매핑 할 때 사용

           ◼  value

              ◻  TemporalType.DATE: 날짜, 데이터베이스 date타입과 매핑(예: 2013-10-11)

              ◻  TemporalType.TIME: 시간, 데이터베이스 time 타입과 매핑(예: 11:11:11)

              ◻  TemporalType.TIMESTAMP: 날짜와 시간, 데이터베이스 timestamp 타입과 매핑(예:2013-10-11 11:11:11)

        @Enumerated: enum타입 매핑

           ◼  주의! ORDINAL사용하지 않기

           ◼  value: EnumType.ORDIAL: enum순서를 데이터베이스 저장(기본)/ EnumType.String: enum이름 데이터 베이스 저장.

        @lob: BLOB, CLOB 매핑

           ◼  @Lob 에는 지정할 수 있는 속성이 없다.

           ◼  매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑.

              ◻  CLOB: String, char[], java.sql.CLOB

              ◻  BLOB: byte[], java.sql.BLOB

        @Transient 특정 필드를 컬럼에 매핑하지 않음(매핑 무시)

           ◼  필드 매핑 안함

           ◼  데이터 베이스에 저장 및 조회 안함

           ◼  메모리상에서만 임시로 어떤 값 보관하고 싶을 때 사용.

 

3. 기본키 매핑

  ◍  @Id

        직접할당: @Id 만 사요ㅕㅇ

 

  ◍  @GenerateValue (자동생성)

        IDENTITY: 데이터베이스에 위임, MYSQL

           ◼  기본키 생성을 데이터 베이스에 위임

           ◼  주로 MySQL, PostgreSQL, SQL Server, DB2에서 사용

           ◼  문제점

              ◻  JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행

              ◻  AUTO_INCREMENT는 데이터베이스에 INSERT SQL을 실행한 이후에 ID값을 알 수 있음.

              ◻  IDENTITY 전략은 em.persit() 시점에 즉시 INSERT SQL 실행하고 DB에서 식별자를 조회.

              ◻  ==> 트랜젝션에서 이런 경우가 많아지면 성능 이슈가 있을 수 있으나 insert쿼리가 여러번 날라간다고 큰 성능 이슈 X

@Entity
public class Member{
	@Id
    @GeneratedValue(strategy=GenerationType.IDENNTITY)
    private Long id;

 

        SEQUENCE: 데이터베이스 시퀀스 오브젝트 사용, ORACLE

           ◼  @SequenceGenerator 필요.

              ◻  name: 식별자 생성기 이름(기본: 필수)

              ◻  sequenceName: 데이터베이스에 등록되어 있는 시쿼스 이름(기본: hibernate_sequence)

              ◻  initialValue: DDL 생성시에만 사용됨. 시퀀스 DDL을 생성할 때 처음 1 시작하는 수를 지정. (기본:1)

              ◻  allocationSize: 시퀀스 한 번 호출에 증가하는 수(기본:50)

              (성능 최적화에 사용됨. 데이터 베이스 시퀀스 값이 하나 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정해야함.)

              ◻  catalog, schema: 데이터베이스 catalog, schema 이름

           ◼  데이터 베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브잭트

           ◼  오라클, PostgreSQL, DB2, H12 에서 사용

           ◼  성능 최적화 위해 em.persist()때 call next value for 로 50까지 가져온후 (첫번째:1, 두번째:50) 메모리에서 증가 시킴. 

@Entity
@SequenceGenerator(
	name ="MEMBER_SEQ_GENERATOR"
    sequenceName="MEMBER_SEQ"
    initialValue = 1, allocationSize = 1)
public class Member{

	@Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
    				generator = "MEMBER_SEQ_GENERATOR")
	private Long id;

        TABLE:키 생성용 테이블 사용, 모든 DB에서 사용

           ◼ @TableGenerator 필요

              ◻  name: 식별자 생성기 이름(기본: 필수)

              ◻  table: 키생성 테이블명(기본: hibernate_sequcnees)

              ◻  pkColumnName: 시퀀스 컬럼명(기본: sequence_name)

              ◻  pkColumnValue: 키로 사용할 값 이름(기본: 엔티티 이름)

              ◻  valueColumnNa: 시쿼스 값 컬럼명(기본: next_val)

              ◻  initialValue: 초기 값, 마지막으로 생성된 값이 기준 (기본: 0)

              ◻  allocationSize: 시퀀스 한번에 호출에 증갓하는 수(성능 최적화에 사용(기본: 50)

              ◻  catalog,schema: catalog, schema 이름

           ◼  키 생성 전용 테이블을 하나 만들어서 데이터베이스 시퀀스를 흉내내는 전략

           ◼  장점: 모든 데이터베이스에 적용가능

           ◼  단점: 성능

create table MY_SEQUENCE(
	sequence_name varchar(255) not null, next_val bigint, primary key( sequence_name )
    
@Entity 
@TableGenerator(
	name="MEMBER_SEQ_GENERATOR",
    table = "MY_SEQUENCES"
    pkColumnValue="MEMBER_SEQ", allocationSize = 1)
public class Member {

	@Id
    @Generatedvalue(strategy = GenerationType.TABLE,
    				generator="MEMBER_SEQ_GENERATOR")
	private Long id;

        AUTO: 방언에 따라 자동 지정 (기본값)

 

  ◍ 권장하는 식별자 전략

        기본키 제약조건: null 아님, 유일, 변하면 안됨

        미래까지 이 조건을 만족하는 자연키 찾기 어렵다면 대리키 사용.

           ◼  예: 주민등록번호도 기본 키로 적절치 않음.

           ◼  권장: Long형 + 대체키 + 키 생성전략 사용.

 

**스프링부트 사용시 변수이름이 orderDate(카멜케이스) 라도 order_date(언더스코어)로 바꾸어 줌

 

4. 연관관계 매핑 : @ManyToOne, @JoinColumn

  ◍  단방향 연관관계

        일대다라면 @ManyToOne을  '다'쪽에 붙이면 됨 / commit이나 flush하지 않으면 영속성 컨텍스트에서 가져옴.

 

  ◍  양방향 연관관계

        DB와 객체의 차이

           ◼  DB는 외래키로 FK의 정보를 가져올수 있지만 객체는 일대다라면 일쪽에 @OneToMany(mappedBy="객체이름")을

                List와함께 붙여줘야함.

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

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

              ◻  객체: 회원 -> 팀 단방향 1개 / 팀 -> 회원 연관관계  1개(단방향)

              ◻  테이븛: 회원 <-> 팀 연관관계 1개 (양방향)

           ◼  mappedBy 이유

              ◻  둘 중 하나로 외래 키를 관리해야 한다. = 연관관계 주인

              ◻  연관관계 주인만이 외래 키를 관리(등록, 수정)

              ◻  주인이 아닌 쪽은 읽기만 가능. 주인이 아니면 mappedBy 속성으로 주인 지정.

              ◻  어디를 주인으로 해야 하는지: 외래키가 있는 쪽을 주인으로 관리하라. 

                 -> 직관적인 설계(하나의 엔티티에 접근해서 바꿀때 업데이트 됨)/ 성능이슈. 

      ○ 양방향 매핑시 가장 많이 하는 실수(연관관게의 주인에 값 미입력.)

           ◼  순수 객체 상태를 고려해서 항상 양쪽에 값을 설정.

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

Member member = new Member9);
member.setName("member1");
//2. member.setTeam(team) 필요

//1. 역방향만 관계 설정.
team.getMembers().add(member);

em.getpersist(member);

//3. 그러나 add없이 setTeam만 하는 것도 문제
// em.flush(); // 여기서 플러시나 클리어 강제로 시키지 않으면 아래 for문 비어있음.
// em.clear(); // find는 DB에서 가져오는데 setTeam한 내역은 아직 commit되지 않음.
//그래서 setTeam과 add둘다 세팅하는게 좋다.

Team findTeam = em.find(Team.class, team.getId()); //1차 캐시
List<Member> members = findTeam.getMembers();

for (Member m : members){
	System.out.println("m =" 
}

tx.commit()

 

           ◼  solution: 연관관계 편의 메소드 생성하자. (둘 중 선택, 그러나 둘다는 X)

//Member 클래스
public void changeTeam(Team teama){//setter와 다르게 구별 주기 위해
	this.team = team;
	team.getMembers().add(this);
}
//Team 클래스
public void addMember(Member member){
	member.setTeam(this);
    members.add(member)
}

 

           ◼  양방향 매핑시 무한 루프를 조심하자.

              ◻  **lombok의 toString(), JSON 생성 라이브러리 등 조심 (컨트롤러로 엔티티 반환하지 않기. DTO로 변해서 보내기)  

           ◼  정리

              ◻  처음은 단방향 매핑으로 이미 연관관계 객체 매핑 완료 -> 이후 양방향 매핑 필요시 추가.

              ◻  필요한 경우: 객체 그래프탐색 기능 추가. JPQL에서 역방향 탐색시.  

◍  다중성

        @JoinColumn

           ◼ 속성

              ◻  name: 매핑할 외래키 이름(기본: 필드명 + _ + 참조하는 테이블의 기본키 컬럼명)

              ◻  referencedColumnName: 외래 키가 참조하는 대상 테이블의 컬럼명 (기본: 참조하는 테이블의 기본 키 컬럼명)

              ◻  foreignKey(DDL): 외랰 키 제약 조건을 직접 지정할 수 있다. 이 속성은 테이블 생상할 때만 사용.

              ◻  unique, nullable insertable, updatable, columnmDefinition, table @Column 속성과 같음. 

        다대일: @ManyToOne (가장 많이 사용)

           ◼  양방향시 외래키가 있는 쪽이 연관관계 주인

           ◼ 속성

              ◻  optional: false로 설정하면 연관된 엔티티가 항상 있어야 한다. (기본: TRUE) 

              ◻  fetch: 글로벌 페치 전략을 설정(기본: @MantToOne=FetchType.EAGER/ @OneToMany=FetchType.LAZY)

              ◻  cascade: 영속성 전이 기능을 사용.

              ◻  targetEntity: 연관된 엔티티 타입 정보 설정. 이 기능 거의 사용하지 않고 컬렉션을 사용해도 제네릭으로 타입정보 알 수 있다.

        일대다: @OneToMany

           ◼  단방향

              ◻  팀은 맴버를 알고 싶지만 맴버는 팀을 알고 싶지 않음. 

              ◻  일대다 단방향은 일대다(1:N)에서 1이 연관관계의 주인

              ◻  테이블 일다다 관계는 항상 다(N)쪽에 외래키가 있을 수 밖에 없음(구조상) 

              ◻  @JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용함 (중간 테이블 하나 추가 됨.) 

              ◻  단점1: 엔티티가 관리하는 외래 키가 다른 테이블에 있음 (객체와 테이블 차이때문에 반대편의 외래키를 관리.)

              ◻  단점2: 연관관계 관리를 위해 추가로 UPDATE SQL 실행

              ◻  일대다 단방향 매핑보다 다대일 양방향 매핑을 사용!

              ◻  사용 권장 하지 않음. 

        ◼  양방향

              ◻  공식적으로 존재하지 않음.

              ◻  @JoinColumn(insertrable=false, updateable=false)

              ◻  읽기 전용 필드를 사용해서 양방향처럼 사용하는 방법

              ◻  다대일 양방향을 사용 권장.

        ◼  속성

              ◻  mappedBy: 연관관계의 주인 필드를 선택.

              ◻  fetch: 글로벌 페치 전략을 설정(기본: @MantToOne=FetchType.EAGER/ @OneToMany=FetchType.LAZY)

              ◻  cascade: 영속성 전이 기능을 사용.

              ◻  targetEntity: 연관된 엔티티 타입 정보 설정. 이 기능 거의 사용하지 않고 컬렉션을 사용해도 제네릭으로 타입정보 알 수 있다.

        일대일: @OneToOne

           ◼  일대일 관계는 그 반대도 일대일 

           ◼  주 테이블이나 대상 테이블 중에 외래키 선택가능/ 방법

              ◻  주 테이블에 외래 키 단방향: 다대일(@ManyToOne)단방향 매핑과 유사

              ◻  주 테이블에 외래키 양방향: 다대일 양방향 매핑 처럼 외래키가 있는 곳이 연관관계의 주인. 

              ◻  대상 테이블 외래키 단방향: 단방향 관계 JPA 지원 X

              ◻  대상 테이블 외래키 양방향: 일대일 주 테이블에 외래키 양방향 매핑 방법과 같음.

           ◼  외래 키에 데이터베이스 UNIQUE 제약조건 추가.            

           ◼  trade-off..?

           ◼  주 테이블에 외래 키

              ◻  주 객체가 대상 객체의 참조를 가지는 것처럼 주 테이블에 외래키를 두고 대상 테이블을 찾음.

              ◻  객체지향 개발자 선호

              ◻  JPA매핑 편리

              ◻  장점: 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조 유지

              ◻  단점: 값이 없으면 외래 키에 null허용     

           ◼  대상 테이블에 외래키

              ◻  전통적인 데이터베이스 개발자 선호

              ◻  장점: 주 테이블과 대상 테이블을 일대일 에서 일대다 관계로 변경할때 테이블 구조 유지.

              ◻  단점: 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩 됨.

        다대다: @ManyToMany

           ◼  관계형 데이터 베이스는 정규화된 테이블 2개로 다대다 관계 표현할 수 없ㅇ.ㅁ

           ◼  연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야함.

           ◼  객체는 컬렉션을 사용해서 객체 2개로 다대다 관계 가능. 

           ◼  @ManyToMany사용하고 @JoinTable로 연결 테이블 지정(단방향, 양방향 가능)

           ◼  한계

              ◻ 편리해 보이지만 실무에서 사용 안됨.

              ◻연결 테이블이 단순히 연결만하고 끝나지 않음. 다른 데이터가 들어 올 수 있음.

           ◼  극복

              ◻ 연결 테이블용 엩티티 추가(연결 테이블을 엔티티로 승격)

              ◻ @ManyToMany -> @OneToMany, @ManyToOne

           ◼  실무에서 미사용 권장.

 

'Spring > JPA' 카테고리의 다른 글

JPA Basic - 값 타입  (0) 2024.01.09
JPA Basic - 프록시, CASCADE, 고아객체  (0) 2024.01.09
JPA Basic - 상속관계 매핑  (0) 2024.01.09
JPA Basic - 영속성 관리  (0) 2024.01.08
JPA Basic - Intro  (0) 2024.01.08