JPA 개념 공부의 기록

timothy jeong·2021년 7월 16일
0

Study

목록 보기
3/11

공부의 기록 시리즈는 공유하고자 적는 글이 아닙니다. 공부할때 필기 하는 습관이 있고, 모르는 내용을 메모할 공간이 필요하기 때문에 적는 글입니다. 나중에 이 글을 잘 정제하고 편집해서 공유를 위한 글을 따로 포스팅합니다.

JPA란?

JPA는 ORM 프레임워크로, RDB와 OOP 사이에서 발생하는 페러다임 불일치 문제를 해결해준다. 아래의 그림과 같이 Jdbc API에서 완전히 독립적인 것이 아니라, Jdbc API를 직접 다룰 필요없이 추상화 해준 프레임워크라고 볼 수 있다.

JPA는 학습하기 어렵다

JPA가 어려운 근본적인 이유는 ORM이 객체지향과 관계형 데이 터베이스라는 두 기둥 위에 있기 때문입니다. 이 둘의 기초가 부족하면 어려울 수밖 에 없습니다.

인터넷에 돌아다니는 예제 몇번 따라한것 만으로는 금방 한계에 부딪힐 수 밖에 없습니다.

JPA를 사용해야 하는 이유

■ 생산성
JPA를 사용하면 다음 코드처럼 자바 컬렉션에 객체를 저장하듯이 JPA에게 저장 할 객체를 전달하면 된다. INSERT SQL을 작성하고 JDBC API를 사용하는 지루 하고 반복적인 일은 JPA가 대신 처리해준다.

따라서 지루하고 반복적인 코드와 CRUD용 SQL을 개발자가 직접 작성하지 않 아도 된다. 더 나아가서 JPA에는 CREATE TABLE 같은 DDL 문을 자동으로 생성 해주는 기능도 있다. 이런 기능들을 사용하면 데이터베이스 설계 중심의 패러 다임을 객체 설계 중심으로 역전시킬 수 있다.

■ 유지보수
SQL을 직접 다루면 엔티티에 필드를 하나만 추가해도 관련된 등록, 수정, 조회 SQL과 결과를 매핑하기 위한 JDBC API 코드를 모두 변경해야 했다. 반면에 JPA를 사용하면 이런 과정을 JPA 가 대신 처리해주므로 필드를 추가하거나 삭제해도 수정해야 할 코드가 줄어든 다. 따라서 개발자가 작성해야 했던 SQL과 JDBC API 코드를 JPA가 대신 처리 해주므로 유지보수해야 하는 코드 수가 줄어든다.

또한, JPA가 패러다임의 불일치 문제를 해결해주므로 객체지향 언어가 가진 장 점들을 활용해서 유연하고 유지보수하기 좋은 도메인 모델을 편리하게 설계할 수 있다.

■ 패러다임의불일치해결
지금까지 패러다임의 불일치 문제가 얼마나 심각한지 다루었고, JPA를 통한 해 결책도 간단히 보았다. JPA는 상속, 연관관계, 객체 그래프 탐색, 비교하기와 같 은 패러다임의 불일치 문제를 해결해준다. 책 전반에 걸쳐서 JPA가 패러다임의 불일치 문제를 어떻게 해결하는지 자세히 알아보자.

■ 성능
JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화 기회를 제 공한다. JPA는 애플리케이션과 데이터베이스 사이에서 동작한다. 이렇게 애플 리케이션과 데이터베이스 사이에 계층이 하나 더 있으면 최적화 관점에서 시도 해 볼 수 있는 것들이 많다.

■ 데이터접근추상화와벤더독립성
관계형 데이터베이스는 같은 기능도 벤더마다 사용법이 다른 경우가 많다. 단 적인 예로 페이징 처리는 데이터베이스마다 달라서 사용법을 각각 배워야 한 다. 결국, 애플리케이션은 처음 선택한 데이터베이스 기술에 종속되고 다른 데이터베이스로 변경하기는 매우 어렵다.

JPA는 애플리케이션과 데이터베이스 사이에 추상화된 데이터 접근 계층을 제공해서 애플리케이션이 특정 데이터베이스 기술에 종속되지 않도록 한다. 만약 데이터베이스를 변경하면 JPA에게 다른 데이터베이스를 사용 한다고 알려주기만 하면 된다. 예를 들어 JPA를 사용하면 로컬 개발 환경은 H2 데이터베이스를 사용하고 개발이나 상용 환경은 오라클이나 MySQL 데이터베이스를 사용할 수 있다.

