회사 프로젝트 중 AWS에 배포된 서비스 환경에서는 보안상의 이유로 데이터베이스에 직접 접근할 수 없었습니다. 운영 서버에서 PostgreSQL DB를 직접 수정하거나 삽입 쿼리를 실행하는 것이 제한되었고, SSH나 DB 툴 접속도 허용되지 않았습니다.
이러한 제약을 해결하기 위해, Java 애플리케이션 내부에 SQL 파일을 포함시키고 애플리케이션 실행 시 이를 실행하도록 구성하는 방식을 선택했습니다. 과정 중 삽입 대상 데이터에 문자열로 포함된 세미콜론(;) 때문에 실행 오류가 발생하는 중요한 시행착오도 있었기에, 그 내용을 중심으로 아래와 같이 단계별로 정리합니다.
1. Python으로 CSV 파일을 읽어 INSERT SQL 생성
우선 삽입할 데이터는 CSV 형태로 정리되어 있었습니다. 이를 기반으로 Python으로 SQL 삽입 문을 자동 생성했습니다.
import pandas as pd
df = pd.read_csv('data.csv')
with open('insert_target_table.sql', 'w', encoding='utf-8') as f:
for _, row in df.iterrows():
values = "', '".join(str(v).replace("'", "''") for v in row.tolist())
f.write(f"INSERT INTO target_table (col1, col2, col3) VALUES ('{values}');\n")
※ 문자열 내 ' 문자는 SQL 문법에 맞게 ''로 이스케이프 처리했습니다.
2. PostgreSQL 시퀀스 설정 SQL 추가
자동 증가하는 ID 컬럼을 위해, 기존 데이터의 최대값 이후부터 시작하는 시퀀스를 설정했습니다.
CREATE SEQUENCE IF NOT EXISTS id_seq START WITH 9051 INCREMENT BY 1;
ALTER SEQUENCE id_seq OWNER TO app_user;
ALTER TABLE target_table ALTER COLUMN id_column SET DEFAULT nextval('id_seq');
ALTER SEQUENCE id_seq OWNED BY target_table.id_column;
이 시퀀스 설정 SQL과 INSERT 문을 하나의 init_target_table.sql 파일로 통합했습니다. 여기서 target_table 의 소유자가 app user인지 꼭 확인해야 합니다. 소유자가 다르다면 에러가 발생합니다.
3. SQL 파일을 Java 애플리케이션 내부에서 실행
DB 직접 접근이 불가능한 상황이었기 때문에, 위에서 만든 SQL 파일을 Java 애플리케이션에 포함시켜 실행하도록 했습니다. 처음에는 단순히 SQL 문을 세미콜론(;) 기준으로 분할하여 jdbcTemplate.execute()로 실행하는 방식으로 접근했습니다.
String sql = Files.readString(Paths.get("init_target_table.sql"));
for (String stmt : sql.split(";")) {
jdbcTemplate.execute(stmt.trim());
}
⚠️ 하지만 문제 발생: 문자열 안의 세미콜론이 SQL을 잘못 분리시킴
예를 들어 아래와 같은 SQL 문이 있을 경우,
INSERT INTO target_table (col1, col2) VALUES ('value;with;semicolon', 'abc');
이 문장은 문자열 내부에 세미콜론이 포함되어 있지만 실제 SQL 문은 하나입니다. 그러나 위 방식에서는 이 문자열 내의 ;도 분리 기준으로 오인되어 잘못된 SQL이 생성되고 실행 에러가 발생합니다.
4. 해결 방법: 문장 종료 기준으로 SQL을 읽고 실행
이를 해결하기 위해, 파일을 한 줄씩 읽으며 세미콜론으로 종료되는 문장만 실행하는 방식으로 로직을 변경했습니다. 문자열 안의 세미콜론은 무시되고, 오직 문장의 끝에 있는 세미콜론(;)만 실행 기준으로 삼습니다.
또한 .jar 파일로 배포 시 리소스를 getFile()로 읽을 수 없는 문제도 있어, InputStream을 통해 리소스를 읽도록 처리했습니다.
var resource = new ClassPathResource("sql/init_target_table.sql");
try (InputStream inputStream = resource.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
StringBuilder statementBuilder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
String trimmedLine = line.trim();
if (trimmedLine.isEmpty() || trimmedLine.startsWith("--")) continue;
statementBuilder.append(line).append("\n");
if (trimmedLine.endsWith(";")) {
String sql = statementBuilder.toString().trim();
sql = sql.substring(0, sql.length() - 1); // 마지막 세미콜론 제거
if (!sql.isEmpty()) {
jdbcTemplate.execute(sql);
}
statementBuilder.setLength(0); // 초기화
}
}
}
이 방식은 다음을 만족합니다:
- SQL 문이 여러 줄로 되어 있어도 문제 없음
- 세미콜론으로 끝나지 않는 중간 줄은 누적
- 문자열 내부 세미콜론은 무시되어 안전하게 실행 가능
.jar파일에서도 정상 동작
마무리
이번 작업을 통해 운영 환경에서 직접 DB 접근이 제한된 상황에서도, SQL 파일을 Java 애플리케이션 내부에서 안전하게 실행하는 우회 방안을 구현할 수 있었습니다. 특히, 문자열 내부에 포함된 세미콜론이 SQL 파싱 오류를 유발할 수 있다는 점을 경험하며, 단순한 split(";") 방식은 매우 위험할 수 있다는 교훈을 얻었습니다.
이러한 접근은 운영환경의 보안정책에 따라 직접적인 DB 접근이 어려운 다른 프로젝트에서도 유용하게 활용될 수 있습니다.
'개발 (Development) > Java' 카테고리의 다른 글
| [Java/SpringBoot] Spring에서 예외를 던지지 않고 API 응답은 유지하며 로그는 ERROR로 남기지 않도록 처리하는 방법 (0) | 2025.06.28 |
|---|---|
| [Java] printStackTrace() 경고 해결 및 로깅 적용하기 (0) | 2025.06.28 |
| [Java] Java jar 파일에서 리소스 파일 경로 사용하는 법 (0) | 2025.05.18 |
| [Java/Spring Boot] Spring Boot + MyBatis 환경에서 쿼리 조회용 API를 만들기 전에 꼭 고려해야 할 5가지 (2) | 2025.05.18 |
| [Java] 사내망에서 Gradle 빌드 시 PKIX 인증서 오류 해결기 (feat. 프록시 & 인증서 등록) (0) | 2025.04.19 |