당니의 개발자 스토리
필드와 컬럼 매핑 본문
필드와 컬럼 매핑
필드와 컬럼 맵핑에 대해서 알아보겠습니다.

JPA에서 엔티티랑 테이블 맵핑하는 것은 사실 별게 없죠. 근데 필드랑 컬럼은 좀 다양합니다. 예를 들면 자바에는 Enum 타입도 있고 그러니까요.
제가 이 예시를 해보기 위해서 요구사항을 한번 해보겠습니다.

회원은 일반 회원과 관리자로 구분해야 된다. 회원 가입일과 수정일이 있어야 한다. 회원을 설명할 수 있는 필드가 있어야 한다. 이 필드는 길이 제한이 없다. 뭐 이런 게 있어요.

그러면 제가 시간 관계상 이거를 복붙을 해보겠습니다.

import를 하고,

그 다음에 RollType을 Enum class로 만들고,

그냥 일반 USER랑 ADMIN 두 개만 만들죠.

그리고 Member의 getter, setter과 @Table도 지우고 이제 설명드리겠습니다.

자 먼저 @id는 pk를 맵핑 한 거죠.
그리고 두 번째,

이제 컬럼에 예를 들어서 객체는 username 이라고 쓰고 싶은데, db에는 name 이라고 써야 돼요. 그러면 이렇게 @Column 해가지고 name 이라는 속성을 쓰시면 됩니다. 그러면 '데이터베이스 column 명은 name 이야' 라고 이해하시면 됩니다.

그 다음에 이렇게 다른 타입을 쓸 수도 있습니다. 예를 들어서 뭐 Integer 이런 것들을 그냥 쓰시면 생각한 대로 돌아갑니다. Integer 같은 경우에는 db에도 숫자 타입이 만들어집니다.

그 다음에 JPA에서, 객체에서 Enum 타입을 쓰고 싶어요.

Java에는 이렇게 Enum 타입이 있어서 굉장히 좋죠. 근데 DB에는 Enum 타입이 없단 말이에요. 물론 Enum 타입 비슷한 것이 있는 DB도 있긴 한데 기본적으로 없다고 보시면 돼요.

그러면 이 @Enumerated 라는 어노테이션을 쓰시면 됩니다.

그 다음에 날짜 타입이에요. 날짜 타입을 쓰게 되면, 예를 들어 여기에 생성일자, 수정일자 이렇게 있죠. 그럼 @Temporal 라고 쓰시면 됩니다. 재미있는 게 여기 @Temporal에 타입이 3가지가 있습니다.

보시면 DATE, TIME, TIMESTAMP 이렇게 있습니다.

사실 자바의 Date 타입 안에는 그냥 날짜랑 시간이 다 있습니다.

근데 보통 데이터베이스들은 이 세가지를 구분해서 써요. 날짜, 시간, 날짜랑 시간 두가지 다 포함된거. 그래서 뭔가 맵핑 정보를 줘야 됩니다.

그래서 여기에 TemporalType.TIMESTAMP를 줘야 되구요.

그 다음에 이제 CLob, BLob 이런거 아시죠. 그 데이터베이스에 뭔가 Varchar를 넘어서는 굉장히 큰 컨텐츠를 넣고 싶으면 이제 Lob을 쓰시면 되구요. @Lob 해서 쓰시면 됩니다.
이거 한번 실행해 볼게요.

그 전에 필요 없는 것들 다 지울게요. 이렇게 해서 돌리면,

자 보시면 쭉 생성이 됐어요. id 는 BigInt 타입이라서 만들어지고 날짜는 h2 데이터베이스 에서 Integer 라는 데이터베이스 타입이 있습니다. 그 다음에 CreateDate는 TimeStamp, Description은 CLob이죠.

원래는 BLob, CLob이 있는데 여기 보시면 @Lob 이라고 해놓고 이게 문자(String) 타입이면 CLob으로 생성이 됩니다. 기본이 맵핑이 그렇게 됩니다. 그 다음에 lastModifiedDate(최종 수정 시간)이 있고, RoleType 을 보면 varchar(255)로 되어있죠. name도 varchar구요.

