본문 바로가기
Framework/JPA

[JPA] 엔티티(Entity)매핑(객체-테이블, 필드-컬럼)

by 곰민 2023. 2. 11.

엔티티(Entity) 객체와 데이터 베이스 테이블 간 매핑, 엔티티(Entity) 필드와 컬럼간 매핑에서 사용하는 속성값들과 활용법들에 대해서 알아보도록 하겠습니다.
해당 글은 김영한님 ORM 책과 강의를 참고하여 정리하고 몇몇 내용을 추가하여 만들었습니다.

 

객체와 테이블 매핑


 

@Entity


@Entity 가 붙은 클래스는 JPA 가 관리, 엔티티라고 하며 EntityClass를 DataBase Table과 Mapping 해줍니다.

 

주의할 점

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

final 클래스, enum, interface, inner 클래스 사용 금지

저장할 필드에 final 필드 사용 x

 

 

🤔왜? 기본 생성자가 필요할까?

 

hibernatesStartGuid 를 보면

The no-argument constructor, which is also a JavaBean convention, is a requirement for all persistent classes. Hibernate needs to create objects for you, using Java Reflection. The constructor can be private. However, package or public visibility is required for runtime proxy generation and efficient data retrieval without bytecode instrumentation.


hibernate는 객체를 Reflection을 사용해서 생성하는데. 생성자는 private으로 접근 지정자가 되어있을 수 있다.
하지만 런타임에서 proxy를 생성 또는 효율적인 package 또는 public 접근 지정자가 bytecode instrumentation없이 data 검색을 효율적으로 하기 위해서 필요하다.

 

🤔왜? 저장할 필드에 final 필드 사용 하지 않는 것을 권장할까?

추후 포스팅할 지연로딩(lazy-loading) 과 같은 경우
EntityClass와 같은 프록시 객체를 생성한뒤 값을 넣어주기도 합니다
.
그러려면 Entity 객체의 필드가 final로 되어있으면 안됩니다.

 

 

🤔왜? final 클래스, enum, interface, inner 클래스는 Entity 객체가 되지 못할까?

final 클래스의 경우 위와 같은 이유이며 Enum Class 또한 유사합니다.

Enum의 경우 고정된 상수값의 집합을 의미하는데 JPA엔티티는 값을 변경할 수 있어야 하므로 호환되지 않습니다.

interface의 경우 필드 값이 static final 이기도 하고 생성자를 갖지 못합니다.

innerclass의 경우 enclosing class의 인스턴스 없이는 인스턴스화가 될 수가 없습니다.

해당사항이 jpa에서 reflection을 활용하여 클래스를 인스턴스화 해서 생성하는 것을 힘들게 합니다.
외부의 enclosing class의 생성자가 만약 private으로 설정시에 내부클래스를 reflection해 올수 없게 됩니다.

 

 

Name 

Entity의 이름을 지정하며 따로지정하지 않을 경우 기본값은 클래스 이름을 그대로 사용합니다.

다른 패키지에 같은 클래스 이름이 존재하거나 하는경우 활용 가능합니다.

@Entity(name = "사용할 이름")

 

Table

엔티티와 매핑할 테이블 지정합니다.

@Table(name="매핑할 테이블")
//ex) insert in to 위에 적은 매핑할 테이블 이름

 

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


JPA에서 애플리케이션 로딩 시점에 DB 테이블을 생성하는 기능을 지원합니다.
즉 데이터베이스에 맞는 적절한 DDL을 애플리케이션 실행 시점에 자동 생성합니다.

생성된 DDL은 운영시점이 아닌 개발단계 로컬PC에서 테스트 용으로 도움이 됩니다.

 

create 기존 테이블 삭제후 다시 생성(DROP + CREATE)
create-drop create와 같으나 종료시점에 테이블 DROP
update 변경분만 반영
validate 엔티티와 테이블이 정상 매핑되었는지만 확인
none 사용하지 않음

 

//persistance.xml
<property name="jhibernate.hbm2ddl.auto" value="create">

---------------------------------------------------------
Hibernate: 
    
    drop table PCMember if exists
Hibernate: 
    
    create table PCMember (
       id bigint not null,
        name varchar(255),
        primary key (id)
    )

create의 경우 위와 같이 기존에 데이터가 존재한다면 데이터가 없어집니다.

테이블 수정 및 변경사항이 많은 경우 개발단계에서 옵션을 활용하면 유용하게 사용 가능합니다.

 

데이터베이스 스키마 자동 생성 주의사항