SQL 사용의 문제점
■ 진정한 의미의 계층 분할이 어렵다.
■ 엔티티를 신뢰할 수 없다.
■ SQL에 의존적인 개발을 피하기 어렵다.

페러다임 불일치

지속 가능한 어플리케이션 개발을 위해 가장 중요한 것은 복잡성 관리이다. 아무리 좋은 어플리케이션이라도 코드가 복잡해져서 유지보수, 확장에 속도를 낼 수가 없다면 시장에서 도태될 수 밖에 없다. 이러한 복잡성 관리를 위해 대부분의 어플리케이션은 OOP의 원칙을 따른다.

하지만 RDB는 어떤가? OOP의 핵심 개념인 상속, 추상화, 다형성 ,캡슐화 등은 RDB에서 주된 관심사가 아니다. 그러므로 개발자는 OOP 기반 어플리케이션과 DB를 연동하면서 둘 사이에 페러다임 불일치를 조정하는 작업을 해주게 되는데, 기존에 사용하는 Jdbc API, JdbcTemplate 각 그것이다. 둘 사이의 불일치를 조정하면서 어플리케이션의 복잡성이 커지게 된다.

JPA는 이러한 OOP의 핵심 개념을 그대로 DB에 매핑해주면서 어플리케이션의 복잡성 관리에 큰 도움을 준다.

(1) JPA와 상속

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

[데이터 저장]

추상화된 item 객체를 상속한 album 객체가 있다고 해보자.
이 album 객체를 DB에 추가하기 위해서는 아래의 JPA 메서드를 호출 하여 사용된다.

jpa.persist(album); // JPA 데이터 저장

내부적으로 다음의 SQL 문이 실행된다.

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

[데이터 조회]

데이터 조회시에는 다음의 메서드를 이용한다.

String albumId = "id100";
Album album = jpa.find(Album.class, albumId);

내부적으로 다음의 SQL 문이 실행된다.

SELECT I.*, A.*
    FROM ITEM I
    JOIN ALBUM A ON I.ITEM_ID = A.ITEM_ID 
    //둘 사이의 key값이 뭔지는 자동으로 매핑되나?
    //메서드 호출시 줬던 albumId 값을 where절에서 다뤄야 하는게 아닌가..?

(2) JPA와 연관관계

Member 라는객체가 Team 객체를 참조하는 다음과 같은 구조가 있는 상황에서.

class Memeber {
	Long id;
    	Team team;
        int score;
        
        public Team getTeam() {
       	   return this.team; 
        }
        
        public void setTeam(Team team) {
       	   this.team = team;
        }
}
  
class Team {
	Long id;
    	int size;
}

java 코드 상에서는 Member 객체와 연관된 Team객체까지 함께 조회하기 위해서는 Member 객체와 해당 객체에서 getTeam 메서드를 호출하면 된다. 이를 SQL 문으로 표현하면 다음과 같을 것이다.

SELECT M.*, T.* FROM MEMBER M
JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

언뜻보면 아무런 이슈도 없을 것 같은데, sql은 FK로 연동되어 있기 때문에 Member 테이블에서도 JOIN이 되고, Team 테이블에서도 JOIN이 된다. 하지만 Java 코드에서 참조된 객체를 조회하는 것은 일방향적이다. 오로지 Member 객체의 메서드를 통해서만 조회할 수 있고, Team 객체에서 자신을 참조한 Memeber를 조회할 방법은 없다.

또한 Member 라는 객체는 테이블 상에서 Team 정보를 전부 가지고 이있는게 아닌 teamId만 가진 형태일텐데, 이러한 점을 고려하면 OOP적으로 모델링된 객체를 그대로 테이블에 사용할 수 없게된다. 결국, 개발자가 이러한 불일치를 해소하기 위해 중간에서 변환 역할을 해야 한다.

Jdbc를 이용해본 경험이 있어서 공감이 많이 가는 대목이다...

public Member find(String memberId) {
//SQL 실행

...
Member member = new Member(); 

...
//데이터베이스에서 조회한 회원 관련 정보를 모두 입력 

Team team = new Team();

...
//데이터베이스에서 조회한 팀 관련 정보를 모두 입력

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

return member;
}

