🚀
Incheol's TECH BLOG
  • Intro
  • Question & Answer
    • JAVA
      • JVM
      • String, StringBuffer, StringBuilder
      • JDK 17일 사용한 이유(feat. JDK 8 이후 훑어보기)
      • 스택 오버 플로우(SOF)
      • 블럭킹 | 논블럭킹 | 동기 | 비동기
      • 병렬처리를 이용한 이미지 리사이즈 개선
      • heap dump 분석하기 (feat. OOM)
      • G1 GC vs Z GC
      • JIT COMPILER
      • ENUM
      • STATIC
      • Thread(쓰레드)
      • hashCode()와 equals()
      • JDK 8 특징
      • break 와 continue 사용
      • STREAM
      • Optional
      • 람다와 클로저
      • Exception(예외)
      • Garbage Collector
      • Collection
      • Call by Value & Call by Reference
      • 제네릭(Generic)
    • SPRING
      • Spring 특징
      • N+1 문제
      • 테스트 코드 어디까지 알아보고 오셨어요?
      • 테스트 코드 성능 개선기
      • RestTemplate 사용시 주의사항
      • 동시성 해결하기(feat. TMI 주의)
      • redisson trylock 내부로직 살펴보기
      • DB 트래픽 분산시키기(feat. Routing Datasource)
      • OSIV
      • @Valid 동작 원리
      • mybatis @Builder 주의사항
      • 스프링 클라우드 컨피그 갱신 되지 않는 이슈(feat. 서비스 디스커버리)
      • ImageIO.read 동작하지 않는 경우
      • 카프카 transaction 처리는 어떻게 해야할까?
      • Spring Boot 특징
      • Spring 5 특징
      • JPA vs MyBatis
      • Filter와 Interceptor
      • 영속성 컨텍스트(Persistence Context)
      • @Transactional
      • @Controlleradvice, @ExceptionHandler
      • Spring Security
      • Dispatcher Servlet
      • @EnableWebMvc
      • Stereo Type(스테레오 타입)
      • AOP
      • JPA Repository 규칙
    • DATABASE
      • Database Index
      • SQL vs NoSQL
      • DB 교착상태
      • Isolation level
      • [MySQL] 이모지 저장은 어떻게 하면 좋을까?
      • SQL Hint
      • JOIN
    • INFRA
      • CLOUD COMPUTING
      • GIT
      • DOCKER
      • 카프카 찍먹하기 1부
      • 카프카 찍먹하기 2부 (feat. 프로듀서)
      • 카프카 찍먹하기 3부 (feat. 컨슈머)
      • JENKINS
      • POSTMAN
      • DNS 동작 원리
      • ALB, NLB,ELB 차이는?
      • 카프카 파티션 주의해서 사용하자
      • DEVOPS
      • JWT
      • OSI 7 Layer
      • MSA
      • 서비스 디스커버리는 어떻게 서비스 등록/해제 하는걸까?
      • 핀포인트 사용시 주의사항!! (feat 로그 파일 사이즈)
      • AWS EC2 도메인 설정 (with ALB)
      • ALB에 SSL 설정하기(feat. ACM)
      • 람다를 활용한 클라우드 와치 알림 받기
      • AWS Personalize 적용 후기… 😰
      • CloudFront를 활용한 S3 성능 및 비용 개선
    • ARCHITECTURE
      • 객체지향과 절차지향
      • 상속보단 합성
      • SOLID 원칙
      • 캡슐화
      • DDD(Domain Driven Design)
    • COMPUTER SCIENCE
      • 뮤텍스와 세마포어
      • Context Switch
      • REST API
      • HTTP HEADER
      • HTTP METHOD
      • HTTP STATUS
    • CULTURE
      • AGILE(Feat. 스크럼)
      • 우리는 성장 할수 있을까? (w. 함께 자라기)
      • Expert Beginner
    • SEMINAR
      • 2022 INFCON 후기
        • [104호] 사이드 프로젝트 만세! - 기술만큼 중요했던 제품과 팀 성장기
        • [102호] 팀을 넘어서 전사적 협업 환경 구축하기
        • [103호] 코드 리뷰의 또 다른 접근 방법: Pull Requests vs. Stacked Changes
        • [105호] 실전! 멀티 모듈 프로젝트 구조와 설계
        • [105호] 지금 당장 DevOps를 해야 하는 이유
        • [102호] (레거시 시스템) 개편의 기술 - 배달 플랫폼에서 겪은 N번의 개편 경험기
        • [102호] 서버비 0원, 클라우드 큐 도입으로 해냈습니다!
  • STUDY
    • 오브젝트
      • 1장 객체, 설계
      • 2장 객체지향 프로그래밍
      • 3장 역할, 책임, 협력
      • 4장 설계 품질과 트레이드 오프
      • 5장 책임 할당하기
      • 6장 메시지와 인터페이스
      • 7징 객체 분해
      • 8장 의존성 관리하기
      • 9장 유연한 설계
      • 10장 상속과 코드 재사용
      • 11장 합성과 유연한 설계
      • 12장 다형성
      • 13장 서브클래싱과 서브타이핑
      • 14장 일관성 있는 협력
      • 15장 디자인 패턴과 프레임워크
      • 마무리
    • 객체지향의 사실과 오해
      • 1장 협력하는 객체들의 공동체
      • 2장 이상한 나라의 객체
      • 3장 타입과 추상화
      • 4장 역할, 책임, 협력
    • JAVA ORM JPA
      • 1장 JPA 소개
      • 2장 JPA 시작
      • 3장 영속성 관리
      • 4장 엔티티 매핑
      • 5장 연관관계 매핑 기초
      • 6장 다양한 연관관계 매핑
      • 7장 고급 매핑
      • 8장 프록시와 연관관계 관리
      • 9장 값 타입
      • 10장 객체지향 쿼리 언어
      • 11장 웹 애플리케이션 제작
      • 12장 스프링 데이터 JPA
      • 13장 웹 애플리케이션과 영속성 관리
      • 14장 컬렉션과 부가 기능
      • 15장 고급 주제와 성능 최적화
      • 16장 트랜잭션과 락, 2차 캐시
    • 토비의 스프링 (3.1)
      • 스프링의 이해와 원리
        • 1장 오브젝트와 의존관계
        • 2장 테스트
        • 3장 템플릿
        • 4장 예외
        • 5장 서비스 추상화
        • 6장 AOP
        • 8장 스프링이란 무엇인가?
      • 스프링의 기술과 선택
        • 5장 AOP와 LTW
        • 6장 테스트 컨텍스트 프레임워크
    • 클린코드
      • 1장 깨끗한 코드
      • 2장 의미 있는 이름
      • 3장 함수
      • 4장 주석
      • 5장 형식 맞추기
      • 6장 객체와 자료 구조
      • 9장 단위 테스트
    • 자바 트러블슈팅(with scouter)
      • CHAP 01. 자바 기반의 시스템에서 발생할 수 있는 문제들
      • CHAP 02. scouter 살펴보기
      • CHAP 03. scouter 설정하기(서버 및 에이전트)
      • CHAP 04. scouter 클라이언트에서 제공하는 기능들
      • CHAP 05. scouter XLog
      • CHAP 06. scouter 서버/에이전트 플러그인
      • CHAP 07. scouter 사용 시 유용한 팁
      • CHAP 08. 스레드 때문에(스레드에서) 발생하는 문제들
      • CHAP 09. 스레드 단면 잘라 놓기
      • CHAP 10. 잘라 놓은 스레드 단면 분석하기
      • CHAP 11. 스레드 문제
      • CHAP 12. 메모리 때문에 발생할 수 있는 문제들
      • CHAP 13. 메모리 단면 잘라 놓기
      • CHAP 14. 잘라 놓은 메모리 단면 분석하기
      • CHAP 15. 메모리 문제(Case Study)
      • CHAP 24. scouter로 리소스 모니터링하기
      • CHAP 25. 장애 진단은 이렇게 한다
      • 부록 A. Fatal error log 분석
      • 부록 B. 자바 인스트럭션
    • 테스트 주도 개발 시작하기
      • CHAP 02. TDD 시작
      • CHAP 03. 테스트 코드 작성 순서
      • CHAP 04. TDD/기능 명세/설계
      • CHAP 05. JUnit 5 기초
      • CHAP 06. 테스트 코드의 구성
      • CHAP 07. 대역
      • CHAP 08. 테스트 가능한 설계
      • CHAP 09. 테스트 범위와 종류
      • CHAP 10. 테스트 코드와 유지보수
      • 부록 A. Junit 5 추가 내용
      • 부록 C. Mockito 기초 사용법
      • 부록 D. AssertJ 소개
    • KOTLIN IN ACTION
      • 1장 코틀린이란 무엇이며, 왜 필요한가?
      • 2장 코틀린 기초
      • 3장 함수 정의와 호출
      • 4장 클래스, 객체, 인터페이스
      • 5장 람다로 프로그래밍
      • 6장 코틀린 타입 시스템
      • 7장 연산자 오버로딩과 기타 관례
      • 8장 고차 함수: 파라미터와 반환 값으로 람다 사용
      • 9장 제네릭스
      • 10장 애노테이션과 리플렉션
      • 부록 A. 코틀린 프로젝트 빌드
      • 부록 B. 코틀린 코드 문서화
      • 부록 D. 코틀린 1.1과 1.2, 1.3 소개
    • KOTLIN 공식 레퍼런스
      • BASIC
      • Classes and Objects
        • Classes and Inheritance
        • Properties and Fields
    • 코틀린 동시성 프로그래밍
      • 1장 Hello, Concurrent World!
      • 2장 코루틴 인 액션
      • 3장 라이프 사이클과 에러 핸들링
      • 4장 일시 중단 함수와 코루틴 컨텍스트
      • 5장 이터레이터, 시퀀스 그리고 프로듀서
      • 7장 스레드 한정, 액터 그리고 뮤텍스
    • EFFECTIVE JAVA 3/e
      • 객체 생성과 파괴
        • 아이템1 생성자 대신 정적 팩터리 메서드를 고려하라
        • 아이템2 생성자에 매개변수가 많다면 빌더를 고려하라
        • 아이템3 private 생성자나 열거 타입으로 싱글턴임을 보증하라
        • 아이템4 인스턴스화를 막으려거든 private 생성자를 사용하라
        • 아이템5 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
        • 아이템6 불필요한 객체 생성을 피하라
        • 아이템7 다 쓴 객체 참조를 해제하라
        • 아이템8 finalizer와 cleaner 사용을 피하라
        • 아이템9 try-finally보다는 try-with-resources를 사용하라
      • 모든 객체의 공통 메서드
        • 아이템10 equals는 일반 규약을 지켜 재정의하라
        • 아이템11 equals를 재정의 하려거든 hashCode도 재정의 하라
        • 아이템12 toString을 항상 재정의하라
        • 아이템13 clone 재정의는 주의해서 진행해라
        • 아이템14 Comparable을 구현할지 고려하라
      • 클래스와 인터페이스
        • 아이템15 클래스와 멤버의 접근 권한을 최소화하라
        • 아이템16 public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
        • 아이템17 변경 가능성을 최소화하라
        • 아이템18 상속보다는 컴포지션을 사용하라
        • 아이템19 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라
        • 아이템20 추상 클래스보다는 인터페이스를 우선하라
        • 아이템21 인터페이스는 구현하는 쪽을 생각해 설계하라
        • 아이템22 인터페이스 타입을 정의하는 용도로만 사용하라
        • 아이템23 태그 달린 클래스보다는 클래스 계층구조를 활용하라
        • 아이템24 멤버 클래스는 되도록 static으로 만들라
        • 아이템25 톱레벨 클래스는 한 파일에 하나만 담으라
      • 제네릭
        • 아이템26 로 타입은 사용하지 말라
        • 아이템27 비검사 경고를 제거하라
        • 아이템28 배열보다는 리스트를 사용하라
        • 아이템29 이왕이면 제네릭 타입으로 만들라
        • 아이템30 이왕이면 제네릭 메서드로 만들라
        • 아이템31 한정적 와일드카드를 사용해 API 유연성을 높이라
        • 아이템32 제네릭과 가변인수를 함께 쓸 때는 신중하라
        • 아이템33 타입 안전 이종 컨테이너를 고려하라
      • 열거 타입과 애너테이션
        • 아이템34 int 상수 대신 열거 타입을 사용하라
        • 아이템35 ordinal 메서드 대신 인스턴스 필드를 사용하라
        • 아이템36 비트 필드 대신 EnumSet을 사용하라
        • 아이템37 ordinal 인덱싱 대신 EnumMap을 사용하라
        • 아이템38 확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라
        • 아이템 39 명명 패턴보다 애너테이션을 사용하라
        • 아이템40 @Override 애너테이션을 일관되게 사용하라
        • 아이템41 정의하려는 것이 타입이라면 마커 인터페이스를 사용하라
      • 람다와 스트림
        • 아이템46 스트림에는 부작용 없는 함수를 사용하라
        • 아이템47 반환 타입으로는 스트림보다 컬렉션이 낫다
        • 아이템48 스트림 병렬화는 주의해서 적용하라
      • 메서드
        • 아이템49 매개변수가 유효한지 검사하라
        • 아이템50 적시에 방어적 본사본을 만들라
        • 아이템53 가변인수는 신중히 사용하라
        • 아이템 54 null이 아닌, 빈 컬렉션이나 배열을 반환하라
        • 아이템56 공개된 API 요소에는 항상 문서화 주석을 작성하라
      • 일반적인 프로그래밍 원칙
        • 아이템56 공개된 API 요소에는 항상 문서화 주석을 작성하라
        • 아이템57 지역변수의 범위를 최소화하라
        • 아이템 60 정확한 답이 필요하다면 float와 double은 피하라
      • 예외
        • 아이템 73 추상화 수준에 맞는 예외를 던지라
        • 아이템 74 메서드가 던지는 모든 예외를 문서화하라
      • 동시성
        • 아이템78 공유 중인 가변 데이터는 동기화해 사용하라
        • 아이템79 과도한 동기화는 피하라
        • 아이템 80 스레드보다는 실행자, 태스크, 스트림을 애용하라
      • 직렬화
        • 아이템 87 커스텀 직렬화 형태를 고려해보라
    • Functional Programming in Java
      • Chap 01. 헬로, 람다 표현식
      • Chap 02. 컬렉션의 사용
      • Chap 03. String, Comparator, 그리고 filter
      • Chap 04. 람다 표현식을 이용한 설계
      • CHAP 05. 리소스를 사용한 작업
      • CHAP 06. 레이지
      • CHAP 07. 재귀 호출 최적화
      • CHAP 08. 람다 표현식의 조합
      • CHAP 09. 모든 것을 함께 사용해보자
      • 부록 1. 함수형 인터페이스의 집합
      • 부록 2. 신택스 오버뷰
    • 코틀린 쿡북
      • 2장 코틀린 기초
      • 3장 코틀린 객체지향 프로그래밍
      • 4장 함수형 프로그래밍
      • 5장 컬렉션
      • 6장 시퀀스
      • 7장 영역 함수
      • 9장 테스트
      • 10장 입력/출력
      • 11장 그 밖의 코틀린 기능
    • DDD START!
      • 1장 도메인 모델 시작
      • 2장 아키텍처 개요
      • 3장 애그리거트
      • 4장 리포지터리와 모델구현(JPA 중심)
      • 5장 리포지터리의 조회 기능(JPA 중심)
      • 6장 응용 서비스와 표현 영역
      • 7장 도메인 서비스
      • 8장 애그리거트 트랜잭션 관리
      • 9장 도메인 모델과 BOUNDED CONTEXT
      • 10장 이벤트
      • 11장 CQRS
    • JAVA 8 IN ACTION
      • 2장 동작 파라미터화 코드 전달하기
      • 3장 람다 표현식
      • 4장 스트림 소개
      • 5장 스트림 활용
      • 6장 스트림으로 데이터 수집
      • 7장 병렬 데이터 처리와 성능
      • 8장 리팩토링, 테스팅, 디버깅
      • 9장 디폴트 메서드
      • 10장 null 대신 Optional
      • 11장 CompletableFuture: 조합할 수 있는 비동기 프로그래밍
      • 12장 새로운 날짜와 시간 API
      • 13장 함수형 관점으로 생각하기
      • 14장 함수형 프로그래밍 기법
    • 객체지향과 디자인패턴
      • 객체 지향
      • 다형성과 추상 타입
      • 재사용: 상속보단 조립
      • 설계 원칙: SOLID
      • DI와 서비스 로케이터
      • 주요 디자인 패턴
        • 전략패턴
        • 템플릿 메서드 패턴
        • 상태 패턴
        • 데코레이터 패턴
        • 프록시 패턴
        • 어댑터 패턴
        • 옵저버 패턴
        • 파사드 패턴
        • 추상 팩토리 패턴
        • 컴포지트 패턴
    • NODE.JS
      • 1회차
      • 2회차
      • 3회차
      • 4회차
      • 6회차
      • 7회차
      • 8회차
      • 9회차
      • 10회차
      • 11회차
      • 12회차
      • mongoose
      • AWS란?
    • SRPING IN ACTION (5th)
      • Chap1. 스프링 시작하기
      • Chap 2. 웹 애플리케이션 개발하기
      • Chap 3. 데이터로 작업하기
      • Chap 4. 스프링 시큐리티
      • Chap 5. 구성 속성 사용하기
      • Chap 6. REST 서비스 생성하기
      • Chap 7. REST 서비스 사용하기
      • CHAP 8 비동기 메시지 전송하기
      • Chap 9. 스프링 통합하기
      • CHAP 10. 리액터 개요
      • CHAP 13. 서비스 탐구하기
      • CHAP 15. 실패와 지연 처리하기
      • CHAP 16. 스프링 부트 액추에이터 사용하기
    • 스프링부트 코딩 공작소
      • 스프링 부트를 왜 사용 해야 할까?
      • 첫 번째 스프링 부트 애플리케이션 개발하기
      • 구성을 사용자화 하기
      • 스프링부트 테스트하기
      • 액추에이터로 내부 들여다보기
    • ANGULAR 4
      • CHAPTER 1. A gentle introduction to ECMASCRIPT 6
      • CHAPTER 2. Diving into TypeScript
      • CHAPTER 3. The wonderful land of Web Components
      • CHAPTER 4. From zero to something
      • CHAPTER 5. The templating syntax
      • CHAPTER 6. Dependency injection
      • CHAPTER 7. Pipes
      • CHAPTER 8. Reactive Programming
      • CHAPTER 9. Building components and directives
      • CHAPTER 10. Styling components and encapsulation
      • CHAPTER 11. Services
      • CHAPTER 12. Testing your app
      • CHAPTER 13. Forms
      • CHAPTER 14. Send and receive data with Http
      • CHAPTER 15. Router
      • CHAPTER 16. Zones and the Angular magic
      • CHAPTER 17. This is the end
    • HTTP 완벽 가이드
      • 게이트웨이 vs 프록시
      • HTTP Header
      • REST API
      • HTTP Method 종류
        • HTTP Status Code
      • HTTP 2.x
  • REFERENCE
    • TECH BLOGS
      • 어썸데브블로그
      • NAVER D2
      • 우아한 형제들
      • 카카오
      • LINE
      • 스포카
      • 티몬
      • NHN
      • 마켓컬리
      • 쿠팡
      • 레진
      • 데일리 호텔
      • 지그재그
      • 스타일쉐어
      • 구글
      • 야놀자
    • ALGORITHM
      • 생활코딩
      • 프로그래머스
      • 백준
      • 알고스팟
      • 코딜리티
      • 구름
      • 릿코드
