도커를 이용해서 DB를 만들었는데 서버에서 접근하려고 할때 SQL Error: 1054, SQLState: 42S22
에러가 발생합니다.
해당하는 칼럼을 못찾아서 에러가 발생한건데 DB를 생성한 도커의 스크립트를 확인해보면
CREATE TABLE todos (
id BIGINT auto_increment primary key,
user_id BIGINT not null,
title varchar(100) not null,
done boolean not null
);
INSERT INTO todos (user_id, title, done) VALUES
(1, '아침먹고 운동하기', false),
(1, '점심먹고 운동하기', false);
commit;
이렇게 잘 생성되어 있고 서버에서는 매핑을 하기 위해서 @Calumn
어노테이션을 사용했습니다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Entity
@Table(name = "todos")
@Builder
public class Todo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "user_id")
private Long userId;
private String title;
@Column(columnDefinition = "TINYINT(1)")
private boolean done;
}
그런데 도대체가 왜 요청만 하면 could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet
이라는 응답을 받게 되는걸까요.
그래서 디버깅을 하면서 역추적을 했습니다.
동일한 스크립트로 H2.DB를 이용해 테스트를 했을경우 서비스단의 문제는 아니었습니다.
@DataJpaTest
@ActiveProfiles("test")
public class TodoRepositoryTest {
@Autowired
private TodoRepository todoRepository;
@Test
@Transactional
public void findAll_test() {
List<Todo> todoList = todoRepository.findAll();
Assertions.assertEquals(todoList.get(0).getId(), 1);
Assertions.assertEquals(todoList.get(1).getTitle(), "점심먹고 운동하기");
}
@WebMvcTest(TodoController.class)
@EnableAspectJAutoProxy
@MockBean(JpaMetamodelMappingContext.class)
@Import({WebSecurityConfig.class, MyValidAdvice.class})
public class TodoMockTest {
@MockBean
private TodoService todoService;
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper om;
List<Todo> todos;
@BeforeEach
public void setup() {
todos = List.of(
Todo.builder().id(1L).userId(1L).title("아침먹고 운동하기").done(false).build(),
Todo.builder().id(2L).userId(1L).title("점심먹고 운동하기").done(false).build());
}
@Test
@MyWithMockUser(id = 1L, username = "son", role = "USER")
public void findAll_MockTest() throws Exception {
// given
Long id = 1L;
given(todoService.findByUserId(id)).willReturn(todos);
// when
this.mockMvc.perform(
MockMvcRequestBuilders
.get("/todos"))
// then
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(print())
.andExpect(MockMvcResultMatchers.jsonPath("$.data[0].id").value(1))
.andExpect(MockMvcResultMatchers.jsonPath("$.data[0].title").value("아침먹고 운동하기"))
.andExpect(MockMvcResultMatchers.jsonPath("$.data[0].done").value(false));
}
스크립트에 문제가 없다는것을 알았으니 DB에 문제가 있을거라는 생각이 들었습니다.
그래서 이번에는 도커의 스키마를 확인해보기로 합니다.
먼저 도커 컨테이너에 접속한 뒤에 mysql클라이언트를 실행해야 합니다.
docker ps
docker exec -it [container_id_or_name] bash
mysql에 접속한 뒤 사용할 db를 선택해 들어갑니다.
mysql -u [username] -p
Enter password: #패스워드
db를 선택하고 스키마를 확인했습니다.
show databases;
use metacoding;
desc todos;
스키마를 확인해보니 칼럼명이 userId
로 되어 있는것을 확인했습니다.
처음 스키마를 만들때 userId
로 만들었지만 스크립트를 user_id
로 수정했는데 왜 도커의 db 스키마는 변하지 않았을까요.
원인을 더 알아보니 도커의 볼륨 사용이 문제였습니다.
도커는 변경사항이 생겼을때 이미지를 다시 빌드해서 변경사항을 적용시켜야 합니다.
그래서 실행할 때 매번 변경을 감지한 후 감지되면 새로운 이미지를 만들어 빌드하는 방법을 이용했습니다.
docker-compose up --build
하지만 볼륨을 이용하면 얘기가 달라집니다.
도커는 컨테이너 종료와 상관없이 데이터를 유지하기 위해 볼륨을 제공하는데 이 볼륨은 위 명령어를 통해서 빌드를 하더라도 볼륨의 데이터는 변경되지가 않습니다.
따라서 도커의 볼륨이 수정 전의 스크립트로 만들어졌기 때문에 에러가 발생하게 되었습니다.
이 문제를 해결하기 위한 첫번째 방법은 볼륨을 삭제하고 다시 만드는 것입니다.
docker-compose down -v
docker-compose up --build
정상적인 스크립트로 볼륨을 다시 만들고 나서 요청을 하면 문제없이 응답을 받게 됩니다.
두번째 방법은 위에서 mysql클라이언트에 접속한 상태에서 스키마를 변경합니다.
아래 명령어를 통해서 DB의 스키마를 변경 후 요청하면 정상적인 응답을 받을 수 있습니다.
ALTER TABLE todos CHANGE user_id userId BIGINT;