Spring Boot 3.3.3 기반 프로젝트에서 발생한 트러블슈팅 정리
/todos API 호출 시 아래 에러 발생.
Connection is read-only. Queries leading to data modification are not allowed
insert into todos (contents,created_at,modified_at,title,user_id,weather) values (?,?,?,?,?,?)
TodoService 클래스 레벨에 @Transactional(readOnly = true) 설정 상태에서, saveTodo() 메소드에 별도 @Transactional 미적용.
읽기 전용 트랜잭션에서 INSERT 시도 → 오류 발생.
쓰기 작업 메소드에 @Transactional 명시적 추가.
// 클래스 레벨
@Transactional(readOnly = true)
public class TodoService {
// 쓰기 작업 메소드에 별도 선언
@Transactional
public TodoSaveResponse saveTodo(...) { ... }
}
shouldNotFilter에서 /api/auth를 제외 처리했음에도 회원가입/로그인 요청에서 400 Bad Request - JWT 토큰이 필요합니다. 발생.
@Component를 붙인 JwtFilter가 Spring Security 필터 체인과 서블릿 필터 양쪽에 이중 등록.
요청 흐름:
1. 서블릿 필터로 실행 → shouldNotFilter 무시 → JWT 검증 시도 ❌
2. Spring Security 필터 체인 실행 → shouldNotFilter 동작 ✅
FilterRegistrationBean으로 서블릿 필터 자동 등록 방지.
@Bean
public FilterRegistrationBean<JwtFilter> jwtFilterRegistration(JwtFilter jwtFilter) {
FilterRegistrationBean<JwtFilter> registration = new FilterRegistrationBean<>(jwtFilter);
registration.setEnabled(false); // 서블릿 필터 자동 등록 방지
return registration;
}
GET 요청(조회)은 정상 동작하지만, POST 요청(생성)만 401 Unauthorized 발생.
토큰도 정상적으로 설정된 상태.
Postman의 Inherit Auth from Parent 옵션 사용 중, 회원가입 요청에도 토큰이 자동 포함.
실제로는 500 에러(data.sql PK 충돌)였지만 Postman에서 401로 오인.
Postman에서 회원가입/로그인 요청의 Authorization을 No Auth로 명시적 설정.
회원가입 POST /api/auth/signup → Authorization: No Auth
로그인 POST /api/auth/signin → Authorization: No Auth
기타 API → Authorization: Bearer Token
http://localhost:8080/h2-console 접속 시 "localhost에서 연결을 거부했습니다" 표시.
SecurityConfig에서 permitAll() 설정 완료 상태.
Spring Security가 기본적으로 X-Frame-Options: DENY를 설정하여 iframe 차단.
H2 콘솔은 내부적으로 iframe을 사용하므로 렌더링 불가.
SecurityConfig에 frameOptions 비활성화 추가.
http
.csrf(AbstractHttpConfigurer::disable)
.headers(headers -> headers
.frameOptions(frame -> frame.disable()) // H2 콘솔 iframe 허용
)
application-local.yml H2 콘솔 활성화 확인.
spring:
h2:
console:
enabled: true
path: /h2-console
Todo 생성, 회원가입 등 INSERT 작업 시 아래 에러 발생.
Unique index or primary key violation: PRIMARY KEY ON PUBLIC.TODOS(ID)
data.sql에서 ID를 직접 지정(1~6)하여 초기 데이터 삽입.
JPA의 ID 시퀀스(AUTO_INCREMENT)는 1부터 시작하므로 충돌 발생.
-- data.sql: ID 1~6 직접 삽입
INSERT INTO todos (id, ...) VALUES (1, ...) ~ (6, ...)
-- JPA: 새 데이터 삽입 시 ID=1 부터 시작 → 충돌!
data.sql 하단에 각 테이블의 ID 시퀀스를 초기 데이터 이후 값으로 재설정.
ALTER TABLE users ALTER COLUMN id RESTART WITH 3;
ALTER TABLE todos ALTER COLUMN id RESTART WITH 7;
ALTER TABLE managers ALTER COLUMN id RESTART WITH 7;
ALTER TABLE comments ALTER COLUMN id RESTART WITH 19;
애플리케이션 실행 시 아래 에러 발생.
Failed to instantiate TodoCustomRepositoryImpl: No default constructor found
Caused by: java.lang.NoSuchMethodException: TodoCustomRepositoryImpl.<init>()
TodoCustomRepositoryImpl에 EntityManager를 받는 생성자만 존재.
Spring이 Bean 등록 시 기본 생성자(파라미터 없는 생성자)를 찾지 못함.
// 문제 코드 - Spring이 자동 주입 불가
public class TodoCustomRepositoryImpl {
private final JPAQueryFactory queryFactory;
public TodoCustomRepositoryImpl(EntityManager em) {
queryFactory = new JPAQueryFactory(em);
}
}
EntityManager 대신 이미 Bean으로 등록된 JPAQueryFactory를 직접 주입.
@RequiredArgsConstructor로 생성자 자동 생성.
// 수정 후
@Repository
@RequiredArgsConstructor
public class TodoCustomRepositoryImpl implements TodoCustomRepository {
private final JPAQueryFactory queryFactory; // Bean으로 등록된 JPAQueryFactory 주입
}
JPAQueryFactory는 QuerydslConfig에서 Bean으로 등록되어 있음.
@Configuration
public class QuerydslConfig {
@PersistenceContext
private EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(em);
}
}
| # | 문제 | 원인 | 해결 |
|---|---|---|---|
| 1 | Todo 저장 시 read-only 에러 | @Transactional(readOnly=true) 상속 | 메소드에 @Transactional 추가 |
| 2 | shouldNotFilter 무시 | @Component로 이중 등록 | FilterRegistrationBean 추가 |
| 3 | POST만 401 발생 | Postman 토큰 자동 포함 | No Auth 명시적 설정 |
| 4 | H2 콘솔 접근 불가 | Security iframe 차단 | frameOptions().disable() |
| 5 | PK 충돌 | data.sql과 시퀀스 불일치 | ALTER TABLE RESTART WITH |
| 6 | Bean 등록 실패 | 기본 생성자 없음 | JPAQueryFactory 직접 주입 |