Powered by GitBook
On this page
  • SQL을 직접 다룰 때 발생하는 문제점
  • SQL에 의존적인 개발
  • JPA와 문제 해결
  • 패러다임의 불일치
  • 상속
  • 연관관계
  • 객체 그래프 탐색
  • 비교
  • 정리
  • JPA란 무엇인가?
  • 왜 JPA를 사용해야 하는가?
  • ORM에 대한 궁금증과 오해

Was this helpful?

  1. STUDY
  2. JAVA ORM JPA

1장 JPA 소개

자바 ORM 표준 JPA 프로그래밍 1장을 요약한 내용 입니다.

SQL을 직접 다룰 때 발생하는 문제점

SQL을 직접 다룰 때의 문제점을 알아보기 위해 자바와 관계형 데이터베이스를 사용해서 회원 관리 기능을 개발해보자.

먼저 회원을 조회하는 기능을 개발한다.

  • 회원 조회용 SQL을 작성한다.

SELECT MEMBER_ID,  NAME FROM MEMBER M WHERE MEMBER_ID = ?
  • JDBC API를 사용해서 SQL을 실행한다.

ResultSet rs = stmt.executeQuery(sql);
  • 조회 결과를 Member 객체로 실행한다.

String memberId = rs.getString("MEMBER_ID");
String name = rs.getString("NAME");

