20P by carnoxen 12일전 | favorite | 댓글 22개

요약

JPA/Hibernate는 Java 코드에서 더 이상 SQL을 쓸 필요가 없다는 이유로 많이 쓰이는 프레임워크가 되었습니다. 하지만, 저는 새 프로젝트에 이를 사용하지 말아야 한다고 주장하고 싶습니다.

이유

매우 긴 공식 문서

공식 문서를 pdf로 변환하면 무려 406쪽이나 되는데, 이는 반지의 제왕(231쪽), SQL 표준 문서(288쪽)보다 큽니다. 데이터베이스 쿼리를 익히기 위해 석사 과정을 밟을 필요는 없습니다.

가변성

  1. 어떤 엔티티가 한 요소를 필요로 한다 해도, 아무 인자도 없는 생성자를 강요합니다.
  2. 엔티티 클래스에 final, abstract 키워드를 넣음으로써, 상속을 방지할 수 없습니다.
  3. Reflection/Introspection은 OOP의 캡슐화 원칙을 무시합니다.
  4. 누군가가 악의적인 코드를 심어 데이터를 완전히 날릴 수 있습니다.

지연 로딩 및 캐시

  1. @Lazy 어노테이션은 초심자에게 있어 가장 최악의 기술입니다. 하지만 도메인 설계가 Hibernate와 맞지 않거나 쿼리를 작성할 수 없을 때 이를 피할 수 없습니다.
  2. 캐시 메커니즘은 이해하기 어렵습니다. 게다가 이해했다 해도 쿼리 결과가 아닌 엔티티 자체를 캐시에 저장해야 합니다.

메모리-데이터베이스 동기화(Flush)

Flush라는 기술은 메모리에 저장된 객체와 데이터베이스를 동기화합니다. 이는 두 문제를 야기합니다.

  • Flush가 작동하면 메모리 안의 편집이 끝나버리기 때문에, Hibernate가 아닌 다른 영속성 도구는 꿈도 꿀 수 없으며,
  • Flush 도중 충돌이 발생하면 코드와 상관 없는 Stack Trace 오류가 발생할 수 있습니다.

한 테이블의 특정 칼럼만 얻기

어떤 엔티티 중 한 칼럼만 접근하고 싶으면 SQL의 접근 방식은 다음과 같이 단순합니다.

select url from image   
where id = 'F462E8D9-9DF7-4A58-9112-EDE0434B4ACE';  

하지만 Hibernate는 무조건 엔티티의 모든 칼럼을 조회합니다. 이를 피하려면 복잡한 과정을 거쳐야 합니다.

칼럼에 대한 제한 사항(Constraint) 정의

어느 한 칼럼에 대한 제한 사항을 정의하기 위해 아래처럼 여러 어노테이션을 붙여야 합니다.

...  
    @NotNull  
    @NotEmpty  
    @Email  
    private String email;  
...  

이 방식은 다음과 같은 문제를 야기합니다.

  • 이 조건을 위해 단위 테스트를 할 수 없습니다.
  • Flush 작업을 하는 동안 이 처리 과정을 파악하기엔 너무 늦습니다.
  • 나올 수 있는 예외는 일반적이고, 쓸모 없습니다.
  • 비즈니스 규칙을 기술적인 규칙으로만 다룹니다.

전략에 대한 문제

  1. 프레임워크는 업데이트가 최악이고, 하위 호환성을 무시, 무조건적으로 의존하게 만듭니다. 이를 만드는 회사는 독점하기 위해 자신의 프레임워크를 쓰는 것이 당연하다고 여기게 만듭니다. 이 악순환은 막아야 합니다.
  2. 개념 증명(Proof of Concept)을 굳이 프레임워크를 통해야만 얻는 것은 자신의 시야를 좁히는 결과만 얻을 것이며, 특히 JPA/Hibernate의 경우는 더더욱 그렇습니다. 티끌만큼도 허용해선 안 됩니다.

어떻게 해야 할까요?

SQL을 쓰세요

SQL만으로 모든 것이 가능합니다. 모든 프로그래머가 알고, 쿼리가 직관적이고, 프레임워크가 필요 없습니다.

관리자가 Hibernate를 쓰라는데요?

퇴사하시거나, 코드를 프레임워크와 분리하는 작업을 하세요.

이미 사용 중이라면...

  1. 기본 생성자와 설정자의 공개 수준을 public으로 만들지 마세요.
  2. SQL이 생성하는 아이디 대신 UUID같은 문자열을 쓰세요.
  3. XXXRepository라는 이름 대신 XXXDao라는 이름을 쓰세요.
  4. @SequenceGenerator 어노테이션을 쓰지 마세요.
  5. 도메인 클래스와 DAO 클래스를 intarface로 분리하세요.
  6. *대다 관계(@OneToMany 등)를 쓰지 마시고, 차라리 엔티티 맵핑을 피하세요.

결론

