안녕하세요👋
오늘은 제가 개발시 느꼈던 에러와 불편함에 대한 해결책을 이야기 해보려고 합니다.
해결 방법이 급하게 궁금하신분은 맨 아래에 해결 방법이 나와 있습니다ㅎㅎ
✅ 서론
제 개발 환경은 Intellj, Java17, SpringBoot3.2, MySQL8.0 입니다 (참고 해주세요)
저는 항상 DB 툴을 통해서 미리 ERD 를 통해 DB 설계 후 , DB에 알맞게 Entity 를 구현하였습니다.
그리고 실무라고 생각하고 개발을 진행하였기에 application.yml 에서 JPA 설정은
spring:
jpa:
show-sql: true
properties:
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
hibernate:
ddl-auto: validate
ddl-auto 는 validate 로 잡아두고 엔티티 구현을 했습니다.
간단하게 ddl-auto Rule 에 대한 설명을 하자면
#개발 초기 단계 또는 로컬에서 테스트 : create 또는 update
#테스트 서버 : update 또는 validate
#스테이징 및 운영 서버 : validate 또는 none
실무에서는 위 운영 방식을 가지고 설계를 합니다.
위 처럼 설정을 하고 엔티티 설계를 하였고, DB 설계 시 불변의 값을 가지는 Status 값을 저는 varchar(50) 으로 즉 String 타입으로
설정해 두었습니다. 아래는 제가 엔티티를 설계한 코드 입니다.
@Column(length = 50, nullable = false)
@Enumerated(EnumType.STRING) // 열거형 -> 문자열로 저장
private UserOrderStatus status;
위 처럼 설계를 한 후에 서버를 실행 해보니
🤪 위 오류가 발생하였습니다.
오류 내용을 자세히 살펴보니, Hiberante 에서 ddl-auto: validation 에 의해 컬럼들을 검증하는 과정에서
column type 이 일치하지 않는 것이 있다는 이야기 였습니다.
자세히 살펴보니 Entity 설계 할 때 status 컬럼이랑 db 에 있는 status 컬럼이랑 타입이 불일치 한다는 이야기 였습니다.
이제 본격적으로 해결해보는 이야기를 해보겠습니다.
✅ 본론
왜 오류가 났을까? 라는 생각이 먼저 들었고 어디선가 주어 들은게 있어서 yml 파일을 찾았고, yml 파일에서 단서를 찾았다.
ddl-auto: validate 를 -> ddl-auto: update 로 바꾸니 오류가 생기지 않았다
그래서 문서들을 찾아보니 ddl-auto: update 는 말 그대로 update 를 시키긴 하는데,
기존에 존재하는 컬럼의 속성(nullable, 크기, 데이터 타입 등) 은 건드리지 않고 새로운 컬럼이 추가되는 변경사항만 반영한다고 합니다.
간단하게 말해 db 에 있는 name 타입은 int 이고, entity 설계시 name 은 String 이여도 그냥 오류 없이 진행이 된다는 것 입니다.
즉 update 는 컬럼의 속성은 건드리지 않는다는 뜻 입니다.
이래서 오류가 나지 않았던 것입니다.
그렇다면 Validate 는 ??
validate 는 다른 속성들과 다르게 DDL 을 작성하면서 생성,변경을 해주는 것이 아닌, Entity 와 DB 가 정상적으로 매핑이 되는지만 검사합니다. 컬럼 이름과, 컬럼 속성이 다르면 예외를 발생시키면서 어플리케이션을 종료합니다.
즉 저는, DB 와 Entity 에 Status 속성이 달랐기 때문에 나는 오류라는걸 확신했습니다.
그럼 이제 저는 원인을 찾았으니 해결방법을 고민해봤습니다.
❓ 어떻게 하면 Entity 와 DB 속성을 맞출 수 있을까 ❓
방법은 간단하죠
1) Entity 에서 어떠한 작업을 통해, DB 와 속성을 일치시켜주기
2) DB 에서 Entity 와 맞는 속성으로 테이블을 생성하기
다행이도 제가 고민한 내용이 둘다 해결책이 있어 다행이였습니다.
1번 방법
Entity 에서 이 컬럼은 enum 이라고 명시를 해주는 것입니다.
어떻게 명시를 하냐면 바로 @Column 어노테이션 안에서 답을 찾을 수 있었습니다.
위 코드는 @Column 어노테이션 내부 로직 입니다.
이 부분에서 columnDefinition 에 주목해야 합니다.
(Optional) The SQL fragment that is used when generating the DDL for the column. Defaults to the generated SQL to create a column of the inferred type.
columnDefinition 에 공식문서 설명 입니다.
번역기를 돌려보니
(선택 사항) 열에 대한 DDL을 생성할 때 사용되는 SQL 조각입니다. 유추된 유형의 열을 생성하려면 생성된 SQL을 기본값으로 사용합니다.
즉 DDL 생성시 @Column 어노테이션의 columnDefinition 속성은 엔티티의 특정 필드에 대한 데이터베이스 열의 정의를 직접 지정하는 데 사용됩니다. 이 속성을 사용하면 JPA가 기본 데이터베이스 열 정의를 무시하고 사용자가 제공한 정의를 사용하게 됩니다.
그리고 위 해결 방법을 통해 제 코드에 적용하였습니다.
@Column(columnDefinition = "varchar(50)", nullable = false)
@Enumerated(EnumType.STRING)
private UserOrderStatus status;
2번 방법
mysql 테이블 설계시 status 를 enum 으로 준다.
CREATE TABLE table_name (
id INT AUTO_INCREMENT,
status ENUM('ORDER', 'UNORDER') NOT NULL,
PRIMARY KEY (id)
);
아주아주 간다합니다.
치명적인 단점으로는 enum은 모든 RDB에서 제공하는 일반적인 기능이 아니기에 다른 rdb로 마이그레이션 할 경우 문제가 생길 수 있다.
그리고 데이터 수정이 어렵다는 단점이 있습니다.
위처럼 DB 설계 후 entity 생성시
@Entity(name="enumtest")
public class enumtest {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Enumerated(EnumType.STRING)
private Status status;
}
@Getter
public enum Status {
ORDER("등록"),
UNORDER("해지")
;
private String description;
}
위 처럼 설계를 하면은 오류가 발생하지 않습니다.
MySQL 에 enum 타입이 있는데 왜 사용을 지양한다는 이야기들이 많습니다.
그 이야기는 추후에 다시 다뤄서 포스팅을 해보겠습니다.
✅ 결론
1) Entity 에서 명시를 해라
@Column(columnDefinition = "varchar(50)")
@Enumerated(EnumType.STRING)
private UserOrderStatus status;
2) DB 생성시 타입을 enum 으로 해라
CREATE TABLE table_name (
id INT AUTO_INCREMENT,
status ENUM('ORDER', 'UNORDER') NOT NULL,
PRIMARY KEY (id)
);
추가적인 이야기로 예전에는 명시가 없이도 되었는데, Hibernate6.2 부터는 매핑 검증방식이 조금 더 엄격해졌고
MySQL DB 인 경우 Java Enum 매핑을 MySQL enum 타입을 사용하도록 전략이 변경되었기 때문도 있습니다.
위 방법을 통해 DB 와 Hibernate 엔티티 사이의 일관성을 유지하고, 예상치 못한 타입 불일치로 인한 오류를 방치하는데 도움을 줄 수 있습니다.
이상 포스팅 마치겠습니다. 감사합니다.
ref: https://velog.io/@superkkj/Hibernate-6-Enum%EA%B4%80%EB%A0%A8