Member member = new Member();
member.setMemberId(memberId);
member.setName(name);
...

이번엔 회원 등록 기능을 개발한다.

  • 회원 등록용 SQL을 작성한다.

String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME) VALUES(?,?)";
  • 회원 객체의 값을 꺼내서 등록 SQL에 전달한다.

pstmt.setString(1, member.getMemberId());
pstmt.setString(2, member.getName());
  • JDBC API를 사용해서 SQL을 실행한다.

pstmt.executeUpdate(sql);

회원 객체를 데이터베이스가 아닌 자바 컬렉션에 보관한다면 어떨까? 컬렉션은 다음 한 줄로 객체를 저장할 수 있다.

list.add(member);

따라서 개발자가 객체지향 애플리케이션과 데이터베이스 중간에서 SQL과 JDBC API를 사용해서 변환 작업을 직접 해주어야 한다.

문제는 객체를 데이터베이스에 CRUD하려면 너무 많은 SQL과 JDBC API를 코드로 작성해야 한다는 점이다. 데이터 접근 계층(DAO)을 개발하는 일은 이렇듯 지루함과 반복의 연속이다.

SQL에 의존적인 개발

회원 객체를 관리하는 MemberDAO를 완성하고 애플리케이션의 나머지 기능도 개발을 완료했다. 그런데 갑자기 회원의 연락처도 함께 저장해 달라는 요구사항이 추가되었다.