이제 맵핑 어노테이션에 대해서 봤으니까 간단하게 정리를 해드리겠습니다. 이게 몇 개 없어요. @Column, @Temporal, @Enumerated, @Lob, @Transient라고 있는데

@Transient는 맵핑을 안 하고 싶은 거예요.

예를 들어서 private int temp; 라는 게 있어요. 'temp는 DB랑 전혀 관계없이 메모리에서 계산하고 싶어! temp는 DB랑 하지 말아줘' 라고 기본적으로 해놓으면 일단 데이터베이스에 temp 라는 컬럼이 있어야 돼요. '나는 DB에서 temp는 신경 쓰고 싶지 않아' 라고 했을 때 @Transient 이거를 딱 쓰면,

실행하면 여기 보시면 temp가 없죠. 그래서 @Transient는 특정 필드를 컬럼에 매핑하지 않겠다는 겁니다. temp는 그냥 메모리에서만 쓰겠다는 거죠. 그럴 수 있잖아요. 메모리에 임시로 계산해 두고 뭔가 캐시 데이터를 넣어 두거나 그럴 때요.

자 그래서 @Column은 Column 맵핑, @Temporal은 날짜 맵핑 이렇게 뭐 쭉쭉 말씀드린 것들이 있구요.

자 @Column 이걸 이제 좀 자세히 알아보겠습니다. 제일 많이 쓰겠죠. 이 속성을 하나씩 설명을 드리겠습니다. 사실 맵핑은 배울게 크게 없어요. 이 정도만 알아 두시면 기본적인 맵핑 테이블 하나를 맵핑 하는 것은 다 할 수 있습니다.

자 여기 보시면, name은 username 필드랑 맵핑할 테이블의 컬럼명이라고 말씀드렸죠.

그 다음에 insertable, updatable 이라고 있는데요. 이게 뭐냐면 이 컬럼을 수정했을 때, 내가 데이터베이스에 인서트를 할 거야, 말 거야.

이 컬럼을 변경을 할 거야, 말 거야. 말 그대로 진짜 예를 들어서 '등록하고 나서 변경은 절대 하면 안 돼' 라고 한다면,

기본이 true기 때문에 updatable만 챙기면 되겠죠. 이렇게 updatable을 false로 해두시면, 이 컬럼은 절대 변경되지 않습니다. 물론 DB에서 강제로 update 치면 내가 뭐 어떻게 할 수 없겠지만 이 애플리케이션 JPA을 쓸 때는 절대로 반영되지 않습니다.
그 다음에 이제 nullable, nullable은 이제 좀 중요합니다.

이 nullable의 기본이 true 인데요. 이거를 false로 하게 되면 우리가 잘 아는 Not Null 제약 조건이 걸리게 되겠죠.
돌려보면,

여기 보시면 naem 옆에 Not Null이라고 보이시죠. 이거는 좀 자주 쓰겠죠. 그리고 이거는 제가 알기로는 Hibernate 같은 경우에는 DDL도 해주고 Not Null인 경우에는 없으면 자기가 먼저 체크도 한번 해줍니다.
그 다음에 unique,

간단하게 여기에 unique 제약 조건을 걸고 싶어요. 그러면 unique = true를 해주시면 됩니다. 근데 얘는 잘 안 써요. 왜 안 쓰냐면,

돌려보면 unique 제약 조건을 만들어 주긴 하는데, 이름이 랜덤처럼 이상한 게 나오죠. 이렇게는 사실 운영에서 못 쓰겠죠. 왜냐면 운영에서는 Exception, 딱 unique 제약 조건에 걸리면 바로 이거를 보고 '어 이거 때문에 Exception이 났네' 라고 알아야 되는데 이렇게 돼 있으면 모르겠죠.
그래서 여기에다가 unique를 쓰면 문제가 이 이름을 반영을 하기가 어려워요. 그래서 여기에 쓰면 안돼요. 그리고 이게 복합으로는 안 돼요.

