JAVA ORM 표준 JPA 프로그래밍 - 김영한님 책 스터디 개인 1장 2장 정리
JPA
JPA는 Java Persistence API의 줄임말로서, ORM(Object-Relational Mapping, 객체-관계형 매핑) 프레임 워크이다.
JPA는 자바 진영의 ORM 기술 표준이다
JPA는 자바 애플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이며,
이 인터페이스를 구현한 구현체가 우리가 흔히 사용하는 하이버네이트(hibernate)이다.
또한 JPA 인터페이스를 구현한 구현체는 OpenJPA, EclipseLink, ToLink 등과 같은 구현체가 있다.
JPA는 특정 기능을 하는 라이브러리가 아니고, 자바 어플리케이션에서 관계형 데이터 베이스를 어떻게 사용해야 하는지 정의하는 한 방법일 뿐이다.
ORM은 RDB를 객체지향적으로 사용하기 위한 기술이다. 관계형 데이터베이스로 정의된 데이터들을 객체로 맵핑해준다고 생각하면 된다.
RDB 테이블은 객체지향적 특징인 상속, 다형성, 레퍼런스, 객체 등이 없고 자바와 같은 언어로 접근하기 쉽지 않다.
때문에 ORM 기술을 사용해 오브젝트와 RDB 사이에 존재하는 개념과 접근을 객체지향적으로 좀더 쉽게 다루기 위한 기술이다.
JPA 는 명세이면서 인터페이스 이기 때문에 구현이 없다.
JPA를 정의한 javax.persistence 패키지는 대부분 interface, enum, annotation, Exception으로 이루어져 있다.
JPA는 지루하고 반복적인 간단한 CRUD SQL을 알아서 처리해준다.
- 다만 복잡한 쿼리같은 경우 native SQL을 사용하거나 querydsl 등을 사용해서 문제를 해결한다.
객체지향적으로 데이터를 관리할 수 있기 때문에 비즈니스 로직에 집중 할 수 있으며, 객체지향 개발이 가능하다.
테이블 생성, 변경, 관리가 쉽다. (JPA를 잘 이해하고 있는 경우)
로직을 쿼리에 집중하기 보다는 객체자체에 집중 할 수 있다.
빠른 개발이 가능하며 유지보수가 쉬워진다.
SQL을 직접 다룰 때 발생하는 문제점
- 애플리케이션을 만들라면 프로그래밍 언어를, 관계형데이터베이스를 다루어서 데이터를 이용할라면 SQL을 사용해야 한다.
기존 JDBC API를 사용하면 String타입으로 SQL을 작성해야하지, (오타가 나서 오류나는 경우 많음)
try-catch-finally로 커넥션 객체를 열었다 닫아줘야하지,
SQL을 실행한 다음, 결과를 담은 ResultSet 객체에서 하나하나꺼내서 객체에 매핑 시켜줘야 한다.
정말 간단한, join도 없고 where문도 복잡하지 않은 단순한 CRUD 기능을 우리는 매번 반복해서 작성해야 한다.
실제로 하나하나 기능 구현을 하다보면 너무나도 SQL에 의존적이며 많은 코드가 나오게 된다.
시간을 공들여서 기능을 개발했더니 수정 요구사항이 들어오면 너무나도 많은 부분을 바꿔야 한다. JSP 처럼
JPA와 문제해결
JPA를 사용하면 객체를 데이터베이스에 저장하고, 관리할 때 개발자가 직접 SQL을 작성하는것이 아니라 JPA가 제공하는 API를 사용하면 된다.
- 모든 쿼리들이 그런것은 아니고, 대부분 간단한 기능들은 쉽게 해결할 수 있다는 것이다.
여기서 나오는 jpa는 EntityManager의 객체 이름이다.
- 저장 기능 - insert
-
jpa.persist(Object); // 저장
-
- 조회 기능 - select
-
jpa.find(Member.class, memberId); // 조회. 멤버 아이디로 멤버 객체를 찾아오라는 것으로 해석된다.
-
- 수정 기능 - update
-
Member member = jpa.find(Member.class, memberId); member.setName("변경할 값"); // 수정. 객체를 찾은 다음 이름만 바꿔주면 된다. 물론 바뀌는데 조건도 있다- 트랜잭션
-
- 연관된 객체 조회 - select , join
-
Member member = jpa.find(Member.class, memberId); Team team = member.getTeam(); // 연관된 객체 조회
-
- Member와 Team이 연관 관계가 맺어져 있고, Member class 안에 Team이 정의되어 있을 때 연관된 객체 조회 가능
패러다임의 불일치
객체지향 프로그래밍은 추상화
, 정보은닉(캡슐화)
, 상속
, 다형성
등 다양한 개발 방법을 제공한다.
비즈니스 요구사항을 정의하여, 비즈니스 로직이 있는 도메인 모델도 객체로 모델링하면 훨씬 쉽고 코드가 깔끔하게 데이터를 다룰 수 있다.
- '도메인'은 해결하고자 하는 문제 영역이라고 할 수 있다. 현실세계의 어떠한 문제를 도메인이라는 모델로 구조화 한것
- 단순히 클래스 다이어그램이나 엔티티가 아니다.
문제는 이렇게 정의한 도메인 모델을 데이터베이스에 저장할때 발생한다.
관계형 데이터 베이스에 객체를 저장하는 것인데,
관계형 데이터베이스는 데이터 중심으로 구조화 되어 있어서 데이터만 저장하면 되는 곳인데
객체는 필드(속성)와 메서드(기능), 상속, 다형성, 추상화 등이 구현되어있다.
자바 언어로 만든 다양한 데이터들을, SQL 언어를 이용한 데이터베이스랑은 지향하는 목적 자체가 다르므로
기능과 표현 방법이 아예 다르기 때문에 문제가 발생하는 것이다.
객체 구조 자체를 테이블에 저장할 수가 없다
이것이 객체지향 언어와 관계형 데이터베이스의 패러다임의 불일치 라고 할 수 있다.
패러다임의 불일치로 인해 발생하는 문제들은 다음과 같다
상속
객체지향 언어는 상속이라는 개념과 기능을 가지고 있지만 테이블은 상속이란 개념이 없다.
- 상속 : 부모의 것을 자식이 물려받아 사용하는것
- postgres같은 데이터베이스는 지원하는 경우가 있지만, 대부분의 관계형 데이터베이스는 상속을 지원하지 않고 객체의 상속과는 약간 다르다.
- 이그림은 객체 상속 모델을 다이어그램으로 그린 것.
- Item이라는 큰 틀로 추상화하고 자식들이 상속받았다고 할 수 있다.
- 이 그림은 관계형 데이터베이스를 슈퍼타입 / 서브타입 관계를 사용해서 그나마 객체 상속과 유사한 형태로 테이블로 만든것
- 슈퍼/서브 타입 데이터 모델 : Extended ER모델이라고도 부른다
- 공통 부분을 슈퍼타입으로 모델링하고, 공통부분으로부터 상속받아 다른 엔티티와 차이가 있는 속성에 대해서는 별도의 서브엔티티(서브 테이블)로 구분하여 업무의 모습을 정확하게 표현하면서 물리적인 데이터
모델로 변환을 할 때 선택의 폭을 넓힌 것
- 공통 부분을 슈퍼타입으로 모델링하고, 공통부분으로부터 상속받아 다른 엔티티와 차이가 있는 속성에 대해서는 별도의 서브엔티티(서브 테이블)로 구분하여 업무의 모습을 정확하게 표현하면서 물리적인 데이터
- 즉 슈퍼타입(전체를 하나의 테이블로 관리)에 정의된 공통 속성과 각 서브타입의 속성을 더하여 각각의 서브타입별로 테이블을 설계하는 방법
- 슈퍼/서브 타입 데이터 모델 : Extended ER모델이라고도 부른다
- JDBC API를 사용해서 자식 객체들을(여기서는 ALBUM, MOVIE, BOOK) 저장하려면 비용이 만만치 않다.
- 일단 모든 자식들이 부모 ID를 참조하여 식별관계(외래키가 자기의 PK)로 만들어져 있기 때문에 부모 데이터만 꺼내서 부모 데이터 INSERT 기능 만들고, 자식 객체에서 자식 데이터만 꺼내서 또
INSERT 해야한다 - 코드량이 어마어마하다 SQL부터 작성하고 쿼리 날려서 결과 받아온 다음 ResultSet 객체를 다시 하나하나 맵핑해야 하고.
- 일단 모든 자식들이 부모 ID를 참조하여 식별관계(외래키가 자기의 PK)로 만들어져 있기 때문에 부모 데이터만 꺼내서 부모 데이터 INSERT 기능 만들고, 자식 객체에서 자식 데이터만 꺼내서 또
- 이런 과정들이 모두 객체지향과 관계형 데이터베이스의 패러다임의 불일치를 해결하려고 소모하는 비용이다.
- 곰곰히 생각해보면 진짜 너무 비싸다. 막말로 노가다라고 표현할 수 있는데 이 시간들이 진짜 어마어마 하다.
JPA는 해당 자식 객체들을 데이터베이스가 아닌 자바 컬렉션에 보관해서 추가하면 너무 쉬워진다
부모객체.getAlbums().add(album);
부모객체.getMovies().add(movie);
- 트랜잭션 내에서 insert하고 후에 배우는 persist하게 되면 실제로 데이터베이스에 반영되어 insert된다
연관관계
객체는 참조를 사용해서(다른 엔티티를 필드로 보관. 연관관계) 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다.
반면에 테이블은 외래키를 사용해서 다른 테이블과 연관관게를 가지고 조인을 사용해서 연관된 테이블을 조회한다.
- 그림을 보면, 테이블 연관관계에서는 외래키를 보관해서 이 외래키를 이용해서 조인해서 TEAM 객체를 찾아오거나 Member 객체를 찾는다
- 하지만 객체 연관관계에서는 그냥 자신의 멤버변수인 team을 호출해서 찾을 수 있다.
- 반면 이그림에서의 객체 연관관계에서는, 단방향 연관관계이기 때문에(Member->team)
- member.getTeam()은 가능하지만, team.getMember()는 불가능하다
JPA와 연관관계
JPA없이 member와 Team을 저장하려고 할때 SQL과 JDBC API나 MyBatis를 이용해서 저장하게 된다면 아주 많은 코드가 들어가지만, JPA는 다음과 같이 두줄로 해결할 수 있다.
member.setTeam(team); // 회원과 팀 연관관계 설정
jpa.persist(member); // 회원과 팀(연관관계)을 같이 저장.
개발자는 코드에서 처럼 연관관계를 설정하고 member객체만 저장하면 된다 .
JPA가 알아서 team의 참조를 외래키로 변환해서 적절한 insert sql을 사용해서 데이터베이스에 전달한다.
반대로 객체를 조회할 때 외래키를 참조로 변환하는 일도 JPA가 처리해준다
Member member = jpa.find(Member.class, memberId);
Team team = member.getTeam();
member와 필드로서 연관관계를 맺은 team은 그냥 getTeam() 메서드로 호출해서 조회할 수 있다.
하지만 이런 연관관계도 극복하기 어려운 문제들이 있다. 다음 패러다임의 불일치 문제들을 보자.
객체 그래프 탐색
객체에서 member가 소속된 team을 조회할 때는 다음처럼 참조를 사용해서 연관된 팀을 찾으면 된다.
참조를 사용해서 연관된 객체를 조회하는 것을 객체 그래프 탐색
이라고 한다
Team team = member.getTeam(); // 객체 그래프 탐색.
- 다음 그림처럼 관계가 설계되어 있다고 가정해보자
- 벌써 그림부터 복잡하다
- member가 주문한 item을 찾고싶다면 ..?
- member.getOrder().getOrderItem();
- 예를 들어 member 객체를 조회할 때 Member와 team만 조인하는 SQL을 실행해서 member와 team에 대한 데이터만 조회했다면 member.getTeam()은 가능하지만 ,
member.getOrder()는 불가능하다.- 당연하다. member와 order, orderItem은 같이 조인하지 않았으니까.
- SQL을 직접 다루면 처음 실행하는 SQL에 따라 그래프를 어디까지 탐색할 수 있는지 정해져 버린다.
- 이말이 무슨말이냐 하면, sql을 직접 써서 조회한 쿼리로서는 조회한 만큼만 가져올 수 밖에 없단 이야기다.
- 위 예시대로 한번에 다 조인을 안하고 member와 team만 조인하면 두 데이터만 가져오지 order와 orderItem은 가져오지 않으니까 member와 team 까지만으로 그래프를 탐색할 수 있는
범위가 정해진 것이다.
- 이것이 객체지향 개발자한테 너무나 큰 제약인것도 같은 의미이다.
- 코드만 보고서는 team까지 가져올 수 있을지, 아니면 저 멀리 order와 orderItem, item 까지 가져올 수 있는지는 알 수 없다.
- DAO(데이터 접근 계층, 쿼리가 작성되어있고 DB와 연동하는 곳)를 열어서 SQL을 직접 확인하기 전 까진 알 수 없다.
- 쿼리가 join 1번만 한건지 2번 3번 4번 해서 다 가져오는지는 알 수가 없으니까..
- 이것도 결국 비용이 되는것이다.
- 그렇다고 모든 연관된 테이블들과 조회해서 매번 애플리케이션에 불러와 메모리에 올려두고 사용하는것도 매우 비효율적이다
- 그나마 현실적인게 메서드를 각각 네이밍도 다르게 짓고 SQL도 다르게 해서 사용해야 한다.
- 이것도 또 비용인것이다
하지만 JPA를 사용하면 다르다
JPA를 사용하면 객체 그래프를 마음껏 탐색할 수 있다.
- 쿼리 작성할 필요가 없고, 연관관계가 있다면 이 SELECT를 호출했을 때 어디까지 쓸 수 있는지 알아볼 필요가 없다.
- member.getOrder().getOrderItem()... 쭉쭉쭉
- 참조하는 연관된 객체(테이블)를 사용하는 시점에 그 시점마다 알아서 SQL을 날려주고 조회해와 주는 것이다.
- 실제 객체에 접근해서 사용하는 시점까지 데이터베이스 조회를 미룬다고 해서
지연로딩 Lazy Loading
이라고 한다
// 처음 조회 시점에는 SELECT MEMBER SQL
Member member = jpa.find(Member.class, memberId);
Order order = member.getOrder();
order.getOrderDate(); // Order를 사용하는 시점에 JPA가 알아서 SELECT Order SQL을 날려서 조회해온다. - 지연로딩
- 이렇게 둘이 따로따로 쿼리 2방을 날려서 조회하는 것보다, member와 order를 같이 조회해야 하는 기능이 필요하다면 Join을 사용해서 조회해서 가져오는게 효과적이다.
- JPA는 연관된 객체를
즉시 함께 JOIN해서 조회할지(EAGER)
- 아니면 실제 사용되는 시점에
쿼리를 한방더 날려서 총 2방으로 조회 해올지(LAZY)
를 간단하게 설정할 수 있다.
비교 (==) 같은지, 다른지
데이터베이스는 기본 키 값으로 각 row(로우)를 구분한다.
반면에 객체는 동일성(identity)
비교와 동등성(equality)
비교라는 두가지 방법을 사용해서 비교한다
- 동일성 비교는
obj1 == obj2
객체 인스턴스의 주솟값 비교. 같은 주소를 참조하는지. 같으면 true 다르면 false - 동등성 비교는 equals() 메소드를 사용해서 객체 내부의 값을 비교하는것. 주소는 달라도 내부 값이 같으면 같은걸로 치는 둥
- 물론 값이 여러개라면 equals() 메서드는 오버라이딩을 해야한다.
- 오버라이딩 안한 Object의 기본 equals() 메서드는 기본적으로 == 비교를 한다
JPA를 사용 안하고 DAO등으로 같은 기본키 (pk) 값으로 조회해온 두 객체를 동일성 ==
으로 비교하면 false가 반환된다.
- 왜? 둘이 주소가 다르니까. ==비교는 주소 비교다. 값은 같아도 둘 인스턴스는 다른 인스턴스이고 생성된 주소가 다르다.
- 그러나 객체를 컬렉션에 보관하면 동일성 비교에 성공한다
Member member1 = list.get(0);
Member member2 = list.get(0);
member1 == member2; // true. 같다
리스트가 참조하는 같은 주소의 인스턴스를 == 비교 하는거니까 같은것이다.
데이터베이스의 같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하는 것은 쉽지 않다.
하지만 JPA의 비교는 다르다.
JPA는 같은 트랜잭션
일 때 같은 객체가 조회되는 것을 보장한다.
그러므로 다음 코드에서는 동일성 비교가 성공한다
Member member1 = jpa.find(Member.class, 1L);
Member mebmer2 = jpa.find(Member.class, 1L);
- 위에서 설명한 오버라이딩 안한 기본 Object 클래스의 equals 메서드를 사용해도 같다고 나온다. (오버라이딩 안하면 ==비교니까)
- 뒷장에 나오는 영속성 컨텍스트와 연관이 있다.
- 반드시 트랜잭션을 명심하자!
JPA란 무엇인가?
JPA는 자바 진영의 ORM 기준 표술이며 인터페이스이다.
자바 애플리케이션과 JDBC 사이에서 동작해준다
- ORM(Object-Relational Mapping) : 객체와 관계형 데이터베이스를 맵핑해주는 프레임워크
- 객체와 테이블을 매핑해서 패러다임의 불일치 문제를 개발자 대신 해결해준다.
- JVM : 자바 애플리케이션 안에서 동작하고 자바 기술이므로 JVM 위에서 동작한다.
- 단순히 SQL을 개발자 대신 생성해서 DB에 전달해주는 것뿐만 아니라 다양한 패러다임의 불일치 문제들을 해결해준다.
- 상속
- 연관관계
- 객체그래프탐색
- 동등비교로 엔티티들의 비교
- 객체 모델링을 하면서 데이터베이스에 맞도록 모델링만 하면 된다.
- 그리고 어노테이션과 연관관계 등으로 매핑 방법만 ORM 프레임워크인 Hibernate에게 알려주면 된다.
왜 JPA를 사용해야 하는가?
- 생산성
- 반복적인 코드와 간단한 CRUD SQL등을 프레임워크가 알아서 작성해주고 사용해준다.
- 유지보수
- SQL이나 API 코드가 수정되거나 컬럼이 바뀌어도 모두 수정해야하는 JDBC API 등과 달리, JPA가 대신 처리해준다
- 패러다임의 불일치 해결
- 상속, 연관관계 객체 그래프탐색, 동등비교 등 패러다임 불일치 문제를 해결해준다
- 성능
- 영속성 컨텍스트를 이용하여, 같은 트랜잭션 안에서 같은 쿼리를 날리면 실제로는 한번만 날리는 성능 최적화를 야기할 수 있다.
- 힌트를 넣을 수 있는 기능도 있다.
- 데이터 접근 추상화와 벤더 독립성
- 관계형 데이터베이스는 같은 기능도 벤더마다 사용법이 다른 경우가 많다
- 벤더 : 판매사
- DBMS를 제공해주는 각종 회사들.
- Oracle, MySQL, H2, Postgres.. 등
- 애플리케이션과 데이터베이스 사이에 추상화된 데이터 접근 계층을 이용해서 특정 데이터베이스 기술에 종속되지 않고 교체하기 용이하다.
- 예를들어, 내가 MySQL로 개발하다 Oracle로 DBMS를 바꾸고 싶다는 등
- 로컬은 H2를 사용하고 개발서버랑 운영서버는 MySQL로 사용하는 등
- 그림에서는 Dialect로 표현한다. (방언이라고도 한다)
- 관계형 데이터베이스는 같은 기능도 벤더마다 사용법이 다른 경우가 많다
- ORM을 사용하더라도 데이터베이스와 SQL에 대한 학습은 필수다.
- 아무리 쿼리 최적화를 해주더라도, 제대로된 방식으로 코드를 짜지 않으면, 쿼리 성능은 장담할 수 없다.
- 테이블 설계도 마찬가지다.
2장. JPA 시작
H2 Database 설치
- https://www.h2database.com/html/main.html 접속
- Download에 Windows나 ALL Platforms를 클릭하여 다운로드.
- 또는, https://www.h2database.com/html/download.html 에 접속하여 최신 다운로드 가능
- 다른 버전을 다운로드 하고 싶다면?
H2 실행
- H2 데이터베이스는 자바로 작성되어 있으며 JVM 메모리 안에서 실행되는 임베디드 모드와, 실제 데이터베이스처럼 별도의 서버를 띄워서 동작하는 서버 모드가 있다.
- h2가 설치된 폴더에서 ./h2.sh 을 실행하면
서버모드
로 실행하게 된다.- 서버모드?
- 간단히, 자바 애플리케이션(스프링 부트 실행한 애플리케이션)과 독립되어 따로 컴퓨터에서 돌아가는 모드라 생각하면 된다.
- 임베디드 모드면 애플리케이션이 종료되면 종료되지만, 서버모드는 따로 종료하지 않으면 계속 유지된다
- 실행을 종료하면 h2도 종료하게 된다
- 서버모드?
- 서버모드로 실행하고 localhost:8082로 접속하면 h2 콘솔이 나오고 접속이 가능하다.
- 다음과 같이 정보를 입력하자
드라이버 클래스 : org.h2.Driver
JDBC URL : jdbc:h2:tcp://localhost/~데이터베이스 파일이 있는곳 /데이터베이스명
사용자명 : sa
비밀번호 : 입력하지 않는다
- 나의 경우, ~/h2/ 경로에 test.mv.db 라는 파일이 존재하며 이 test 라는 파일명이 데이터베이스 명이 된다
- JDBC URL :
jdbc:h2:tcp://localhost/~h2/test
- 경로명 : ~/h2/
- 디비명 : test
- JDBC URL :
- 빈칸에 쿼리를 입력하고 실행하면, 쿼리가 실행된다
- 예제 테이블 생성
create table MEMBER (
id varchar(255) not null,
name varchar(255),
age integer not null,
primary key(id)
)
- 대소문자 상관 없지만, 헷갈리지 않게 통일성을 유지해주는것이 좋다.
라이브러리와 프로젝트 구조
책에서는 Maven을 의존성 관리 도구를 사용하고, 이클립스를 사용한다.
하지만 이 예제는 인텔리제이와 Maven을 이용하여 프로젝트를 구성하도록 하겠다.
- 자바 11
- 스프링부트 2.7.3
- 개발 편의를 위한 롬복
- h2 데이터베이스 현재 기준 최신버전인 2.1.214
책에서 사용한 라이브러리인 org.hibernate 라이브러리를 사용하는 것이 아닌,
하이버네이트가 다 포함되어있는 Spring Data Jpa 라이브러리를 사용했다.
포함하고 있기 때문에, 다 같이 사용할 수 있다.
pom.xml - 접기/펼치기
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.ys</groupId>
<artifactId>jpa-book-study</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>jpa-book-study</name>
<description>jpa-book-study</description>
<properties>
<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
객체 매핑 시작
- 예제에서 사용하는 테이블 생성 SQL
create table MEMBER (
id varchar(255) not null,
name varchar(255),
age integer not null,
primary key(id)
)
- 예제에서 사용하는 회원 클래스
@Getter @Setter
public class Member {
private String id;
private String username;
private Integer age;
}
- 회원 클래스와 회원 테이블을 비교하면서 매핑해보자
매핑 정보 | 회원 객체 | 회원 테이블 |
---|---|---|
클래스 명과 테이블 명 | Member | MEMBER |
필드와 컬럼 | username | NAME |
필드와 컬럼 | age | AGE |
- 멤버 클래스에 매핑 어노테이션 추가
@Entity
@Table(name = "MEMBER")
@Getter @Setter
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name = "name")
private String username;
private Integer age;
}
@Entity
: 지정한 클래스를 테이블과 매핑 @Entity가 사용된 클래스를엔티티클래스
라고 한다.@Table
: 엔티티 클래스에 매핑할 테이블 정보.- name 속성을 사용해서 DB의 MEMBER 테이블로 이름 짓고 매핑한다는 의미.
- 생략시 클래스이름이나 엔티티 이름으로 매핑
- name 속성을 사용해서 DB의 MEMBER 테이블로 이름 짓고 매핑한다는 의미.
@Id
: 엔티티 클래스의 필드를 기본 키(PK)에 매핑.- @Id가 사용된 필드를
식별자 필드
라 한다. 식별자 = PK
- @Id가 사용된 필드를
@Column
: 필드를 컬럼에 매핑.- name속성을 사용해서 DB의 MEMBER 테이블의 NAME 컬럼에 매핑.
- 어노테이션이 없는 필드 : 매핑 어노테이션을 생략하면 필드명이 컬럼명이다.
- 만약 대소문자를 구분하는 데이터베이스를 사용하면 명시적으로 매핑 해주어야 한다.
JPA 어노테이션의 패키지는 javax.persistence 이다
- 왜? JPA는 표준 명세이다. 즉 인터페이스.
- 구현체인 하이버네이트가 어노테이션을 이용해서 정보를 받아 알아서 처리 해준다.
property 설정
- 예제는 xml을 사용하나, 여기서는 스프링부트 설정 파일인 application.yml 을 사용하겠다.
- properties보다 읽기 쉽고.. 자주 써서
- 위치는 main/resources/ 아래이고 파일명은 application.yml 이다
spring:
datasource:
url: jdbc:h2:tcp://localhost/~/h2/test # 접속 url
driver-class-name: org.h2.Driver # JDBC Driver
username: sa # userID
password: # password
jpa:
database: h2
show-sql: true
properties:
hibernate:
dialect: org.hibernate.dialect.H2Dialect #Dialect, 방언 설정
format_sql: true
use_sql_comments: true
id:
new_generator_mappings: true
- spring.jpa.show-sql : 하이버네이트가 실행한 SQL을 콘솔에 출력
- spring.jpa.properties.hibernate.format_sql : 하이버네이트가 실행한 SQL을 출력할 때 보기좋게 정렬
- spring.jpa.properties.hibernate.use_sql_comments : 쿼리 출력시 주석도 함께 출력
- spring.jpa.properties.hibernate.id.new_generator_mappings : JPA 표준에 맞춘 키 생성 전략을 사용. 기본 false
데이터베이스 방언 : Dialect
JPA는 특정 데이터베이스 (위에서 이야기한 데이터베이스 벤더들 마다 다르단 이야기)
에 종속적이지 않은 기술이다. 그래서 다른 데이터베이스로 손쉽게 교체할 수 있다.
하지만 각 DBMS 마다 제공하는 SQL 문법과 함수들이 다르다는 문제점이 있따.
- 데이터 타입 : 가변 문자 타입으로 MySQL은 varchar, 오라클은 varchar2 사용
- 다른 함수명 : 문자열을 자르는 함수로 SQL 표준은 SUBSTRING(). 하지만 오라크는 SUBSTR()
- 페이징 처리 : MySQL은 limit, 오라클은 ROWNUM 사용
방언(Dialect)
: SQL 표준을 지키지 않거나 특정 데이터베이스만의 고유한 기능을 JPA는 Dialect라고 한다- 특정 데이터베이스에 의존적인 SQL은 데이터베이스 Dialect가 알아서 처리해준다.
- 우리는 현재 사용하고 있는 DBMS에 맞게 Dialect만 잘 설정하면 된다.
- 위 옵션처럼.
- 우리는 현재 사용하고 있는 DBMS에 맞게 Dialect만 잘 설정하면 된다.
- 다른 데이터베이스를 사용할 시, Dialect(방언)만 교체해주면 되는경우가 대부분이다.
- 특정 DBMS만의 고유한 함수나 기능들은 당연히 따로 교체해줘야 하는 경우가 있다..
- 직접 구현했는데 문법이 다른경우.
- 다음과 같이 다양한 Dialect(방언)들이 존재한다.
- package : org.hibernate.dialect;
- 정말 다양하게 많다.
- org.hibernate.dialect.Dialect 추상 클래스를 상속받고 있으므로 필요한 구현체들을 찾아 Dialect로 설정하면 된다.
- 없는 Dialect를 설정하면 당연히 빌드시 오류 발생한다
- https://docs.jboss.org/hibernate/orm/5.6/javadocs/org/hibernate/dialect/package-summary.html
- docs이다 책에서 나온건 구버전이고 현재 버전은 5.6 버전이라 좀 달라서 찾아봤다.
- https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html#configurations
- 여기에도 나오긴 한다. 뭐 같은 javadoc 링크를 공유한다.
애플리케이션 개발
public class JpaBookStudyApplication {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin(); //트랜잭션 시작
//비즈니스 로직 수행
tx.commit(); // 트랜잭션 커밋
} catch (Exception e) {
tx.rollback(); // 예외 발생시 롤백
} finally {
em.close(); // 엔티티 매니저 종료
}
emf.close(); // 엔티티 매니저 팩토리 종료
}
}
코드는 크게 3가지 부분으로 나뉘어져있다.
- 엔티티 매니저 설정
- 트랜잭션 관리
- 비즈니스 로직
엔티티매니저 설정
- 엔티티 매니저 팩토리 생성
- persistence.xml을 이용하여 entityManagerFactory를 생성한다.
- Persisetence 클래스가 엔티티 매니저 팩토리를 생성해준다
- 이 때, 설정정보를 읽어서 JPA를 동작시키기 위한 기반 객체를 만들고 구현체에 따라서는 커넥션 풀도 생성한다.
- 그래서 엔티티매니저팩토리를 생성하는 비용은 아주 비싸다.
- 따라서 엔티티 매니저 팩토리를 애플리케이션 전체에서
딱1번만 생성
하고 공유해서 사용해야 한다
- 엔티티 매니저 생성
- 엔티티매니저팩토리(EntityManagerFactory)로 엔티티매니저(EntityManager)를 생성해준다.
-
EntityManager em = emf.createEntityManager();
- 엔티티 매니저를 이용해서 엔티티를 데이터베이스에 등록 수정 삭제 조회 할 수 있따.
- 엔티티매니저는 내부에 DataSource(커넥션)을 유지하면서 데이터베이스와 통싱한다.
- 엔티티 매니저가가상의 데이터베이스라고 생각할 수 있다.
엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로, 스레드간에 공유하거나 재사용하면 안된다.
-
- 엔티티매니저팩토리(EntityManagerFactory)로 엔티티매니저(EntityManager)를 생성해준다.
- spring, spring boot 환경에서는 이 EntityManagerFactory를 이용해서 Entitymanager를 생성하지 않는다.
- 스프링 프레임워크 내에서 JPA 를 사용할 때에는 트랜젝션을 관리하는 일을 프레임워크가 처리해준다이렇게 등록된 EntityManager 빈을 어노테이션의 도움을 받아 주입한다 .
- 따라서 애플리케이션 초기화 시점에 EntityManager를 프레임워크가 Bean으로 등록 하는데
@PersistenceContext
어노테이션-
@PersistenceContext private Entitymanager entityManager;
- 종료
- 사용이 끝난 엔티티 매니저는 close를 호출해서 반드시 종료해야 한다
- em.close(); // 엔티티 매니저 종료
- 애플리케이션을 종료할 때 엔티티 매니저 팩토리도 종료해야 한다
- emf.close(); // 엔티티 매니저 팩토리 종료
- 사용이 끝난 엔티티 매니저는 close를 호출해서 반드시 종료해야 한다
트랜잭션 관리
JPA를 사용하면 항상 트랜잭션
안에서 데이터를 변경해야 한다.
트랜잭션 없이 데이터를 변경하면 예외가 발생한다.
트랜잭션을 시작하려면 엔티티 매니저(entityManager)에서 트랜잭션 API를 받아와야 한다
EntityTransaction tx = em.getTransaction();
try {
tx.begin(); // 트랜잭션 시작
logic(em); // 비즈니스 로직 실행. 이안에서 변경이 발생
tx.commoit(); // 트랜잭션 커밋
} catch (Exception e) {
tx.rollback(); // 예외 발생 시 트랜잭션 롤백
}
- 트랜잭션을 사용하면, 비즈니스 로직이 정상 동작하면 트랜잭션을 commit 하고 예외가 발생하면 rollback 한다
비즈니스 로직
엔티티 매니저를 이용해서 데이터베이스 등록, 수정, 삭제, 조회한다
public static void logic(EntityManager em) {
String id = "id1";
Member member = new Member();
member.setId(id);
member.setUsername("영수");
member.setAge(28);
//등록, 저장
em.persist(member);
// 수정
member.setAge(20);
//한 건 조회
Member findMember = em.find(Member.class, id);
// 목록 조회
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
// 삭제
em.remove(member);
}
등록
- 엔티티를 저장하려면 엔티티 매니저의
persist()
메서드에 저장할 엔티티를 넘겨주면 된다. - JPA는 엔티티의 매핑 정보(어노테이션)을 분석해 INSERT SQL을 만들어 데이터베이스에 전달한다
수정
- JPA는 어떤 엔티티가 변경되었는지 추적하는 기능을 갖추고 있다.
- 수정하려면 엔티티의 값을 수정하기만 하면 된다.
- 따라서 엔티티의 수정이 발생한다면 UPDATE SQL을 생성해서 데이터베이스에 값을 변경한다.
- 후에 나오는 조회가 되어서 영속성 컨텍스트 안에 있는 엔티티여만 변경을 추적할 수 있다.
삭제
- 엔티티매니저의
remove()
메서드에 삭제하려는 엔티티를 넘겨준다 - 그러면 JPA는 DELETE SQL을 생성하여 실행한다.
한 건 조회
-
Member findMember = em.find(Member.class, id);
- em.find(찾을 엔티티 타입, 식별자);
JPQL
하나 이상의 Member 목록을 조회하는 다음 코드를 보자
TypedQuery<Member> query =
em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
- 개발자는 엔티티 객체를 중심으로 개발하고, 데이터베이스에 대한 처리는 JPA에게 맡긴다.
- 기본적인 등록, 수정, 삭제, 조회를 제외하고는 개발자가 JPA에게 조건을 알려줘야 한다.
- JPA는 엔티티 객체를 중심으로 개발하므로 조건을 알려줄 때도 테이블이 아닌 엔티티 객체를 대상으로 한다.
애플리케이션이 필요한 데이터만 데이터베이스에서 불러오거나 이용하려면 결국 검색 조건이 필요한 SQL을 사용해야 한다.
JPA는 JPQL(Java Persistence Query Language) 라는 쿼리 언어로 이 기능을 제공한다
- SQL과 문법이 거의 유사해서 SELECT, FROM, WHERE, GROUP BY, ORDER BY 등을 제공해준다
- JPQL은 엔티티 객체를 대상으로 쿼리한다.(위에서 from Member 한 것처럼 .Member는 엔티티 객체)
- 클래스와 필드를 대상으로 쿼리
- SQL은 데이터베이스 테이블을 대상으로 쿼리한다.
JPQL을 사용하려면 em.createQuery(JPQL, 반환타입); 메서드를 실행해서 쿼리 객체를 생성한 후 쿼리 객체의 메서드를 호출하여 실행한다.
'스터디 > JPA 프로그래밍 스터디' 카테고리의 다른 글
JPA 프로그래밍 스터디 7장 정리 (1) | 2022.10.01 |
---|---|
JPA 프로그래밍 스터디 6장 정리 (0) | 2022.10.01 |
JPA 프로그래밍 스터디 5장 정리 (0) | 2022.09.13 |
JPA 프로그래밍 스터디 4장 정리 (0) | 2022.09.13 |
JPA 프로그래밍 스터디 3장 정리 (0) | 2022.09.04 |