김영한님의 JPA 프로그래밍 기본기 강의
- 회사에 입사한 지 얼마 안됬을 때 사수가 ORM에 대해 한번 훑어보라며, 책을 추천해준 적이 있었다.
그 때는 정신도 없어서 나중에 봐야겠다... 라고만 생각했는데, 인프런 인강과 다른 블로그에 있는 스프링부트 실습을 따라하면서 JPA 간단하게 경험해볼 수 있었다. 공부해보고 싶기도하고, 토이프로젝트에 적용시켜보고 싶기도 해서 찾아보니 사수가 추천해준 책의 저자께서 직접 설명하는 기본 강의가 유튜브에 있었다!!
(19.12.01 인프런에 JPA강의도 있다.)
실습을 통해 이해할 수 있는 좋은 강의였고, 책이나 인프런강의를 통해 더 자세히 공부도 해보고 싶다!
1강 - JPA 소개
- JPA 기본기 교육임.
- 애플리케이션은 객체지향언어로 짬. but 데이터베이스는 관계형DB을 사용하고 있음.
- SQL 중심적인 개발이 이루어짐 -> 무한 반복, 지루한 코드
- 객체에 필드가 추가될 때? -> 모든 쿼리의 수정이 필요하게 됨
- 앤티티 신뢰 문제 -> DAO 에서 우리가 필요한 것들을 가져오는게 맞는 지 확인해야함
- 진정한 의미의 계층 분할이 어렵다
- SQL에 의존적인개발을 피하기 어렵다
- 객체를 저장할 때 ?? -> 현실적인 대안 관계형 DB
- 객체와 관계형 데이버베이스의 차이
-> 객체답게 모델링할 수록 매핑작업이 늘어남(코드가 늘어난다)
- Java Persistence API : 자바 진영의 ORM 기술 표준
- ORM
- Object-relational mapping(객체 관계 매핑)
- 객체는 객체대로 설계
- 관계형 데이터베이스는 관계형 데이터베이스대로 설계
- ORM프레임워크가 중간에서 매핑
- 대중적인 언어에는 대부분 ORM기술이 존재함
- JPA를 왜 사용해야 하는가?
- SQL 중심적인 개발에서 객체 중심으로 개발
- 생산성 : 간단한 사용가능
- 유지보수 : 컬러 변동 시 모든 sql 수정 -> 필드만 추가하면됨(sql은 jpa가 처리해줌)
- 패러다임의 불일치 해결
- 성능 : 최적화기능(캐시,동일성보장 / 트랜잭션을 지원하는 쓰기 지연 / 지연 로딩)
- 데이터 접근 추상화와 벤더 독립성
- 표준
- ORM은 객체와 RDB 두 기둥 위에 있는 기술
2강 - JPA 기초와 매핑
- 이번 강의는 실습 위주
- JPA에서는 크게 중요한 것 두 가지
- 객체와 관계형DB와 매핑하는 과정(설계)
- 실제 JPA가 어떻게 동작하는지 (영속성 컨텍스 트)
- H2 데이터 베이스
- 최고의 실습용 DB
- 가볍다 (1.5M) (자바에 올려서 배포하기도 한다고 함)
- 웹용 쿼리 툴 제공
- MySQL, Oracle 데이터베이스 시뮬레이션 기능
- 시퀀스, AUTO INCREMENT 기능 지원
- 메이븐
- 자바 라이브러리, 빌드 관리
- 라이브러리 자동 다운로드 및 의존성 관리
- @Entity : JPA가 관리할 객체 - 엔티티라 한다. (DB랑 매핑할 클래스)
- @Id : DB PK와 매핑 할 필드
- persistence.xml
- JPA 설정 파일
- /META_INF/persistence.xml 위치
- javax.persistence로 시작 : JPA 표준 속성
- hibernate로 시작 : 하이버네이트 전용 속성
- 데이터베이스 방언
- JPA는 특정 데이터베이스에 종속적이지 않은 기술
- 각각의 데이터베이스가 제공하는 SQL 문법과 함수는 조금씩 다르다.
- 가변문자 : mysql은 varchar, oracle은 varchar2
- 문자열을 자르는 함수 : SQL 표준은 SUBSTRING(), oracle은 SUBSTR()
- 페이징 : mysqldms limit , oracle ROWNUM
- 방언: SQL 표준을 지키지 않거나 특정 데이터베이스만의 고유한 기능
- Dialect 를 통해 (MySQLDialect, OracleDialect, H2Dialect 등)으로 각 DB용 SQL 생성
- hibernate.dialect 속성에 지정
- H2: org.hibernate.dialect.H2Dialect
- Oracle 10g : org.hibernate.dialect.Oracle10gDialect
- Mysql : org.hibernate.dialect.MySQL5InnoDBDialect
- 하이버네이트는 45가지 방언 지원
- 어플리케이션 개발
- 준비
- pom.xml dependency 입력
- hibernate-entitymanager : 관련 라이브러리 포함되어있음. hibernate core, jpa 2.1 등등 가져옴
- persistence.xml : jpa이 실행할 때 이파일을 읽어서 DB에 붙고 등등 (부트에선 이 설정파일 없이 알아서 해줌)
- 앤티티 매니저 팩토리 설정
- 앤티티 매니저 설정
- 트랜잭션
- 비즈니스 로직(CRUD)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
- 주의
- 이 EntityManagerFactory 는 어플리케이션 띄울 때 한번 로딩해야함. 사용자가 실제 비즈니스로직을 태울 때마다 팩토리에서 앤티티매니저를 꺼내서 써야함. 앤티티매니저가 JPA라고 보면됨
- 앤티티 매니저는 쓰레드간에 공유하면 안된다(사용하고 버려야한다) (데이터베이스 커넥션이 공유될 수 있음.)
- JPA의 모든 데이터 변경은 트랜잭션 안에서 실행
3강 - 필드와 컬럼 매핑
- jpa를 통해 테이블을 직접 만들수가 있음.
- 데이터베이스 스키마 자동 생성하기
- DDL을 애플리케이션 실행 시점에 자동 생성
- 테이블 중심 -> 객체중심
- DB에 맞는 적잘한 DDL 생성
- 이렇게 생성된 DDL은 개 장비에서만 사용
- 생성된 DDL은 운영서버에서는 사용하지 않거나, 적절히 다듬은 후 사용
- hibernate.hbm2ddl.auto
- create : 기존테이블 삭제 후 다시 생(DROP + CREATE)
- create-drop : create 와 같으나 종료시점에 테이블 DROP
- update : 변경분만 반영 (운영 DB에는 사용하면 안됨)
- validate : 엔티티와 테이블이 정상 매핑되었는지 확인
- none : 사용하지 않음
- 운영장비에는 절대 create, create-drop, update 사용하면 안된다.
- 개발초기단계 create or update
- 테스트서버 update or validate
- 스테이징과 운영 validate or none
- 매핑 어노테이션
- @Column (DB의 컬럼이름)
- 가장많이 사용됨
- name : 필드와 매핑할 테이블의 컬럼이름
- insertalbe, updatalbe : 읽기전용
- nullalbe : null 허용여부 결정 , DDL 생성 시 사용
- unique : 유니크 제약조건, DDL 생성 시 사용
- columnDefinition, length, precision, scale(DDL)
- @Temporal (시간관련)
- 날짜 타입 매핑
- @Temporal(TemporalType.DATE)
- localDateTime 써도됨
- @Enumerated
- enum타입.
- 현업에서는 꼭 String을 써야함 (EnumType.STRING). 기본은 ORDINAL(순서임))
- @Lob
- CLOB, BLOB 매핑
- CLOB : String, char[], java.sql.CLOB
- BLOB : byte[], java.sql.BLOB
- 이 어노테이션을 String에 쓰면 CLOB, byte에 쓰면 BLOB이 됨
- @Transient
- 이 필드는 매핑하지 않는다.
- 애플리케이션에서 DB에 저장하지 않는 필드
- 식별자 매핑 어노테이션
- @Id (직접)
- IDENTITY : 데이터베이스에 위임. MYSQL
- SEQUENCE : 데이터베이스 스퀀스 오브젝트 사용. ORACLE
- TABLE : 키 생성용 테이블 사용, 모든 DB에서 사용
- AUTO : 방언에 따라 자동 지정, 기본 값
- @GeneratedValue
- 권장하는 식별자
- 기본 키 제약조건 : null 아님, 유일, 변하면 안된다.
- 미래까지 이 조건을 만족하는 자연키는 찾기 어렵다. 대리키(대체키)를 사용하자.
- 예를 들어 주민등록번호도 기본 키로 적절하지 않다.
- 권장 : Long + 대체키 + 키 생성전략 사용
4강 - 연관관계 매핑
- '객체지향 설계의 목표는 자율적인 객체들의 협력 공동체 를 만드는 것이다.'
- 객체를 테이블에 맞추어 데이터 중심으로 모델링하면, 협력 관계 만들 수 없다.
- 테이블은 외래키로 조인을 사용해서 연관된 테이블을 찾는다.
- 객체는 참조를 사용해서 연관된 객체를 찾는다.
- 테이블과 객체 사이에는 이런 큰 간격이 있다.
- 연관관계 매핑 이론
5강 - 양방향 매핑
- 연관관계의 주인과 mappedBy
- mappedBy : JPA의 멘붕클래스1
- mappedBy는 처음에는 이해하기 어렵다.
- 객체와 테이블간에 연관관계를 맺는 차이를 이해해야 한다.
- 객체와 테이블이 관계를 맺는 차이
- 객체 연관관계
- 회원 -> 팀 연관관계 1개 (단방향)
- 팀 -> 회원 연관관계 1개 (단방향)
- 테이블 연관관계
- 객체의 양방향 관계
- 객체의 양방향관계는 사실 양방향관계가 아니라 서로 다른 단방향 관계 2개이다.
- 객체를 양방향으로 참조하려면 단방향 연관관계를 2개 만들어야 한다.
- 테이블의 양방향 연관관계
- 테이블은 외래 키 하나로 두 테이블의 연관관계를 관리
- MEMBER.TEAM_ID 외래 키 하나로 양방향 연관관계 가짐 (양쪽으로 조인할 수 있다.)
- 연관관계의 주인 (Owner)
- 양방향 매핑 규칙
- 객체의 두 관계중 하나를 연관관계의 주인으로 지정
- 연관관계의 주인만이 외래 키를 관리(등록,수정)
- 주인이 아닌 쪽은 읽기만 가능
- 주인은 mappedBy 속성 X
- 주인이 아니면 mappedBy 속성으로 주인 지정
- 누구를 주인으로?
- 외래 키가 있는 곳을 주인으로 정해라
- 여기서는 Member.team 연관관계의 주인
- 양방향 매핑 시 연관관계의 주인에 값을 입력해야 한다.
- 양방향 매핑의 장점
- 단방향 매핑만으로도 이미 연관관계 매핑은 완료
- 양방향 매핑은 반대 방향으로 조회 (객체 그래프 탐색) 기능이 추가 된 것 뿐
- JPQL에서 역방향으로 탐색할 일이 많음
- 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지않음)
- 연관관계 매핑 어노테이션
- 다대일(@ManyToOne)
- 일대다(@OneToMany)
- 일대일(@OneToOne)
- 다대다(@ManyToMany) (현업애선 잘 안씀)
- @JoinColumn, @JoinTable
- 상속 관계 매핑 어노테이션 (설명은 책 참조..)
- @Inheritance
- @DiscriminatorColumn
- @DiscriminatorValue
- @MappedSuperclass (매핑 속성만 상속)
- 복합키 어노테이션
- @IdClass
- @EmbeddedId
- @Embeddalbe
- @Mapsld
6강 - JPA 내부 구조
- 영속성 컨텍스트
- JPA를 이해하는데 가장 중요한 용어
- "엔티티를 영구 저장하는 환경" 이라는 뜻
- EntityManager.persist(entity);
- 앤티티 매니저 팩토리와 앤티티 매니저
- 앤티티 매니저? 영속성 컨테스트?
- 영속성 컨텍스트는 논리적인 개념
- 눈에 보이지 않는다
- 앤티티 매니저를 통해서 영속성 컨텍스트에 접근
- J2SE 환경 : 엔티티 매니저와 영속성 컨텍스트가 1:1
- J2EE, 스프링 프레임워크 같은 컨테이너 환경 : 앤티티 매니저와 영속성 컨텍스트가 N:1
- 앤티티의 생명주기
- 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태 (객체를 생성만 한 상태)
- 영속(managed) : 영속성 컨텍스트에 저장된 상태 (객체를 저장한 상태 em.persist(member) )
- 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태 (em.detach)
- 삭제(removed) : 삭제된 상태 (em.remove(member) )
- 영속성 컨텍스트의 이점
- 1차 캐시 (DB가기 전 캐시에서 조회)
- 동일성(identity) 보장 (1차 캐시때문)
- 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
- 변경감지(Dirty Checking) (스냅샷과 비교하여 감지)
- 지연 로딩(Lazy Loading)
- 플러시 : 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
- 플러시 발생
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송( 등록, 수정, 삭제 쿼리)
- 영속성 컨텍스트를 플러시하는 방법
- em.flush() : 직접호출
- 트랜잭션 커밋 : 플러시 자동 호출
- JPQL 쿼리 실행 : 플러시 자동 호출(mybatis 나 스프링 jdbc 를 사용할 경우 flush 를 써야줘야 함)
- 플러시는!
- 영속성 컨텍스트를 비우지 않음 (clear가 비움)
- 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
- 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화 하면 됨
- 준영속 상태
- 영속 -> 준영속
- 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
- 영속성 컨텍스트가 제공하는 기능을 사용 못함
- 준영속 상태로 만드는 방법
- em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
- em.celar() : 영속성 컨텍스트를 완전히 초기화
- em.close() : 영속성 컨텍스트를 종료
- 프록시와 즉시로딩, 지연로딩
- Member를 조회할 때 Team도 함께 조회해야 할까? : 지연로딩 LAZY을 사용해서 프록시(가짜객체)로 조회
- 실제 team을 사용하는 시점에 초기화(DB조회)
- Member와 Team을 자주 함께 사용한다면? : 즉시 로딩 EAGER를 사용해서 함계 조회 (현업에선 보통 LAZY.)
- 프록시와 즉시로딩 주의
- 가급적 지연 로딩을 사용
- 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생
- 즉시 로딩은 JPQL에서 N+1 문제를 일으킨다.
- @ManyToOne, @OneToOne은 기본이 즉시로딩 -> LAZY로 설정
- @OneToMany, @ManyToMany는 기본이 지연로딩
7강 - JPA와 객체지향 쿼리
- JPA는 다양한 쿼리방법을 지원
- JPQL
- JPA Criteria
- QueryDSL
- 네이티브 SQL
- JDBC API 직접 사용, MyBaits, SpringJdbcTemplate 함께 사용
- JPQL 소개 (객체 대상으로 조회)
- 가장 단순한 조회 방법
- EntityManager.find()
- 객체 그래프 탐색(a.getB().getC())
- JPA를 사용하면 앤티티 객체 중심으로 개발
- 문제는 검색쿼리
- 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색
- 모든 DB데이터를 객체로 변환해서 검색하는 것은 불가능
- 애플리케이션이 필요한 데이터만 DB에서 불러오려면 결국 검색 조건이 포함된 SQL이 필요
- JPA는 SQL을 추상화한 JPQL이라는 객체 지향 쿼리 언어 제공
- SQL과 문법 유사 SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 지원
- JPQL은 엔티티 객체를 대상으로 쿼리 vs SQL은 데이터베이스 테이블을 대상으로 쿼리
- 테이블이 아닌 객체를 대상으로 검색하는 객체 지향 쿼리
- SQL을 추상화해서 특정 데이터베이스 SQL에 의존 X
- JPQL을 한마디로 정의하면 객체지향 SQL
- JPQL 문법
- select m from Member m where m.age > 18
- 엔티티와 속성은 대문자 구문 (Member, username)
- JPQL 키워드는 대문자 구분안함 (SELECT, FROM , where)
- 엔티티 이름을 사용, 테이블 이름이 아님(Member)
- 별칭은 필수 (m)
- 결과 조회 API
- query.getResultList() : 결과가 하나 이상, 리스트 반환
- query.getSingleResult() : 결과가 정확히 하나, 단일 객체 반환 (정확히 하나가 아니면 예외 발생)
- 파라미터 바인딩 - 이름기준, 위치 기준
- where m.username=:username
- query.setParameter("username", usernameParam);
- where m.username=?1
- query.setParameter(1, usernameParam);
- 프로젝션
- SEELCT m FROM Member m -> 엔티티 프로젝션
- SELECT m.team FROM Member m -> 엔티티 프로젝션
- SELECT username,age FROM Member m -> 단순 값 프로젝션
- new 명령어 : 단순 값을 DTO로 바로 조회
- SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member m
- DISTINCT는 중복제거
- 페이징 API
- JPA는 페이징을 다음 두 API로 추상화
- setFirstResult(int startPosition) : 조회 시작 위치(0부터 시작)
- setMaxResults(int maxResult) : 조회할 데이터 수
- 집합과 정렬 (COUNT, SUM, AVG, MAX, MIN, GROUP BY, HAVING, ORDER BY)
- 조인
- 내부조인 : SELECT m FROM Member m [INNER] JOIN m.team t
- 외부조인 : SELECT m FROM Member m LEFT [OUTER] JOIN m.team t
- 세타조인 : SELECT count(m) FROM Member m, Team t WHERE m.username = t.name
- 페치조인 (많이 씀. LAZY일 경우 필요한경우 이걸로 한번에 조회가능) (List같은거 뿌릴때 안하면 lazy로딩 계속 나감->성능저하)
- 인티티 객체 그래프를 한번에 조회하는 방법
- 별칭을 사용할 수 없다.
- JPQL : SELECT m FROM Member m join fetch m.team
- SQL : SELECT M.* , T.* , FROM MEMBER T INNER JOIN TEAM t ON M.TEAM_ID = T.ID
- JPQL 기타
- 서브쿼리 지원
- EXISTS, IN
- BETWEEN, LIKE, IS NULL
- JPQL 기본함수
- CONCAT
- SUBSTRING
- TRIM
- LOWER, UPPER
- LENGTH
- LOCATE
- ABS, SQRT, MOD
- SIZE, INDEX(JPA용도)
- CASE
- 사용자 정의 함수 호출
- Named 쿼리 - 정적 쿼리
- 미리 정의해서 이름을 부여해두고 사용하는 JPQL
- 어노테이션, XML에 정의 (요즘엔 어노테이션으로 많이 씀 )
- 애플리케이션 로딩 시점에 초기화 후 재사용
- 애플리케이션 로딩 시점에 쿼리를 검증 (오타 등 문법을 애플리케이션 로딩 시 검증가능)
8강 - Spring Data JPA와 QueryDSL의 이해
- JPA 기반 프로젝트
- Spring Data JPA
- 반복되는 CRUD 문제를 세련된 방법을 해결
- 개발자는 인터페이스만 작성
- 스프링 데이터 JPA가 구현 객체를 동적으로 생성해서 주입
- JpaRepository 인터페이스 (공통 CRUD 제공, 제네릭 <엔티티, 식별자> 로 설정)
- 메서드 이름만으로 JPQL 쿼리 생성 (ㅁㅊ), 정렬, 페이징
Pagable page = new PageRequest(1,20,new Sort...); Page<Member> result = memeberRepository.findMyName("hello", page);
int total = result.getTotalElements();
List<Member> members = result.getContent();
- @Query, JPQL 정의 (직접 정의)
- Web 페이징과 정렬기능
- 컨트롤러에서 페이징 처리 객체를 바로 받을 수 있음
- Web 도메인 클래스 컨버터 기능
- QueryDSL
- Spring JPA, QueryDSL 배민에서 쓰고있음. 저자가 추천함
- 실무 경험 공유
- 주문,결제,정상 모두 JPA로 개발됨
- 테이블 중심에서 객체 중심으로 개발 패러다임이 변화
- 유연한 데이터베이스 변경의 장점과 테스트
- Junit 통합 테스트 시에 H2 DB 메모리 로드
- 로컬 PC에는 H2 DB 서버모드로 실행
- 개발 운영은 MySQL, Orcle
- 데이터베이스 변경 경험(개발 도중 MySQL > Oracle 바뀐적도 있다.)
- 테스트, 통합 테스트시에는 CRUD는 믿고 간다.
- 빠른 오류 발견
- 최소환 쿼리 문법 실수나 오류는 거의 발생하지 않는다.
- 대분 비즈니스 로직 오류
- 실무 경험 성능
- JPA 자체로 인한 성능 저하 이슈는 거의 없음
- 성능 이슈 대부분은 JPA를 잘 이해하지 못해서 발생
- 즉시 로딩 : 쿼리가 튐 > 지연로딩으로 변경
- N+1 문제 > 대부분 페치 조인으로 해결
- 내부 파서 문제: 2000줄 짜리 동적 쿼리 생성 1초
- 정적쿼리로 변경(하이버네이트는 파싱된 결과 재사용)
- 실무 경험 생산성
- 단순 코딩 시간 줄어듬 > 개발 생성상 향상 > 잉여시간 발생
- 비즈니스 로직 작성 시 흐름이 끊기지 않음
- 남는 시간에 더많은 테스트 작성
- 남는 시간에 기술 공부
- 남는 시간에 코드 금칠...
- 팀원 대부분 다시는 과거로 돌아가고 싶어하지 않음
- 많이 하는 질문
- ORM 프레임워크를 사용하면 SQL과 데이터베이스는 잘 몰라도 되나요? (둘다 잘해야함 )
- 성능이 느리진 않나요? (잘쓰면 최적화할 수 있는 부분 많음)
- 통계쿼리 처럼 매우 복잡한 SQL은 어떻게 하나요? (QueryDSL, 네이티브 쿼리)
- MyBatis와 어떤 차이가 있나요? (쿼리를 직접 짜야한다)
- 하이버네이트 프레임워크를 신뢰할 수 있나요? (쿠팡 등 많이 씀)
- 제 주위에는 MyBatis(iBatis, myBatis)만 사용하는데요?
- 학습곡선이 높다고 하던데요? (빡세게 공부하고 편하게 살자)
- 팀 서버 기술 스택
- Java8
- Spring Framework(boot)
- JPA, Hibernate
- Spring Data JPA
- QueryDSL
- Juint, Spock(Test)