강의를 들으며 생각 정리 + "자바 ORM 표준 JPA 프로그래밍" 책 참고
과거에는 객체를 DB에 저장할 때 Jdbc를 사용해 sql을 하나하나 전부 작성했다. 그러나 JPA를 사용하면 JPA가 직접 적절한 sql을 생성한다. 이는 개발 속도와 유지보수 측면에서 확연히 차이가 난다.
이토록 편리한 JPA지만 JPA를 바로 실무에 적용하려면 많은 공부가 필요하다.
- 객체와 테이블을 설계하고 이를 매핑하는 방법을 알아야 한다.
- JPA를 여러 sql을 몇 줄의 코드로만 나타내기 때문에 그 내부 동작 방식을 이해해야 한다.
이 두 가지가 매우 중요하며 이를 통해 JPA를 제대로 사용할 수 있다.
SQL 중심적인 개발의 문제점
최근 개발은 객체 지향 프로그래밍과 관계형 DB를 사용하는 추세이다. 즉, 객체를 관계형 DB에 관리한다. 그러나 DB는 sql만 알아들을 수 있기 때문에 개발자가 따로 sql을 DB에 보내줘야 한다. 여기서 문제가 발생한다.
무한반복
만약 어떤 객체에 CRUD 기능을 개발하고 싶다면 등록, 조회, 수정 코드 등에 전부 반복되고 비슷한 sql을 추가해야 한다. 이러한 객체가 50개, 100개 넘어가다 보면 무한 반복, 지루한 코드가 되어 객체를 개발하는 시간보다 sql과 씨름하는 시간이 더 많이 소요되는 경우가 있다.
sql에 의존적인 개발
만약 앞서 만든 객체에 필드 하나를 추가해야 하는 경우를 생각해보자. 이로 인해 등록, 조회, 수정 코드를 전부 일일이 추가된 필드에 맞게 변경해야 한다.
또한 객체에 연관된 다른 객체가 있는경우 기존 객체만 조회하는 메서드 뿐만 아니라 연관된 객체까지 같이 조회하는 메서드를 추가로 만들어야 한다. 객체 중심이 아닌 데이터 중심인 DB는 기존 객체를 조회한다고 해서 다른 연관된 객체 역시 조회해주지는 않기 때문이다.
이로써 아무리 객체 지향 코드를 작성한다고 해도 결국 sql에 읜존적인 개발을 피하기 힘들다. 오히려 객체답게 모델링할수록 실제 DB와 다르기 때문에 sql 매핑 작업만 늘어날 수 있다. 이는 결국 sql에 의존하는 것이고 개발자들이 원하는 모습이 아닐 것이다.
패러다임의 불일치
애플리케이션은 발전하면서 그 내부의 복잡성도 점점 커진다. 객체지향 프로그래밍은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 장치들을 제공한다. 그래서 현대의 복잡한 애플리케이션은 대부분 객체지향 언어로 개발한다.
그러나 관계형 DB에서는 데이터 중심으로 구조화되어 있어 객체지향에서 이야기하는 추상화, 상속, 다형성 같은 개념이 없다. 객체와 관계형 DB는 지향하는 목적이 다르므로 둘의 기능과 표현 방법도 다르다. 이것을 패러다임의 불일치라고 한다.(RDB는 데이터를 잘 정리해 보관하는 것 vs 객체는 필드와 메소드를 묶어 잘 캡슐화해 쓰는 것 등)
따라서 객체 구조를 테이블 구조에 저장하기 위해 개발자가 중간에서 해결해야 하는데, 문제는 너무 많은 시간과 코드를 소비하는 데 있다.
상속을 예를 들면, 객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능이 없다. Jdbc API를 사용해서 객체를 DB에 등록하려면 부모, 자식 객체 각각 insert sql을 작성해야 한다. 조회할 때 역시 부모, 자식에 해당하는 두 테이블을 조인해서 필요한 데이터를 조회해야 한다. JPA는 이처럼 부모, 자식 각각의 sql을 신경쓸 필요 없이 알아서 부모, 자식 객체를 나누어 두 테이블에 저장하고, 알아서 조인하여 테이블을 조회한다.
이 밖에도 많은 패러다임의 불일치 문제가 있지만 중요한 것은 이 차이를 극복하려고 개발자가 너무 많은 시간과 코드를 소비한다는 것이다. 더 어려운 문제는 객체지향 애플리케이션답게 정교한 객체 모델링을 할수록 패러다임의 불일치 문제가 더 커진다는 점이다. 그리고, 이 틈을 메우기 위해 개발자가 소모해야 하는 비용도 점점 더 많아진다. 결국, 객체 모델링은 힘을 잃고 점점 데이터 중심의 모델로 변해간다.
자바 진영에서는 오랜 기간 이 문제에 대한 숙제를 안고 있었고 많은 노력을 기울여왔다. 사실 순수한 자바로 객체를 자바 컬렉션에서 등록, 수정(add, setter 메서드) 등 관리하면 굉장히 편리하다. 그렇다면 "객체를 자바 컬렉션에 저장하듯이 DB에 저장할 수는 없을까?"라는 고민에서 개발된 것이 JPA이다. JPA는 패러다임의 불일치 문제를 해결해주고 정교한 객체 모델링을 유지하게 도와준다.
JPA 소개
JPA
JPA(Java Persistence API)는 자바 진영의 ORM 기술 표준이다.
여기서 ORM(Object-Relational Mapping)은 이름 그대로 객체와 관계형 데이터베이스를 매핑한다는 뜻이다. ORM 프레임워크는 객체와 테이블을 매핑해서 패러다임의 불일치 문제를 개발자 대신 해결해준다. 즉 객체는 객체대로 개발하고, RDB는 RDM대로 개발하여 그 차이들을 ORM이 중간에서 매핑해준다.
자바 진영에도 다양한 ORM 프레임워크들이 있는데 그중에 하이버네이트 프레임워크가 가장 많이 사용된다. 이 하이버네이트를 기반으로 새로운 자바 ORM 기술 표준이 만들어졌는데 이것이 바로 JPA이다.
왜 JPA를 사용해야 하는가?
JPA를 사용해야 하는 이유는 여러 가지다.
- 생산성
- 유지보수
- 패러다임의 불일치 해결
- 성능
생산성
JPA를 사용하면 다음 코드처럼 자바 컬렉션에 객체를 저장하듯이 JPA에게 저장할 객체를 전달하면 된다.
jpa.persist(member); //저장
Member member = jpa.find(memberId); //조회
JPA는 알아서 insert, select sql을 DB에 보내주기 때문에 개발자 입장에서 지루하고 반복적인 SQL을 작성하지 않아도 된다. JPA는 CRUD가 이미 정의되어 있고, 심지어 setter로 DB값까지 변경이 가능하다.
유지보수
앞서 얘기했듯이 SQL에 의존적인 개발에서 엔티티에 필드를 하나만 추가해도 관련된 등록, 수정, 조회 SQL 결과를 매핑하기 위한 JDBC API 코드를 모두 변경해야 했다. 반면에 JPA를 사용하면 이런 과정을 JPA가 알아서 처리해주므로 필드를 추가하거나 삭제할 때 수정해야 할 코드가 줄어든다.
패러다임의 불일치 해결
앞서 얘기한 패러다임의 불일치로 인한 문제점들을 JPA가 해결해준다. 상속을 예로 들면 persist만 해줘도 부모와 자식의 쿼리를 나눠서 insert 해주고 조회의 경우도 JPA가 알아서 부모와 자식을 조인해서 가져온다.
성능
- 캐시
JPA는 자체적으로 1차 캐시를 가지고 있고 이는 한 트랜잭션 단위로 움직인다.
String memberId = "100";
Member m1 = jpa.find(Member.class, memberId); //SQL
Member m2 = jpa.find(Member.class, memberId); //캐시
println(m1 == m2) //true
위와 같이 같은 PK값의 객체를 조회할 때, 한 번 조회한 객체는 1차 캐시에 저장해두고 이 후 조회할 때 sql을 DB에 보내지 않고 캐시에서 해당 객체를 조회한다.
캐시의 경우 일부 성능 향상이 있긴 하지만 사용 목적이 성능 향상을 위한 것은 아니고 JPA 내부 메커니즘을 유지하기 위해서 사용된다.(m1==m2 -> true)
- 쓰기 지연(insert)
트랜잭션을 커밋할 때까지 insert sql을 모아서 한번에 전송한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 모아서 보낸다.
transaction.commit(); // [트랜잭션] 커밋
- 지연 로딩
- 지연 로딩 : 객체가 실제 사용될 때 로딩
- 즉시 로딩 : JOIN SQL로 한번에 연관된 객체까지 미리 조회
지연 로딩
Member member = memberDAO.find(memberId); //select * from member
Team team = member.getTeam();
String teamName = team.getName(); //select * from team
즉시 로딩
Member member = memberDAO.find(memberId); //SELECT M.*, T.* FROM MEMBER JOIN TEAM …
Team team = member.getTeam();
String teamName = team.getName();
지연로딩은 네트워크를 두번 타지만, 객체가 실제로 사용될 때 sql을 보내기 때문에 굳이 사용하지 않는 객체까지 조회할 필요가 없어 성능 향상을 기대할 수 있다. 그러나 멤버를 조회할 때 무조건 팀 객체를 같이 쓴다면 즉시 로딩으로 한 번에 가져오면 DB에 접근하는 횟수가 줄어들기 때문에 좋다.
※ 정리
지금까지 SQL을 직접 다룰 때 발생하는 다양한 문제와 객체지향 언어와 관계형 데이터베이스 사이의 패러다임 불일치 문제를 알아봤다. 그리고 JPA가 각 문제를 어떻게 해결하는지 알아보았다.
※ ORM은 객체와 RDB를 잇는 기술이기 때문에 객체지향프로그램이과 RDB 모두 이해하고 있어야 ORM을 잘 이해할 수 있다. 특히, 언어는 나중에 바뀔 수 있지만 RDB에 대한 이해는 매우 중요하다. (RDB 설계, RDB 인덱스를 통한 최적화, SQL 성능 튜닝 등의 서적을 참고해도 좋다.)
'java > jpa' 카테고리의 다른 글
[JPA] 다양한 연관관계 매핑 (0) | 2021.01.27 |
---|---|
[JPA] 연관관계 매핑 기초 (0) | 2021.01.26 |
[JPA] 엔티티 매핑 (0) | 2021.01.22 |
[JPA] 영속성 관리 - 내부 동작 방식 (0) | 2021.01.21 |
[JPA] JPA 시작하기 (0) | 2021.01.21 |