SPRING

[SPRING] 동적 쿼리

도원좀비 2025. 3. 30. 20:41

1️⃣ 동적 쿼리란?

  • 고정된 SQL이 아니라 조건이나 사용자 입력에 따라 변화하는 쿼리 방식
  • 쿼리를 미리 정의하지 않고, 실행 시점에 조건에 맞게 생성

2️⃣동적 SQL의 필요성

  • 다양한 검색 조건이나 필터링이 필요한 기능 구현에 필수적
  • 사용자의 입력값에 따라 유연한 쿼리 생성 가능

3️⃣ 전통적인 JDBC 방식으로 동적 SQL 구현하기

StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1");
List<Object> parameters = new ArrayList<>();

if(name != null) {
    sql.append(" AND name = ?");
    parameters.add(name);
}
if(age != null) {
    sql.append(" AND age = ?");
    parameters.add(age);
}
// PreparedStatement로 값 바인딩 후 실행

4️⃣ JPA의 Criteria API 방식

  • JPA가 제공하는 타입 안전한 동적 쿼리 작성 방법
  • 코드가 복잡하고 가독성이 떨어지는 단점이 존재
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
List<Predicate> predicates = new ArrayList<>();

if(name != null) predicates.add(cb.equal(user.get("name"), name));
if(age != null) predicates.add(cb.equal(user.get("age"), age));

query.where(cb.and(predicates.toArray(new Predicate[0])));

🚨 StringBuilder로 동적 쿼리 작성 시 SQL Injection 위험성

⚠️ 위험한 예시

StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1");

if (name != null) {
    sql.append(" AND name = '" + name + "'");
}

이런 형태는 사용자의 입력값이 직접적으로 쿼리 문자열에 들어가기 때문에, SQL Injection 공격에 매우 취약


❗️ SQL Injection이란?

사용자가 악의적인 SQL 구문을 입력하여 의도하지 않은 명령을 실행시키는 공격

 

(예시)

홍길동'; DROP TABLE user; --

이 값을 위의 name 변수에 그대로 넣으면 최종 쿼리는 아래와 같이 완성

SELECT * FROM user WHERE 1=1 AND name = '홍길동'; DROP TABLE user; --'

결과적으로 중요한 데이터가 삭제되는 치명적인 상황이 발생할 수 있다.

 


✅ 해결책: PreparedStatement 사용

이러한 문제를 해결하려면, SQL을 직접 문자열로 조합하지 말고 PreparedStatement를 사용하여 바인딩하면 해결

🔑 안전한 예시

StringBuilder sql = new StringBuilder("SELECT * FROM user WHERE 1=1");
List<Object> parameters = new ArrayList<>();

if (name != null) {
    sql.append(" AND name = ?");
    parameters.add(name);
}

PreparedStatement pstmt = connection.prepareStatement(sql.toString());

for (int i = 0; i < parameters.size(); i++) {
    pstmt.setObject(i + 1, parameters.get(i));
}

ResultSet rs = pstmt.executeQuery();

PreparedStatement는 입력된 값을 자동으로 SQL 구문으로 처리하지 않고 데이터로만 인식하기 때문에,
악의적인 SQL 구문 입력이 있어도 안전하게 처리


➕ QueryDSL, JPA(Criteria, JPAQueryFactory)를 사용하는 경우는?

QueryDSL이나 JPA의 Criteria API, 그리고 JPAQueryFactory 같은 라이브러리들은 내부적으로 자동으로 PreparedStatement 방식으로 데이터를 바인딩하여 SQL Injection 공격에서 안전

(예시)

queryFactory.selectFrom(QUser.user)
    .where(QUser.user.name.eq(name))
    .fetch();

 

'SPRING' 카테고리의 다른 글

[SPRING] JPAQueryFactory  (0) 2025.03.30
[SPRING] QueryDSL  (1) 2025.03.30
[SPRING BOOT + JPA] 일정 관리 앱 회고  (2) 2025.03.28
[Spring Boot + JPA] LV.8  (1) 2025.03.28
[Spring Boot + JPA] LV.7  (1) 2025.03.28