등록 코드 변경

  • 회원 클래스에 연락처 필드 추가

public class Member {
  private String memberId;
  private String name;
  private String tel; // 추가
  ...
}
  • 등록 SQL을 수정

String sql = "INSERT INTO MEMBER(MEMBER_ID, NAME, TEL) VALUES(?,?,?)";
  • 회원 객체의 연락처 값을 꺼내서 등록 SQL에 전달한다.

pstmt.setString(3, member.getTel());

조회 코드 변경

다음으로 회원 조회 화면에서 모든 연락처의 값이 null로 출력되었다. 생각해보니 조회 SQL에 연락처 컬럼을 추가하지 않았다.

  • 조회용 SQL을 수정

SELECT MEMBER_ID, NAME, TEL FROM MEMBER WHERE MEMBER_ID = ?
  • 연락처의 조회 결과를 Member 객체에 추가로 매핑한다.

String tel = rs.getString("TEL");
member.setTel(tel);

연관된 객체

회원은 어떤 한 팀에 필수로 소속되어야 한다는 요구사항이 추가되었다. Member 객체에 team 필드가 추가되어 있었다. 그러나 코드를 실행해보니 member.getTeam()의 값이 항상 null이 나오고 있었다.

memberDAO 코드를 열어보니 회원을 출력할 때 사용하는 find() 메소드는 회원만 조회하는 SQL을 그대로 유지하였다. 그래서 findWithTeam() 메서드를 추가하였다.

  • 연관 팀 정보를 가져오는 SQL 추가