그래서 보시면 여기에다가 unique 제약 조건을 걸 수가 있어요. 여기서 걸면 이름까지 줄 수 있습니다. 그래서 이 방식을 선호하고요.

그 다음에 length로 길이를 줄 수가 있고요. 10 이렇게 주면, varchar(10)이 됩니다.

그 다음에 'columnDefinition 자체를, 그러니까 컬럼 정의를 내가 직접 하고 싶어' 예를 들어서 뭐 varchar(100) default ‘EMPTY' 이렇게 넣고싶다고 하면,

이렇게 하면 이거는 그냥 이 문구가 그대로 DDL문에 들어가게 됩니다.

보시면 이거 다 들어간게 보이시죠? 그래서 특정 db에 종속적인 옵션들도 columnDefinition를 통해서 다 넣을 수가 있습니다.

자 그 다음에 @Column 해서 이제 BigDecimal 타입인 경우에 숫자가 엄청 커요. 숫자가 엄청 큰 경우에는 뭐를 할 수가 있냐면, 소수점 이런 것들에 대해서 내가 원하는 대로 옵션들을 줄 수가 있는데요. 이런 옵션들이 있습니다. 아주 큰 숫자나 소수점 쓰실 때 쓰시면 됩니다. 다시 int 로 바꿔 놓고,

그 다음에 enum 타입을 쓸 때 주의사항인데요.EnumType을 쓰실 때 기본이

여기 보시면 Default가 Ordinary입니다. EnumType에는 두 가지를 선택할 수가 있어요. Ordinary랑 String이 있거든요. Ordinary는 Enum의 순서를 DB에다가 저장하는 거고요.

String은 Enum의 이름을 그대로 DB에 저장을 하게 됩니다. 기본이 Ordinary기 때문에

이 EnumType.STRING를 지우고,

이렇게 해서 한번 코드를 짜고 돌려보면,

기본이 Ordinary 라고 했잖아요. Ordinary은 보시면 Integer 타입으로 생성을 해줍니다. 왜냐면 숫자로 들어가기 때문이에요.

그래서 db의 결과를 보면 RollType이 0번으로 들어가 있죠. 0, 1, 2, 3 이렇게 순서가 되는 겁니다.

그러면 제가 여기서 두 번째를 하나 추가 할게요.

지금 설정이 create로 되어있기 때문에 db에 누적이 안되니까 일단은 update로 변경하겠습니다. update로 두면 drop이 나가지 않거든요. 데이터가 유지가 돼요.

그 다음에 id를 2번으로 하고 얘는 ADMIN으로 해보겠습니다. 이름은 b 하고 실행을 하면,

db에 2개가 있겠죠. A는 0번, B는 1번.
근데 왜 이걸 쓰면 안 되냐면, 예를 들어서 제가 Ordinary 라는 기본을 쓰면 안 되는 이유가 예를 들어서 갑자기 요구사항이 늘어나서 guest 가 추가 됐어요. 그럼 이제 사람이 머리에 인지를 할 때 '아 guest, 그 다음에 user, 다음에 admin을 해야지' 하니까

이렇게 넣겠죠.

그러면 제가 여기서 guest를 한번 추가해 보겠습니다. id를 바꾸고 또 회원을 하나 더 만들어서 실행해 볼게요.
자 guest를 추가하고 실행을 하면,

어? A도 0이고 C도 0이에요. 이게 어떻게 된 거죠?

지금 이 순서가 지금 바뀌어 버린 거란 말이에요. 이제는 얘가 0이잖아요.

근데 DB는 어쨌든 옛날 데이터가 변경되지 않을 거니까 둘 다 0이 되어버리는 거죠. 이 시나리오를 보시면 알겠지만 Enum 타입에다가 Ordinary를 쓰면 굉장히 위험합니다. 운영 사항에서는 정말 이런 거 한번 터지면 해결할 수 없는 버그가 되는 거예요. 데이터를 막 다 migration 하고 해야 되는 거죠.

그래서 꼭 필수로 STRING을 써야 됩니다. String으로 하게 되면,