실제로 서비스를 운영하고 있는 장비에서는 create, create-drop, update 옵션을 사용하는것을 비권장합니다.

1. drop한뒤 create 하기 때문에 설정이 꼬이거나 에러상황이 발생하여 운영하고 있는 데이터 테이블이 날아가는 참사가 발생할 수도 있기 때문입니다.

2. update의 경우 application이 loading되는 시점에서 시스템이 자동으로 data를 alter를 사용하여 변경 사항을 반영해주는 기능 자체가 위험합니다.

alter의 경우 db에서 lock이 발생할 수 있으며 특정 시간 동안 서비스가 중지할 수 있습니다.

 

반영전 스테이징하는 서버와 운영서버는 validation 옵션 사용 또는 none을 권장합니다.

 

DDL 자동생성시 제약조건


제약조건 : 값 필수, 10자 초과 x

@Column(nullable = false, length = 10)

 

유니크 제약조건

@Table(uniqueConstraints = {@UniqueConstraint(name = "NAME_AGE_UNIQUE",
						   columnNames={"NAME","AGE"})})

 

DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않습니다.
validation 하는 용도로 사용하기에 좋습니다.

 

필드와 컬럼 매핑


@Column(name="") //컬럼 매핑
@Enumerated(EnumType.STRING) //enum 타입 맵핑 
@Temporal(TemporalType.TIMESTAMP) //
@Lob //DB에 큰 컨텐츠를 넣고 싶을 떄  blob, clob 가능 문자 타입이면 기본이 clob으로 생성됨. 
@Transient //디비랑 신경쓰고 싶지 않고 메모리에만 계산하기 위해서 사용하는 임시 데이터 해시 데이터

 

@Column


name - 필드랑 매핑할 테이블 컬럼 이름

insertable, updatable - 등록, 변경 가능 여부

 

@Column(name = "test", updateble=false) //업데이트 안됨

 

nullable(DDL) - null 값의 허용 여부 설정, false로 설정하면 DDL 생성시 Not null 제약조건

unique(DDL) - unique 제약 조건 걸고 싶을때, 랜덤으로 이름을 생성합니다. 이름 반영이 힘들 수 있습니다.

 

@Table(uniqueConstraints = {@UniqueConstraint(name = "NAME_AGE_UNIQUE", 
                            columnNames={"NAME","AGE"})})

 

columnDefinition(DDL) - 데이터베이스 컬럼 정보 직접 줄 수 있습니다.

 

columnDefinition = "varchar(100) default ‘EMPTY'"

 

length(DDL) - 문자 길이 제약조건 String 타입만 가능합니다.

precision, scale(DDL) - BigDecimal 이나 소숫점을 사용할때 사용합니다.

 

@Enumerated()


EnumType.ORDINAL

enum 순서를 데이터베이스에 저장하며 DB에 숫자로 들어갑니다.

 

ORDINAL 을 사용하지 않는 것을 권고합니다.

만약에 기존 [a,b] enum에 c를 추가 한다고 가정한다면

[a , b] → [c , a , b]

DB에 a는 0 으로 저장되어있고 새로 추가된 c도 0으로 저장되게 되버립니다.

 

EnumType.STRING
enum 이름을 데이터베이스에 저장합니다.

varchar 255 들어가고 enum에 있는 스트링값 그대로 들어갑니다.

순서에 의한 문제가 없다면 string으로 넘기는것을 권장.

 

@Temporal


날짜, 시간, timestamp를 DB Table에 저장하는 데 사용할 수 있는 JPA 어노테이션입니다.

1. DATE (java.sql.Date)

2. TIME (java.sql.Time)

3. TIMESTAMP (java.sql.Timestamp)

 

@Temporal
private Date joinedDate;

 

자바에서 제공하는 방식으로 필드에 값을 주어도됩니다.

private LocalDate testLocalDate; -> date 타입으로
private LocalDateTime testLocalDateTime; -> timestamp로 저장됨
//그냥 이렇게 사용하는 것을 권고

 

@Lob


매핑하는 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑해줍니다.

  • CLOB : String, char[]. java.sql.CLOB
  • BLOB : byte[], java.sql.BLOB

 

기본 키 매핑


기본키 매핑 어노테이션

 

@Id 를 사용하는 경우 직접 할당

@GeneratedValue 를 사용하는 경우 값을 자동 생성

 