SELECT M.MEMBER_ID, M.NAME, M.TEL, T.TEAM_ID, T.TEAM_NAME
FROM MEMBER M
JOIN TEAM T
  ON M.TEAM_ID = T.TEAM_ID

위의 사례에서 보듯이 Member 객체가 연관된 Team 객체를 사용할 수 있을지 없을지는 전적으로 사용하는 SQL에 달려 있다. 이런 방식의 가장 큰 문제는 데이터 접근 계층을 사용해서 SQL을 숨겨도 어쩔 수 없이 DAO를 열어서 어떤 SQL이 실행 되는지 확인해야 한다는 점이다.

이렇듯 SQL에 모든 것을 의존하는 상황에서는 개발자들이 엔티티를 신뢰하고 사용할 수 없다. 대신에 DAO를 열어서 어떤 SQL이 실행되고 어떤 객체 들이 함께 조회 되는지 일일이 확인해야 한다.

앞의 문제점을 요약하면 다음과 같다.

  • 진정한 의미의 계층 분할이 어렵다.

  • 엔티티를 신뢰할 수 없다.

  • SQL에 의존적인 개발을 피하기 어렵다.

JPA와 문제 해결

JPA를 사용하면 객체를 데이터베이스에 저장하고 관리할 때, 개발자가 직접 SQL을 작성하는 것이 아니라 JPA가 제공하는 API를 사용하면 된다. 그러면 JPA가 개발자 대신에 적절한 SQL을 생성해서 데이터베이스에 전달한다.