JPA/Hibernate, 버리세요.

  • 비즈니스 문제를 해결할 더 짧고 좋은 문서가 있습니다.
  • 설계를 너무 빠른 방식으로만 고수하지 마세요.
  • 당신의 코드를 관리할 다음 개발자에게 관용을 베푸세요.

여러분은 무엇을 사용하시나요?

  1. JPA/Hibernate
  2. 다른 ORM 기술

JPA/Hibernate를 버리자는 의견에 반대합니다.

"매우 긴 공식 문서" 부분
SQL도 처음 배울 때는 어렵습니다. 복잡한 조인, 서브쿼리, 프로시져 함수 등을 완벽히 이해하는 게 쉽나요?
JPA는 처음에 핵심 개념만 이해하고 시작해도 충분합니다. 더 깊은 내용은 필요할 때 찾아보면 됩니다.
그리고 LLM이 있습니다.

"가변성과 Reflection 문제"
이는 프레임워크의 동작 방식을 이해하지 못해서 나오는 걱정입니다.
실무에서 이로 인한 실질적인 문제가 발생하는 경우는 거의 없습니다.
오히려 Reflection으로 인해 객체 매핑이 자동화되어 생산성이 크게 향상됩니다.

"지연 로딩 및 캐시"
@Lazy가 "최악의 기술"이라고요? N+1 문제를 해결하고 성능을 최적화하는 데 매우 유용한 기능입니다.
캐시 메커니즘은 오히려 성능 향상에 큰 도움이 됩니다.

"한 테이블의 특정 칼럼만 얻기"
JPQL이나 Projection을 사용하면 필요한 칼럼만 쉽게 조회할 수 있습니다.
그리고 QueryDSL과 함께 사용하면됩니다.

ORM의 목적은 SQL을 완전히 대체하는 게 아니라, 개발자가 비즈니스 로직에 더 집중할 수 있게 돕는거라 생각합니다..

애초에 ORM이 유행하게 된 이유는 모른척하고 있는거 같네요.

학습비용이 좀 들긴하지만 익숙해지면 생산성 향상이 분명히 있는데 말이죠.

SQL이 간단한거 같지만, SQL 한땀한땀 코딩할 때의 그 피곤함은...거기다 테이블 변경되면 연관 쿼리도 다 한땀한땀 바꿔야 해서 SQL 유지보수도 결코 만만한 일이 아니라, 작고 간단한 만큼 작업량이 많아지는데(그래서 생산성 이야기가 계속 따라 나오는 것이고요)

추가로, SQL에서 발생하는 오류는 런타임에 터져서 잡기도 힘들고, SQL 인젝션 같은 공격 방어를 한땀한땀..하다보면 결국 쿼리를 생성하는 코드가 추가되고(보통 간단한 템플릿 형태로 시잭해서..) 진행하다 보면 결국 ORM비슷한 물건이 다시 나올텐데, 그럴바엔 그냥 ORM을 쓰는것이..?

며칠전에 올라왔던 글이 생각나네요
https://news.hada.io/topic?id=17955

동의합니다.
ORM을 사용하는 이유와 그 장점을 충분히 이해하지 못한 경우가 종종 있는 것 같아요.

추가로, ORM을 통해 실제 실행되는 SQL을 분석하거나 이해하려고 하는 사람도 많지 않은 것 같습니다.
단순히 편리함을 넘어서 SQL 최적화와 데이터베이스의 동작을 깊이 이해하는 데도 도움을 줄 수 있다는 점이 더 알려지면 좋겠습니다.

위의 글에서 마지막 질문은 좀 수정되어야할 것 같습니다.

Java 진영에서 1. ORM vs 2. Non-ORM 으로 정리해볼 수 있겠죠.

  1. ORM은 사실상 JPA/Hibernate 조합만 사용된다고 보면 됩니다.
  2. MyBatis, JOOQ, SpringDataJDBC 등이 있겠네요. 주로 SQL을 직접 핸들링하게 되죠.

1, 2 모두 장단점이 확실하기때문에, 위의 글처럼 극단적인 결론을 내리는 것은 적절하지 않습니다.

저희 같은 경우,
ORM인 JPA/Hibernate/QueryDSL을 사용하면서 동시에 MyBatis도 사용합니다.

ORM을 이용해서 최대한 생산성을 높이면서,
ORM으로 커버가 어려운 쿼리들은 MyBatis를 사용합니다.

그리고 위에서 1, 2 어느 것을 선택하건 SQL은 잘 알아야합니다.

저도 수정하고 싶긴 한데 사이트에 그런 기능이 없으니...

프레임워크 말고 근본으로 돌아가는 게 추세인가 싶기도 하네요.
HTMX, SQL 등등..

바퀴를 새로 만들어야 하는 단점이 있지만요

프레임워크를 사용하기 때문에 같은 프레임워크를 상용하는 다른 개발자들과 공통된 패러다임을 가져갈 수 있다는점은 항상 이런 무언가을 사용하지 말라는 글에서 무시되는 것 같네요

