20210623 [error] java.sql.SQLException: Field {field-name} doesn't have a default value
ErrorMarking 2021. 6. 26. 15:03개요
ddl 작업 시, 간과했던 부분에 대해서 장애가 일어난 것을 정리하기 위한 글이다.
환경
- kotlin 1.4.30
- spring-boot 2.3.9.RELEASE
- spring-boot-starter-data-jpa
- mysql-connector-java
상황
- 서점 시스템을 만들었다.
- 서점 시스템에는 세 가지의 기능이 존재하였다.
- 책을 추가한다.
- 책을 단일 조회한다.
- 책을 전체 조회한다.
- 책은 아래와 같은 정보를 가지고 있다.
- 책 제목 / 작가이름 / 출판사 / isbn (국제 도서 고유번호)
- 어느날 해당 서점 시스템의 책 도메인에 새로운 정보가 추가되어야 하는 요구사항이 생겼다.
- " 책에 작가 아이디정보가 필요합니다. "
서점 시스템의 book entity code (by kotlin)
@Entity
@Table(name = "book", indexes = [
Index(name = "isbn_idx", columnList = "isbn")
])
class Book(
@Column(name = "name", columnDefinition = "VARCHAR(80)", length = 80, nullable = false)
val name: String,
@Column(name = "author", columnDefinition = "VARCHAR(60)", length = 60, nullable = false)
val author: String,
@Column(name = "publisher", columnDefinition = "VARCHAR(50)", length = 50, nullable = false)
val publisher: String,
@Column(name = "isbn", columnDefinition = "VARCHAR(80)", length = 80, nullable = false)
val isbn: String
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
companion object {
fun from(bookCreateResources: BookCreateResources): Book {
bookCreateResources.run {
return Book(
name,
author,
publisher,
UUID.randomUUID().toString()
)
}
}
}
}
문제발생
위의 코드는 책의 생성 및 조회에 대한 쿼리에 올바르게 작동하고 있다.
요구사항에 맞게 코드와 db 테이블 컬럼 정보가 추가되어야 한다.
이 때 나는 db 컬럼정보만 추가했을 때, 별 문제가 발생하지 않을거라고 생각했다...
# book table 에 대한 author_id 컬럼 not null 로 추가.
ALTER TABLE book ADD author_id bigint NOT NULL AFTER publisher;
db 컬럼정보를 추가하였고 에러가 발생했다.
java.sql.SQLException: Field 'author_id' doesn't have a default
위의 익셉션이 떨어지면서 book entity 를 생성할 때 문제가 발생한다는걸 인지했다. 조회에만 신경쓰다보니 create 하는 부분은 간과했던 것이다. 현재 author_id 는 not null 로 만든 상태이다. companion object 블럭 내 작성된 from() 에서 authorId 를 만들어주고 있지않았고, 이때 위와 같은 Field {field-name} doesn't have a default 익셉션이 생기는 것이다.
해결
book table 의 author_id 의 정보가 nullable 이었다면 에러가 발생하지 않을 문제였다. 하지만 해당 컬럼은 not null 상태였고 별도의 default value 가 ddl 쿼리에 포함되지 않았던 것도 있었다. 그래서 book entity 생성시 코드레벨에서 author_id 를 만들어주는 부분을 코드레벨에서 수정했고 해당 부분은 핫픽스로 나갔다.
@Entity
@Table(name = "book", indexes = [
Index(name = "isbn_idx", columnList = "isbn")
])
class Book(
// authorId 를 추가한다.
@Column(name = "author_id", columnDefinition = "BIGINT", nullable = false)
val authorId : Long
) {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null
companion object {
fun from(bookCreateResources: BookCreateResources): Book {
bookCreateResources.run {
return Book(
name,
author,
publisher,
UUID.randomUUID().toString(),
// book 엔티티 생성 시, authorId 를 채워넣어준다.
// 테스트 코드이기 때문에, 실제로 아래와 같이 들어가면 안된다..
Random.nextLong(0, 999999)
)
}
}
}
}
그럼 다른 문제는 없을까?
book entity 를 조회할 때는 문제가 없다.
jpa 엔티티상에서 아직은 필드값이 선언이 된 상태가 아니라서 select 쿼리 매핑할 필드가 없기 때문이다.
book entity 에 코드레벨에서 컬럼을 추가하고 기존 book 테이블 조회 시, 문제가 생길 수 있다.
기존 book 테이블에 존재하는 데이터들에는 authorId 가 없는데 어떻게 할 것인가? 기존 book table 의 authorId 에 데이터를 채워넣어주어야 한다. 왜냐하면 비즈니스적으로 authorId 는 값이 채워져서 내려가야 하는데, long 의 wrapper Long 으로 선언된 상태에서 그냥 0 으로 값을 내려주고 있다.
ddl 로 db 컬럼을 추가해놓지 않은 상태에서 코드레벨에서 authorId (not null) 을 추가된 경우에 문제가 생긴다.
spring.jpa.ddl-auto 인 경우가 validate 라면 애초에 애플리케이션이 뜰 때 문제가 생길것이다. 아니면 애플리케이션이 띄어진 상태에서 디비 컬럼이 삭제가 된 상태라면 jpa book entity 조회 시에 java.sql.SQLSyntaxErrorException: Unknown column 'book0_.author_id' in 'field list' 라는 에러를 만나게 된다.
어떻게 이런 문제를 다시 발생시키지 않는게 좋을까?
- 디비 컬럼은 nullable 하게 만들어놓지만 jpa entity 상의 필드는 not null 형태로 만들자.
- real 배포 시, 일반적으로 쿼리가 선적용되고 애플리케이션이 후적용된다. 그래서 코드레벨에서 not null 명확성을 확보하고 db 컬럼에서는 nullable 로 설정하여 유연성을 갖도록 하자.
- 컬럼을 추가함으로써 발생할 수 있는 CRUD 부분에 대해서 한번 더 고려할 수 있도록 한다.
- 위의 문제에서 나는 단순히 데이터 읽는 (READ) 부분만 신경을 써서 이런 문제가 발생했다.
- not null 일 때, default value 가 있어도 괜찮은 컬럼인지 고려해보자
- default value 가 있어도 상관없는 컬럼이면 추가해도 좋다.