이전에 구현하였던 애플리케이션의 기능을 JPA를 사용해서 구현 해보자.

  • 조회 기능

String memberId = "helloId";
Member member = jpa.find(Member.class, memberId); // 조회
  • 수정 기능

Member member = jpa.find(Member.class, memberId);
member.setName("이름변경"); // 수정
  • 연관된 객체 조회

Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam(); // 연관된 객체 조회

패러다임의 불일치

비즈니스 요구사항을 정의한 도메인 모델도 객체로 모델링하면 객체지향 언어가 가진 장점들을 활용할 수 있다. 문제는 이렇게 정의한 도메인 모델을 저장할 때 발생한다.

  • 객체의 기능은 클래스에 정의되어 있으므로 객체 인스턴스의 상태인 속성만 저장 했다가 필요할 때 불러 와서 복구하면 된다.

  • 관계형 데이터베이스는 데이터 중심으로 구조화되어 있고, 집합적인 사고를 요구한다. 그리고 객체 지향에서 이야기하는 추상화, 상속, 다형성 같은 개념이 없다.

객체와 관계형 데이터베이스는 지향하는 목적이 서로 다르므로 둘의 기능과 표현 방법도 다르다. 이것을 객체와 관계형 데이터베이스의 패러다임 불일치 문제라 한다. 따라서 객체 구조를 테이블 구조에 저장하는 데는 한계가 있다.

문제는 이런 객체와 관계형 데이터베이스 사이의 패러다임 불일치 문제를 해결하는 데 너무 많은 시간과 코드를 소비하는 데 있다.

상속

객체는 상속 이라는 기능을 가지고 있지만 테이블은 상속 이라는 기능이 없다.

JDBC를 이용한 상속 구현

JDBC API를 사용해서 상속 관계의 객체를 저장 하려면 부모 객체에서 부모 데이터만 꺼내서 INSERT SQL을 작성하고 자식 객체에서 자식 데이터만 꺼내서 INSERT SQL을 별도로 작성해야 한다.

INSERT INFO ITEM ...
INSERT INTO ALBUM ...

JPA를 이용한 상속 구현

JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다. 개발자는 마치 자바 컬렉션에 객체를 저장 하듯이 JPA에게 객체를 저장하면 된다.

jpa.persist(album);

연관관계

객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다. 반면에 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을 조회한다.

  • 테이블에 맞춘 객체 모델

