목차
개요
제목 정하기가 무척이나 어려웠다. 문제를 해결하고 보니 복합적인 문제였기 때문이다.
최종적으로 본 포스팅에서 다룰 내용은 다음과 같다.
1. Spring boot JPA 스키마 생성 옵션 (create, create-drop, update .. 등등)
2. 엔티티간 관계 정의(@JoinColumn 오버라이드 옵션 사용)
3. JPA Hibernate 컬럼 명명 전략
4. MySQL workbench 내 FK 설정법
5. SQL Error :1146, SQLState: 42S02/ SQL Error:1054, SQLState: 42S22
6. Unknown column ~ in 'field list'
초기 문제 분석
Member 엔티티에 OneToMany 관계로 Event를 매핑하려고 했다.
이는 프로젝트 진행시에 유저별 일정을 저장하기 위해서 만든 엔티티인데 회원별로 같은 개인적인 일정을 저장하는 경우는 거의 없기때문에 따로 중간 데이터베이스를 설계하지 않고 OneToMany로 설계하였다.
해당 코드를 확인해보자면
- member-
@OneToMany(mappedBy = "member", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Event> events;
- event -
@ManyToOne
@JoinColumn(name= "memberId")
private Member member;
딱 이 두 코드를 보고 문제점을 파악했다면 JPA에 대한 이해도가 충분하다고 생각된다.
결론부터 확인하고 싶으면 아래 포스팅을 확인하자
이들 간의 관계를 설정하려고 했을 때, 데이터베이스에 Event 테이블이 존재하지 않는다는 오류 메시지가 발생했다.
2023-06-24T11:15:33.123+09:00 WARN 5444 --- [io-8080-exec-10] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1146, SQLState: 42S02
2023-06-24T11:15:33.123+09:00 ERROR 5444 --- [io-8080-exec-10] o.h.engine.jdbc.spi.SqlExceptionHelper : Table 'rdsreminder.reminderevents' doesn't exist
2023-06-24T11:15:33.145+09:00 ERROR 5444 --- [io-8080-exec-10] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement; SQL [n/a]] with root cause
Spring Boot 애플리케이션에서 Hibernate를 사용하여 데이터베이스 스키마를 자동으로 생성하도록 설정이 되어있었지만 ("update" 옵션) 제대로 생성되지 않았다.
예상 원인 분석 및 1차 해결
1. @JoinColumn 애노테이션의 사용
@JoinColumn 애노테이션은 `Event` 엔티티에 위치하고 있었는데, 그 안의 `name` 속성이 실제 데이터베이스의 컬럼명과 일치하지 않았을 가능성
실제로 나중에 `name` 속성에서 지정했던 "memberId"가 JPA 컬럼 명명 규칙으로 인해서[ {엔티티 이름}_{참조하는 엔티티의 ID 필드 이름} ] 데이터 베이스에는 member_id 로 저장이 되었었다.
2. 실제로 DB가 생성되지 않았다.
DB를 못찾는 이유는 진짜로 없었기 때문이고 이는 이미 member 객체에 대한 DB가 존재했었고 이후에 OneToMany 매핑을 member에 event 객체에 ManyToOne 을 매핑하려 했기에 "update" 옵션을 가지고 제대로 생성되지 않는 이유를 이해하지 못했다.
create를 시도해볼까 했지만 api 제작중에 프론트도 테스트를 하면서 저장해놨던 정보들이 많기때문에 변경하지 않았다.
시도했던 해결방안
문제를 해결하기 위해 SQL구문을 통해 수동으로 DB를 만들어주었다.
MySQL Workbench를 통해 수동으로 `Event` 테이블과 `Member` 테이블 사이의 외래키 설정을 해주는 과정은 아래와 같다.
이렇게 함으로써, JPA가 데이터베이스 스키마를 올바르게 인식하고, 애플리케이션 실행 시점에 `Event` 테이블을 찾을 수 있게 되었지만 또 다른 에러가 발생하였다.
member_id 가 없다는 것이다.
최종 해결
1차 원인 분석에 있던 @JoinColumn의 name 속성 특성 때문이었다.
초기에 저장했던 "memberId"가 MySQL-JPA 컬럼 명명 규칙으로 인해서[ {엔티티 이름}_{참조하는 엔티티의 ID 필드 이름} ] 데이터 베이스에는 member_member_id 로 저장이 되었었다.
따라서 Spring에서 memberId (DB에서 member_id) 라는 선언되지 않은 속성이 존재하고 있던 상황이다.
아래는 JPA명명전략에 관한 내용이다.
https://docs.jboss.org/hibernate/orm/6.2/userguide/html_single/Hibernate_User_Guide.html#naming
시도했던 해결 방안
문제가 복잡할 수 있는데 서순이 중요하다.
1. memberId로 시도했으나 DB가 없다는 에러가 발생하였다.
2. 따라서 수동으로 DB를 만들었고 FK 매핑과정에서 MySQL-JPA 컬럼 명명 규칙을 통해 member_member_id로 저장되었다.
3. @JoinColumn의 `name` 속성을 데이터베이스의 컬럼 이름인 "member_member_id"로 수정했다.
이후에 정상적으로 작동한것을 확인할 수 있었다!
원인 분석 및 마무리
이번 포스트를 통해, 우리는 외래 키 설정 문제를 직면했을 때, JPA의 컬럼 명명 규칙을 이해하고, 이를 바탕으로 문제를 해결하는 방법을 배웠다.
아래 코드는 Member 을 구현한 코드인데 보면 Id 부분에 따로 name옵션을 이용해서 member_id 로 만들어 놓은것을 확인할 수 있다.
전적으로 나의 잘못이었다. 해당 프로젝트를 진행하면서 Spring을 공부하기 시작했고 JPA의 명명 전략을 제대로 알지 못해서 다른 엔티티와의 구분이 필요하다 파악을 해서 id 를 따로 member_id로 명명하였고 이후에 제대로 리팩터링하지 않은 나의 잘못이 크다.
따라서 member에 따로 지정해준 name 옵션을 지웠고 DB에서의 컬럼 또한 member_id로 리팩터링 해주었다.
코드의 일관성을 유지하는것이 무척이나 중요하다고 깨닫게 되는 과정이었다.. 협업시 이런 실수를 했다고 생각하면 이보다 더 헤매서 원인을 찾았을것 같다.
이 포스트가 비슷한 문제를 겪었을 때 도움이 되었으면 좋겠다.
이 문제를 해결하다가 엔티티간 JPA로 매핑하는 옵션에 대해서 더 깊게 알게되었는데 시간을 내서 포스팅해보아야겠다.
'Spring' 카테고리의 다른 글
[SecurityContextHolder] Authentication 분석하기, principal 객체로 유저 이름 가져오기 (0) | 2023.06.29 |
---|---|
FetchType.EAGER vs FetchType.LAZY in JPA (0) | 2023.06.23 |
SpringBoot+MySQL(JPA) 회원가입,JWT 로그 구현하기 - (2) (1) | 2023.06.23 |
스프링 JPA: 엔티티 필드의 기본값에 대한 고찰 (0) | 2023.06.21 |
@Transient 어노테이션과 DTO를 사용한 레이어 분리 (0) | 2023.04.22 |