테이블이 많고 컬럼도 많은 경우에는 (예를 들어 테이블이 50개, 각 테이블당 컬럼이 100개 이상) SQL을 그냥 쓰면 지옥도가 펼쳐지더라구요.

다만 작은 서비스를 만들때 JPA/Hibernate를 만드는 것은 굉장한 낭비라고 생각합니다.

역시나 이런 의견은 케바케인것 같습니다.

(여기서 예시로 든것도 컬럼 3~4개짜리...)

써야 한다, 쓰면 안된다 양 극단에 치우칠 필요는 없다고 생각해요 ㅎㅎ;;
저는 생산성이 필요하다면 ORM을 활용하되,
ORM으로 커버할 수 없는 복잡한 쿼리 혹은 최적화가 더 필요한 쿼리는 로우쿼리로 처리합니다.
ORM이냐 raw query냐는 어떤 상황에서 무엇을 어떻게 만들 것인가에 따라 적절히 선택하면 된다고 생각합니다.

일반적으로 db 정규화가 잘 되어있고, 크게 join 할 필요 없는 데이터들은 그럴수 있다고 봅니다만,
db 정규화부터 시작해서 모든걸 제대로 dba를 통해서 관리할 수 없다면 orm도 좋은 선택이 될수 있다고 봅니다. 특히, join을 통해서 가져오는 쪽에서 relationship으로 가져오면서 생기는 이점은 가히 orm을 왜 쓰게 만드는지 보여주는 좋은 예제라고 생각합니다.
물론 프레임워크가 개발자의 성장을 제한하고, 그걸 프레임웍 의존도를 낮추자는 의견에는 동의합니다만
무조건적으로 orm을 쓰지 말자고 하자는 의견에는 쉽사리 동의 못하겠습니다.
모든 기업들이 다 dba가 있고 ddd나 tdd같은 제대로된 방법론으로 개발되고 있다는 전제를 까는거같아서
실제로 실무에서 저렇게 된다고 한다면 코드가 더 얼마나 개판이 날지도 모르겠습니다.

PYTHON으로 백엔드 만들 때, SQLALCHEMY나 DJANGO ORM 정도 매번 사용하는데요.
SQL 직접 쓰는 거나, ORM 사용하는 것이나 익숙해지니 별 차이 없게 느껴져서 아무 생각 안하고 있었는데. ORM을 사용하지 말자는 의견도 있었네요.
프레임워크 의존도를 낮추자는 의견은 찬성입니다. DJANGO ORM만 쓸 줄 알고 SQL을 다룰 줄 몰라서는 안 되겠지요...

음 글쎄요 저는 반대입니다. 현재 테이블이 3천개 정도인 서비스를 운영하고 있는데, 도메인이 하도 복잡하다보니 쿼리를 하나 만드는데 기본적으로 수십줄은 넘게 작성했습니다. 여기서 동적 쿼리도 생각하면 정말 머리 아픕니다. 복잡하니 버그도 많이 생기고 유지보수도 어려웠구요. 저는 복잡한 도메인에서는 ORM 이 더 유리하다고 생각합니다.

저같은 경우는 정규화가 안된 db를 유지보수 해본 경험이 있는데
그때 동적쿼리를 orm을 안쓰고 일반 SQL로 쓰는데,
그러다보니 코드가 더 알아보기 힘들게 가는 경우가 있더군요
복잡한 도메인 뿐 만 아니라 정규화가 부족한 도메인도 충분히 도입할만한 여지가 있다고 봅니다

오 나쁘게 볼 필요는 없겠군요

저도 개인적으로는 SQL을 그냥 쓰라고 권하고 싶습니다. JS월드에서는 Prisma와 같은 도구들을 많이 이용하는데, SQL이 그렇게까지 어려운 언어도 아니고 데이터베이스 I/O를 위해 너무 불필요한 추상화를 요구하는 것 같아서 조금 꺼려지더라구요.

js/ts 쪽 orm들이 유독 나사빠진 제품들이 많아서 그런 것 같기도 해요

Jdbc같은 거면 되겠죠? 이전에 저랑 같이 일하셨던 분이 "JPA는 느려서 다른 걸 써라"라고 하신 게 생각나네요.

전설 속 이야기같아요

장점도 있긴하죠..
2. MyBatis (ORM은 아니지만 ㅎ)

Orm이 아니라 dao로 바꿀 걸 그랬네요

ORM 비관론자이긴 한데, 충분한 대안을 제시하지는 못한 것 같네요.

ORM-heavy하게 가다보면 정말 끝도없고, 위에 언급처럼 SQL 문서보다 더 넓은 범위의 문서속에서 허덕이다가 말라죽을지도 모릅니다.

최근 개인프로젝트에서 ORM 을 사용하지 않고 개발중인데, 뭔가 재사용성을 고려한 설계를 하다보니 그냥 ORM 을 만들고있다는 생각이 드는 방향으로 개발하게 되기도 하더라구요.ㅎㅎ