class Member {
  String id; // MEMBER_ID 컬럼 사용
  Long temId; // TEAM_ID FK 컬럼 사용
  String username; // USERNAME 컬럼 사용
}

class Team {
  Long id; // TEAM_ID PK 사용
  String name; // NAME 컬럼 사용
}

그런데 여기서 TEAM_ID 외래 키의 값을 그대로 보관하는 teamId 필드에는 문제가 있다. 객체는 연관된 객체의 참조를 보관해야 다음처럼 참조를 통해 연관된 객체를 찾을 수 있어야 하는데 하지 못하게 된다.

Team team = member.getTeam();

이런 방식을 따르면 좋은 객체 모델링은 기대하기 어렵고 결국 객체 지향의 특징을 잃어버리게 된다.

엔티티를 직접 참조하는 것과 간접 참조하는 것에 대한 장단점이 무엇이 있을까?

직접 참조

  • 장점 : 연관된 데이터를 한번에 추출할 수 있다.

  • 단점 : 연관된 데이터에 대한 수정이 발생할 경우 영향의 범위가 커질 수 있다.

간접 참조

  • 장점 : 복잡도를 낮출 수 있고, 응집도를 높이고 결합도를 낮출 수 있다.

  • 단점 : 연관된 데이터를 한번에 추출 하려면 구현해야 하는 로직이 복잡하다.

  • 참조를 사용하는 객체 모델

class Member {
  String id; // MEMBER_ID 컬럼 사용
  Long temId; // TEAM_ID FK 컬럼 사용
  Team team; // 참조로 연관관계를 맺는다.
}

class Team {
  Long id; // TEAM_ID PK 사용
  String name; // NAME 컬럼 사용
}

JDBC를 이용한 연관 관계 구현

만약 JDBC로 저장하는 로직을 만들기 위해서는 team 필드를 TEAM_ID 외래 키 값으로 변환해야 한다.

member.getId(); // MEMBER_ID PK에 저장
member.getTeam().getId(); // TEAM_ID FK에 저장
member.getUsername(); // USERNAME 컬럼에 저장

또는 조회하는 로직에도 객체를 생성하고 연관관계를 설정해서 반환하는 로직이 필요하다.

public Member find(String memberId) {
  // SQL 실행
  ...
  Member member = new Member();
  ...

  // 데이터베이스에서 조회한 회원 관련 정보를 모두 입력
  Team team = new Team();
  ...
  // 데이터베이스에서 조회한 팀 관련 정보를 모두 입력

  // 회원과 팀 관계 설정
  member.setTeam(team);
  return member;
}

JPA와 연관관계 구현

개발자는 회원과 팀의 관계를 설정하고 회원 객체를 저장하면 된다. JPA는 team의 참조를 외래 키로 변환해서 적절한 INSERT SQL을 데이터베이스에 전달한다.

member.setTeam(team);
jpa.persist(member);

객체를 조회할 때 외래 키를 참조로 변환하는 일도 JPA가 처리 해준다.

Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();

객체 그래프 탐색

SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해진다. 이것은 객체지향 개발자에겐 너무 큰 제약이다. 왜냐하면 비즈니스 로직에 따라 사용하는 객체 그래프가 다른데 언제 끊어질지 모를 객체 그래프를 함부로 탐색할 수는 없기 때문이다.

결국, 어디까지 객체 그래프 탐색이 가능한지 알아보려면 데이터 접근 계층인 DAO를 열어서 SQL을 직접 확인해야 한다.

class MemberService {
  ...
  public void process() {
    Member member = memberDAO.find(memberId);
    member.getTeam(); // member->team 객체 그래프 탐색이 가능한가?
    member.getOrder().getDelivery(); // ???
  }
}

JPA와 객체 그래프 탐색

JPA를 사용하면 연관된 객체를 신뢰하고 마음껏 조회할 수 있다. 이 기능은 실제 객체를 사용하는 기점까지 데이터베이스 조회를 미룬다고 해서 지연 로딩이라 한다.

// 처음 조회 시점에 SELECT MEMBER SQL
Member member = jpa.find(Member.class, memberId);

Order order = member.getOrder();
order.getOrderDate(); // Order를 사용하는 시점에 SELECT ORDER SQL

여기서 마지막 줄의 order.getOrderDate() 같이 실제 Order 객체를 사용하는 시점에 JPA는 데이터베이스에서 ORDER 테이블을 조회한다.

비교

데이터베이스는 기본 키의 값으로 각 로우(row)를 구분한다. 반면에 객체는 동일성(indentity)비교와 동등성(equality) 비교라는 두 가지 비교 방법이 있다.

  • 동일성 비교는 == 비교다. 객체 인스턴스의 주소 값을 비교한다.

  • 동등성 비교는 equals() 메소드를 사용해서 객체 내부의 값을 비교한다.