이제 처음부터 다시 해야 되니까 타입을 create로 다시 바꿀게요. 돌려보시면, RoleType이 varchar(255)로 일단 그냥 생성이 되구요.

그 다음에 db를 보시면, RoleType이 guest로 들어가 있죠. enum의 문자 그대로 들어가는 거죠. 몇 자 아끼려다가 큰 장애를 내는 것보다는 데이터를 조금 더 쓰더라도 요즘 DB도 빵빵하고 다 좋아서 그냥 String을 쓰시는 게 맞아요. 이렇게 하고 GUEST, ADMIN을 하면 다음에 별도의 데이터 타입이 추가되어도 순서에 의한 문제가 없기 때문에 잘 동작하게 됩니다.

그래서 Ordinary를 쓰지 마시고 꼭 STRING 타입으로 쓰셔서 그냥 그대로 넣는 걸 권장드립니다.

그 다음 @Temporal 이거는 이제 옛날에 쓰시던 분들한테 필요하고 지금 사실은 필요가 없어요. 이게 날짜 타입 때문에 필요한데요. 지금 Java 8 시대에 오면서 LocalDate, LocalDateTime이 들어왔어요.
보통 하이버네이트 쓰시면 이제 최신 버전을 쓰시게 될텐데, 하이버네이트 최신 버전을 쓰시면 그냥 LocalDate, LocalDateTime을 쓰시면 돼요.

예를 들어서 private LocalDate, LocalDate는 말 그대로 연, 월 데이터 까지만 있는 거죠. 그래서 그냥 이렇게 필드명을 붙이고요. LocalDate는 연, 월만 있는 거구요. LocalDateTime은 연, 월, 일이 다 포함되어 있는 데이터입니다. 이렇게 쓰시면 Hibernate 에서는 그냥 이 어노테이션 없어도 어차피 타입을 보면 아니까,

여기 보시면 testLocalDate는 DB의 date 타입으로, TestLocalDateTime은 DB의 timeStamp 타입으로 딱 생성이 됩니다.

그래서 최신 버전 쓰시는 분들은 그냥 이걸 이렇게 딱 쓰시면 돼요.

과거 버전을 쓰셔야 되고 date 타입을 쓰셔야 되면 이렇게 해서 맞게 맵핑 해주시면 됩니다.

말씀드린 대로 @Temporal는 Date나 Time이나 TimeStamp, 셋 중에 하나를 꼭 지정해 주셔야 됩니다.

그 다음에 @Lob은 재밌는게 지정할 수 있는 속성이 없어요.

들어가 보시면 텅텅 비었죠. @Lob() 해봐도 아무것도 없구요.

그니까 Lob은 맵핑하는 필드 타입이 문자면 CLob으로 맵핑이 되구요. 여기 보시면 문자이기 때문에 데이터베이스에 CLob,

그리고 나머지는 BLob으로 맵핑이 됩니다. 예를 들어서 byte가 되어있거나 이렇게 하면 BLob으로 맵핑이 됩니다. 이렇게 하고 맵핑을 하기 싫으면 이 annotation을 사용하시면 됩니다.

이렇게 하면 사실 필드랑 컬럼 맵핑이 끝나는 거에요. 생각보다 몇 개가 없죠. 이 정도만 해서 보면 별로 어렵지 않은 거에요. 물론 이제 진짜 어려운 거는 나중에 뒤에 가서 연관관계를 맵핑하는 게 어려운데요. 기본적으로 맵핑해서 쓰는 것 자체는 굉장히 간단합니다.
이제 다음 시간에는 기본 키 맵핑에 대해서 알아보겠습니다.
'스프링 > 실전! 스프링 부트와 JPA 활용1' 카테고리의 다른 글
| 상속관계 매핑 (0) | 2024.08.17 |
|---|---|
| 상품 주문 (0) | 2024.05.19 |
| 변경 감지와 병합(merge) (0) | 2024.05.16 |
| 상품 수정 (0) | 2024.05.15 |
| 상품 목록 (0) | 2024.05.14 |