개요

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 가 있어도 상관없는 컬럼이면 추가해도 좋다.
Posted by doubler
,