JDBC로 구현한 비교

class MemberDAO {
  public Member getMember(String memberId) {
    String sql = "SELECT * FROM MEMBER WHERE MEMBER_ID + ?";
    ...

    // JDBC API, SQL 실행
    return new Member(...);
  }
}

String memberId = "100";
Member member1 = memberDAO.getMember(memberId);
Member member2 = memberDAO.getMember(memberId);

member1 == member2; // false

데이터베이스의 같은 로우를 조회했지만 객체의 동일성 비교에는 실패한다.

JPA로 구현한 비교

JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다. 그러므로 다음 코드에서 member1과 member2는 동일성 비교에 성공한다.

String memberId = "100";
Member member1 = jpa.find(Member.class,memberId);
Member member2 = jpa.find(Member.class,memberId);

member1 == member2; // true

정리

객체 모델과 관계형 데이터베이스 모델은 지향하는 패러다임이 서로 다르다. 더 어려운 문제는 객체 지향 애플리케이션 답게 정교한 객체 모델링을 할수록 패러다임의 불일치 문제가 더 커진다는 점이다. 이는 결국 객체 모델링은 힘을 잃고 점점 데이터 중심의 모델로 변해간다. JPA는 패러다임의 불일치 문제를 해결 해주고 정교한 객체 모델링 유지하게 도와준다.

데이터 주도 설계와 도메인 주도 설계의 장단점은 무엇일까?

JPA란 무엇인가?

JPA(Java Persistence API)는 자바 진영의 ORM 기술 표준이다.

그렇다면 ORM이란 무엇일까?

ORM(Object-Relational Mapping)은 이름 그대로 객체와 관계형 데이터베이스를 매핑 한다는 뜻이다. ORM 프레임워크는 객체와 테이블을 매핑 해서 패러다임의 불일치 문제를 개발자 대신 해결해준다.

따라서 객체 측면에서는 정교한 객체 모델링을 할 수 있고 관계형 데이터베이스는 데이터베이스에 맞도록 모델링하면 된다. 그리고 둘을 어떻게 매핑 해야 하는지 매핑 방법만 ORM 프레임워크에게 알려주면 된다.

왜 JPA를 사용해야 하는가?

  • 생산성

    이전에 DAO에서 작업하던 지루하고 반복적인 일은 JPA가 대신 처리 해준다. 이런 기능들을 사용하면 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전시킬 수 있다.

  • 유지보수

    이전엔 엔티티 필드 하나만 수정해도 관련된 DAO 로직의 SQL문을 모두 변경해야 했다. 반면에 JPA는 대신 처리해주므로 필드를 추가하거나 삭제해도 수정해야 할 코드가 줄어든다.

  • 패러다임의 불일치 해결

  • 성능

    JPA는 애플리케이션과 데이터베이스 사이에 동작하여 최적화 관점에서 시도해 볼 수 있는 것들이 많다. 예를 들어 동일한 조건으로 조회 했을 경우엔 SELECT SQL을 한 번만 데이터베이스에 전달하고 두 번째 조회한 회원 객체는 재사용할 수 있다.

  • 데이터 접근 추상화와 벤더 독립성

    관계형 데이터베이스는 같은 기능도 벤더 마다 사용법이 다른 경우가 많다. 단적인 예로 페이징 처리는 데이터베이스마다 달라서 사용법을 각각 배워야 한다. 결국 애플리케이션은 데이터베이스에 종속되어 변경 하기는 매우 어렵다. **JPA는 추상화된 데이터 접근 계층을 제공해서 애플리케이션이 특정 데이터베이스 기술에 종속되지 않도록 할 수 있다.**

ORM에 대한 궁금증과 오해

JPA를 사용하면 성능이 느리지 않나요?

JPA는 다양한 성능 최적화 기능을 제공해서 잘 이해하고 사용하면 SQL을 직접 사용할 때보다 더 좋은 성능을 낼 수도 있다. 하지만 JPA를 잘 이해하지 못하고 사용하면 N+1 같은 문제로 인해 심각한 성능 저하가 발생할 수 있다.

JPA N+1 문제란 무엇인가? 해결 방법은 또한 무엇인가?

통계 쿼리처럼 매우 복잡한 SQL은 어떻게 하나요?

JPA는 통계 쿼리 같이 복잡한 쿼리보다는 실시간 처리용 쿼리에 더 최적화되어 있다. 따라서 통계용은 네이티브 SQL을 사용하거나 마이바티스나 스프링의 jdbcTemplate 같은 SQL 매퍼 형태의 프레임워크를 혼용하는 것도 좋은 방법이다.

PreviousJAVA ORM JPANext2장 JPA 시작

Last updated 5 years ago

Was this helpful?

jpa1