JPA를 이용하면 그 복잡성이 크게 감소한다.

member.setTeam(team); //회원과 팀 연관관계 설정
jpa.persist(member); //회원과 연관관계 함께 저장

JPA는 team의 참조를 외래 키로 변환해서 적절한 INSERT SQL을 데이터베이스에 전달 한다. 객체를 조회할 때 외래 키를 참조로 변환하는 일도 JPA가 처리해준다.

조회하는 것도 간편해진다.

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

(3) 객체 그래프 관계

객체는 객체 그래프를 자유롭게 탐색할 수 있다는 특징이 있다.(물론 이때로 일방향적이다.) 하지만 SQL문을 통해 객체를 생성하게 되면 SQL문 구성에 따라 이러한 객체 그래프 탐색에 단절점이 생기고 만다.

예를 들어 위의 Member 객체는 Team 객체를 참조하고 동시에 Order 객체를 참조한다고 하자.

@Getter @Setter
class Memeber {
	Long id;
    	Team team;
        Order order;
        int score;
}

그런데 Member 객체를 생성하는 SQL문이 다음과 같을 경우 Member.getOrder() 메서드를 쓰면 null이 나올 것이다. 이것이 SQL문 때문에 발생하는 객체 그래프 탐색의 단절이다.

SELECT M.*, T.* 
    FROM MEMBER M
    JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

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

그렇다고 member와 연관된 모든 객체 그래프를 데이터베이스에서 조회해서 애
플리케이션 메모리에 올려두는 것은 현실성이 없다. 결국 MemberDAO에 회원을 조 회하는 메소드를 상황에 따라 여러 벌 만들어서 사용해야 한다.

하지만 JPA를 사용하면 객체 그래프를 마음껏 탐색할 수 있다.

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

--> JPA 메서드를 통해 생성한 객체는 뭔가 따로 관리를 해서, 그 객체가 그래프 탐색을 할때마다 필요한 객체를 DB에서 조회해서 만들어주는(지연 로딩) 기능이 있다는 말 같다.

그런데 JPA는 지연 로딩을 투명(transparent)하게 처리한다. Member 객 체를 보면 getOrder 메소드의 구현 부분에 JPA와 관련된 어떤 코드도 직접 사 용하지 않는다.

그런데 Member.getOrder()를 실행할때 자동으로 sql문을 돌려서 결과를 반환해준다. 대단하다!

만약 비즈니스 로직상 Member 객체를 만들면 항상 getOrder()를 호출하는 상황이라면 이렇게 지연로딩을 하는 것은 비효율적인 방법이다. 이때는 처음부터 같이 생성하는 것이 효율적일텐데, JPA는 이러한 설정을 직접 할 수 있게 해준다.

(4) 비교

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

■ 동일성 비교는 == 비교다. 객체 인스터스의 주소 값을 비교한다.
■ 동등성 비교는 equals() 메소드를 사용해서 객체 내부의 값을 비교한다.

따라서 테이블의 로우를 구분하는 방법과 객체를 구분하는 방법에는 차이가 있다.

동일한 SQL문으로 member1, meber2 인스턴스를 만들었을때 둘은 엄밀히 따지면 서로 다른 객체이기 때문에 동일성 비교에서 false가 나올것이다. 그런데 만약 객체를 컬렉션에 보관했다면 다음과 같이 동일성 비교에 성공했을 것이다.

이러한 패러다임 불일치 문제를 해결하기 위해 동일한 SQL문이 동일한 인스턴스를 반환하도록 하는 것은 쉽지 않다.

JPA는 이러한 불일치를 해결해준다.

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

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

객체 비교하기는 분산 환경이나 트랜잭션이 다른 상황까지 고려하면 더 복잡해
진다. 자세한 내용은 책을 진행하면서 차차 알아보자.

정확히 모르는 개념 공부

마이바티스 JdbcTemplate

마이바티스나 스프링 JdbcTemplate을 보통 SQL 매퍼라 합니다. 이것은 이름 그대로 객체와 SQL을 매핑합니다. 따라서 SQL과 매핑할 객체만 지정하면 지루하 게 반복되는 JDBC API 사용과 응답 결과를 객체로 매핑하는 일은 SQL 매퍼가 대신 처리해줍니다. 이런 SQL 매퍼가 편리하긴 하지만 결국 개발자가 SQL을 직접 작성해야 하므로 SQL에 의존하는 개발을 피할 수 없습니다.

