⚠️ 글을 작성하는 당시 저는 Spring 초보임을 밝힙니다. 참고에 주의해주세요!
User Entity를 하나 만들어서 저장하려고 한다.
@Entity
data class User(
@Id @GeneratedValue var userId: String? = null,
@JsonProperty("device_id") val deviceId: String
)
User를 저장하고 제대로 저장되었는지 확인하는 테스트 코드를 작성했다.
@SpringBootTest
internal class UserServiceTest {
@Autowired
lateinit var service: UserService
@Test
fun saveUser() {
val user = User(deviceId = "test_device_id")
val savedUser = service.save(user)
val savedUserId = savedUser.userId ?: ""
assertThat(savedUserId).isNotBlank
val foundUser = service.findById(savedUserId)
assertThat(foundUser.get()).isEqualTo(savedUser)
}
}
그런데 테스트는 에러를 일으킨다. 왜 그럴까?
Unknown integral data type for ids : java.lang.String
org.springframework.orm.jpa.JpaSystemException: Unknown integral data type for ids : java.lang.String
at app//org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:321)
at app//org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)
at app//org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:550)
at app//org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at app//org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
at app//org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:152)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at app//org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:163)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at app//org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at app//org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:94)
at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at app//org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218)
at app/jdk.proxy3/jdk.proxy3.$Proxy109.save(Unknown Source)
at app//com.cozzin.todayquotes.service.UserService.save(UserService.kt:11)
at app//com.cozzin.todayquotes.service.UserServiceTest.saveUser(UserServiceTest.kt:18)
Entity의 Id로 String을 넣는다고 해뒀는데, @GeneratedValue
의 기본 strategy인 AUTO로 세팅해두면 숫자 형식의 데이터 타입으로 Id를 생성해준다고 추측할 수 있다. 그래서 이런식으로 변경했다.
@Id @GeneratedValue(strategy = GenerationType.UUID) var userId: String? = null,
이제 다른 에러가 나온다.
2023-02-08T22:09:49.282+09:00 WARN 75229 --- [ Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 42001, SQLState: 42001
2023-02-08T22:09:49.283+09:00 ERROR 75229 --- [ Test worker] o.h.engine.jdbc.spi.SqlExceptionHelper : Syntax error in SQL statement "select u1_0.id,u1_0.device_id from [*]user u1_0 where u1_0.id=?"; expected "identifier"; SQL statement:
select u1_0.id,u1_0.device_id from user u1_0 where u1_0.id=? [42001-214]
2023-02-08T22:09:49.283+09:00 INFO 75229 --- [ Test worker] o.h.e.internal.DefaultLoadEventListener : HHH000327: Error performing load command
org.hibernate.exception.SQLGrammarException: could not prepare statement
at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:64) ~[hibernate-core-6.1.6.Final.jar:6.1.6.Final]
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:56) ~[hibernate-core-6.1.6.Final.jar:6.1.6.Final]
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:109) ~[hibernate-core-6.1.6.Final.jar:6.1.6.Final]
JPA가 SQL문을 만들어낼 때 뭔가 이상이 생기고 있다고 유추할 수 있다. SQL 문법 - expected "identifier" 오류 비슷한 문제를 가졌던 블로그를 보면 테이블 명을 바꿔서 해결했다. 나도 테이블명을 바꿔봤더니 같은 에러는 나오지 않았다. user 또한 키워드라고 유추할 수 있다. 이렇게 변경하고 실행하니 제대로 작동한다!
@Entity
@Table(name = "USER_TABLE")
data class User(
테스트를 위해 사용하고 있는 H2 database의 keyword 목록에서user
를 확인할 수 있다. 그리고 SQL standard 이다. ⭐️ https://www.h2database.com/html/advanced.html#keywords 에서 확인할 수 있다 ⭐️ 앞으로도 꽤나 유용하게 참고할 수 있을 것 같다.
그런데 테이블 이름을 USER_TABLE
로 부르는게 정말 좋을까? 아니면 users
같은 복수형으로 부를 수도 있지 않을까? convention을 한번 찾아보자.
Person
추천; https://stackoverflow.com/a/5296972Actor
추천; https://stackoverflow.com/a/58122455테이블명은 일단 actor
로 해보자. 그래서 나온 최종안. class 이름도 Actor로 바꿔야할지는 고민중이다.
@Entity(name = "actor")
data class User(
@Id @GeneratedValue(strategy = GenerationType.UUID) var userId: String? = null,
@JsonProperty("device_id") val deviceId: String
)