그들은 객체를 신처럼 숭배했다.
그들은 객체의 나라에서 태어나
클래스와 메서드를 경전처럼 배웠다.
그리고 외쳤다.
“관계형 데이터베이스? 그것은 과거의 유물이다.”
그들은 눈부신 추상화의 탑을 올렸다.
서비스, 리포지토리, 엔티티…
의존성과 패턴으로 치장된 신전에서
SQL 따위는 입에 담지도 않았다.
그러나 탑은 무너졌다.
JOIN 하나로 끝날 것을,
API 세 번 호출하며 CPU를 태웠기 때문이다.
SQL은 Structured Query Language의 줄임말로, 관계형 데이터베이스에서 데이터를 조작하고 정의하기 위한 언어다. 백엔드 개발자는 데이터와 떨어질 수 없다. 로그인부터 게시물 작성, 통계 집계까지 대부분의 기능은 결국 DB와의 상호작용을 필요로 한다.
이 상호작용의 핵심에 SQL이 있다. 흔한 예를 들면 다음과 같다:
조회: 특정 사용자의 게시글 목록을 최신순으로 정렬해서 가져오기
삽입: 새로운 회원 정보를 DB에 저장
성능 튜닝: 느린 API 응답 시간의 원인을 SQL 쿼리에서 찾고 인덱스를 추가
SQL을 잘 모르면, ORM이 생성하는 쿼리를 무작정 신뢰하게 된다. 이런 신뢰는 위험하다.
SQL은 백엔드 개발자의 '도구'라기보단 '기초 체력'이다.
ORM은 Object-Relational Mapping의 약자로, 객체와 테이블 간의 매핑을 통해 SQL 없이도 DB를 다룰 수 있게 해주는 기술이다. 대표적으로 Java 진영에선 JPA(Hibernate), Python에선 SQLAlchemy 등이 있다.
ORM이 제공하는 가장 큰 장점은 다음과 같다:
생산성: 반복적인 SQL 작성 없이 객체를 다루듯 데이터를 조작할 수 있음
유지보수성: 도메인 중심 설계를 할 수 있어 코드가 더 읽기 쉬워짐
추상화: DBMS에 독립적인 코드 작성 가능 (물론 완전히는 아님)
내가 JPA를 처음 썼을 땐 단순한 CRUD 기능을 너무 편하게 짤 수 있어서 감탄했었다. 특히 @Entity나 @Repository 같은 어노테이션만 붙이면 작동하는 구조는 학습 비용 대비 효율이 높다. 이런 추상화는 대규모 프로젝트에서 개발 속도와 안정성을 동시에 높여준다.
하지만 ORM은 만능이 아니다. 특히 성능 측면에선 오히려 문제를 일으킬 수 있다.
N+1 문제: 지연 로딩에서 잘못된 fetch 전략으로 인해 수십 수백 개의 쿼리가 발생
복잡한 쿼리: 집계 함수, 윈도우 함수, 복잡한 조건부 조인 등은 ORM으로 작성하기 불편하거나 불가능
디버깅 어려움: 쿼리가 자동 생성되기 때문에 성능 이슈가 있을 때 원인 파악이 어렵다
ORM만으로 모든 것을 해결하려다 보면, 결국 기술이 아니라 구조에 발목 잡히는 상황이 생긴다.
ORM에 대한 이해도가 작으면 여러 문제가 발생할 수 있다.
예를 들어, 컬렉션(리스트)형태의 객체를 조회할 때 N+1 문제를 해결하기 위해 단순 fetch join을 사용하게 되면 카티션 곱 문제가 발생하게 된다. 따라서 @Fetch(FetchMode.SUBSELECT) 를 사용하면 이러한 문제를 해결할 수 있다.
위 문제처럼 ORM은 객체를 다루는 기술이기 떄문에 이해도가 부족하면 성능 이슈가 발생할 수 있다.
가장 현실적인 접근은 ORM과 SQL을 '상황에 따라' 조화롭게 활용하는 것이다.
단순 CRUD → ORM 사용
복잡한 통계, 집계 → Native Query
성능 최적화 → JPQL + Index + 튜닝된 SQL
예를 들어 Spring JPA에선 @Query(nativeQuery = true)로 SQL을 직접 쓸 수 있고, 필요하면 JdbcTemplate도 함께 쓸 수 있다.
결국 중요한 건 SQL을 잘 알고, ORM을 전략적으로 쓸 수 있는 능력이다. ORM은 뼈대를 세우기 좋지만, 그 뼈대가 어디까지 유연하게 움직일 수 있는지는 SQL 실력에 달려 있다.
정리하자면, 백엔드 개발자에게 SQL은 기초 체력이고, ORM은 도구다. 둘 다 알아야 한다. 특히 MSA 구조나 고성능 API, 복잡한 데이터 흐름을 다루는 상황이라면 더더욱 그렇다. ORM은 결국 SQL 위에 얹어진 얇은 추상화에 불과하니까.