@Id @GeneratedValue(strategy = GenerationType.AUTO) //DB 방언에 맞춰서 자동 생성
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) //기본키 생성 db 위임
// ex) mysql auto increment
@Id @GeneratedValue(strategy = GenerationType.SEQUENCE)//시퀀스 오브젝트 가져와서 값 세팅
private Long id; //10억이 넘어갈때 타입바꾸는게 어렵기에 애초에 Long 사용 성능저하 요즘 크게없다.

 

IDENTITY 전략

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; //id에 값을 넣으면 안되고 db에 insertquery 날려야됨

DB 가 unique한 pk를 생성하도록 위임하는 전략을 말합니다.

pk값인 Id값이 어떻게 들어 갔는지 DB연결을 해야지 알 수 있습니다.

IDENTITY 전략에서만 em.persist() 호출시점에 insert 쿼리가 날라갑니다.

모아서 한번에 DB에 insert하는 배치 전송이 IDENTITY 전략에서는 힘듭니다.

 

시퀀스 전략 

시퀀스 전략도 시퀀스 객체는 디비가 관리하기 때문에 시퀀스 객체만 슥 가져와서 값을 넣어두고 그다음에 영속성 컨텍스트에 저장합니다.

@Entity
@SequenceGenerator(
	name = "MEMBER_SEQ_GENERATOR",
	sequenceName = "MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
	initialValue = 1, allocationSize = 1)
public class Member{
	@Id
	@GeneratedValue(startegy = GenerationType.SEQUENCE,
				generator = "MEMBER_SEQ_GENERATOR")
	private Long id;
}
//시퀀스 매핑 걸기

 

name 식별자 생성기 이름 필수
sequenceName 데이터베이스에 등록되어 있는 시퀀스 이름 hibernate_sequence
initialValue DDL 생성 시에만 사용, 시퀀스 DDL을 생성할 떄 처음 1 시작하는 수를 지정 1
allocationSize 시퀀스 한 번 호출에 증가하는 수 (성능 최적화에 사용됨, 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값을 반드시 1로 설정) 50
catalog,schema 데이터베이스 catalog, schema 이름  

allocationSize 와 최적화가 필요합니다.

next call로 자주 디비에 가져오게 되면 성능 문제가 생길 수도 있습니다.

next call 할때 미리 디비에 해당 수치만 큼 세팅 해두고 메모리에서 1씩 사용 합니다.

그리고 해당 수치에 도달하면 다시 반복합니다.

디비에 미리 값을 설정해두는것 이기 때문에 여러 웹서버에서 사용해도 동시성 이슈없이 사용가능합니다.

10000이렇게 많이하면 웹서버 다운시 날아가서 중간에 구멍이 생길 수 도 있기에 50~100정도를 권장합니다.

 

 

테이블 전략

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

  • 단점 : 성능이 좀 떨어집니다
  • 장점 : 모든 디비에 활용 가능합니다
create table MY_SEQUENCES (
	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(startegy = GenerationType.TABLE,
				generator = "MEMBER_SEQ_GENERATOR")
	private Long id;
}
//시퀀스 매핑 걸기

 

allocationSize - 위와 동일합니다.

 

기본키 제약조건이 null이면 안되고, 유일해야 하며, 변하면 안된다 이나 변하면 안되는 이부분이 매우 어렵습니다.

미래까지 이 조건을 만족하는 자연키(비즈니스적으로 의미있는 키 주민번호, 전화번호)는 찾기가 어렵습니다.

 

대리키(대체키)를 활용하는 것을 권장합니다.

제너레이트 벨류 나 랜덤값과 같이 비즈니스와 상관 없는 값을 사용하는 것 을 권장합니다.

비즈니스 내부에서 의미가 있는 값을 키와 연관되서 사용하게 되는경우 해당 값이 더이상 쓰이게 되지 않는 경우 이후버전에서 데이터들이 바뀌게되면서 버전간 마이그레이션시 상당히 곤란한 상황이 발생하기 때문입니다.

 

권장방법

Long형 + 대체키(UUID, 시퀀스) + 키 생성 전략 사용

 

 

데이터 중심 설계의 문제점


위 방식은 객체 설계를 테이블 설계에 매핑하여 맞춘 방식입니다.

테이블의 외래키를 객체로 바로 가져오게 됩니다.

객체 그래프 탐색이 불가능 하게 됩니다.

참조가 없으므로 UML도 잘못됬다고 볼 수 있습니다.

 

참조


자바 ORM 표준 JPA 프로그래밍 - 교보문고

김영한 인프런 ORM 강의

baeldung LOB Data

stackoverflow require no args constructors

hibernates start guid

enclosingclass oracle

반응형

댓글