ORM

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

하이버네이트

자바 진영의 ORM 프레임워크들 중에서 가장 많이 사용되는 프레임워크이다.
JPA를 구현한 ORM 프레임워크는 하이버네이트, EclipseLink, DataNucleus 가 있는데 이 중에 하이버네이트가 가장 대중적이다

직렬화

■ 자바 시스템 내부에서 사용되는 Object 또는 Data를 외부의 자바 시스템에서도 사용할 수 있도록 byte 형태로 데이터를 변환하는 기술.

■ JVM(Java Virtual Machine 이하 JVM)의 메모리에 상주(힙 또는 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술

왜 직렬화를 쓰는가?
■ 복잡한 데이터 구조의 클래스의 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화, 역직렬화가 가능합니다.

■ 데이터 타입이 자동으로 맞춰지기 때문에 관련 부분을 큰 신경을 쓰지 않아도 됩니다.

어디에 사용되는가?
■ 서블릿 세션 (Servlet Session)
세션을 서블릿 메모리 위에서 운용한다면 직렬화를 필요로 하지 않지만, 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션 등을 선택하게 되면 세션 자체가 직렬화가 되어 저장되어 전달됩니다.

■ 캐시 (Cache)
Ehcache, Redis, Memcached 라이브러리 시스템을 많이 사용됩니다.

■ 자바 RMI(Remote Method Invocation)
원격 시스템 간의 메시지 교환을 위해서 사용하는 자바에서 지원하는 기술.

직렬화 하는 방법등은 아래의 출처를 참고
직렬화란?

객체 그래프

객체들은 다른 객체를 참조할 수 있습니다. 그렇게 참조한 객체는 또다른 객체를 참조하고 있을 수도 있죠. 이렇게 참조된 객체를 계속해서 찾아가다 보면 시작점이 되는 객체(starting object)까지 찾을 수 있을 것입니다. 이러한 관계는 도달성 분석(reachability analysis)에 유용한 객체간의 그래프를 만들어냅니다.

Objects have references to other objects which may in turn have references to more objects including the starting object. This creates a graph of objects, useful in reachability analysis.
stackoverflow

Reachability Analysis 와 Garbage Collector

xmlns(xml 네임 스페이스)

XML 네임스페이스는 XML 요소 간의 이름에 대한 충돌을 방지해 주는 방법을 제공합니다.

XML 네임스페이스는 요소의 이름과 속성의 이름을 하나의 그룹으로 묶어주어 이름에 대한 충돌을 해결합니다.

이러한 XML 네임스페이스는 URI(Uniform Resource Identifiers)로 식별됩니다.

TCP School

profile
개발자

1개의 댓글

comment-user-thumbnail
2022년 6월 21일

@Transactional
repository나 service계층(repository를 사용하는 계층)에서 사용된다. 기본적으로 RuntimeException이나 error가 발생하면 해당 트랜잭션을 롤백시킨다. Checked Exception에는 롤백이 일어나지 않는다.

여러가지 옵션을 줄 수 있다.

readOnly: 데이터를 변경하는 operation이 없는 경우 true로 설정해주는 것이 좋다.

Isolation: 여러개의 트랜잭션이 동시에 데이터베이스에 접근했을때, 해당 트랜잭션들을 어떻게 처리할 것인지에 대한 설정(동시에 처리할 것인지, 하나 하나 처리할 것인지 등) 기본값은 default로 DB의 기본값을 따라간다.(대부분 READ_COMMITTED 방식이 default값이다.)

Propagation: 트랜잭션을 어떻게 전파시킬 것인지에 관한 것이다. 예를 들어 @Transactional을 가지고 있는 메서드가 있고, 그 메서드 안에서 다른 메서드를 호출했는데 그 메서드도 @Transactional을 가지고있는 경우, 첫 번째 메서드가 가지고 있던 트랜잭션을 두 번째 메서드가 이어갈 것이냐, 두 번째 메서드 자체에서 트랜잭션을 만들어서 따로 사용할 것이냐를 결정하는 것이다.(nested transaction에 관한 내용)

답글 달기