[JPA] JPA 스터디 1장
1주차 학습내용
1주차 스터디에 대해 간략하게 정리하는 내용이다.
JPA란?
Java Persistence API 의 줄임말로써, 자바 진영의 표준 ORM 기술이다.
JPA는 다른 JDBCTemplate 나 Mybatis 등등 객체와 관계형 데이터베이스의 차이를 메우기 위해 SQL을 작성을 해서 개발을 진행해야했었는데, 반복적인 CRUD 뿐만아니라 객체 모델링과 관계형 데이터베이스의 차이를 해결해주었다.
JPA의 핵심은 바로 SQL이 아닌 객체 중심으로 개발을 진행하기 때문에 생산성과 유지보수에서 용이하다.
이렇게 되면 테스트를 작성하기에도 편리하다.
SQL을 직접 다룰 때 발생하는 문제점
지금까지 실무에서 Mybatis나 iBatis를 사용하였다. 그래서 소스코드를 보거나 내가 구현해야 할 때 상당 시간을 데이터베이스를 매핑시킨 xml에 너무 의존하여 개발했다.
- 왜? - 오타에 의한 , 띄어쓰기에 의한 하나라도 틀리게 되면 무수한 에러를 발생시킨다.
기존의 DTO파일에 뭔가 한 칼럼을 추가해야된다고 하면 추가할 클래스들과 sql을 수정해서 작성해야 하는데,
이 때 또 다시 아까처럼 오타가 발생하면 안되기에 꼼꼼하게 개발해야 한다.
또 연관관계에 있어서 Member라는 객체가 Team을 갖고 있는다고 가정을 한다면, Team에 대한 SQL문도 직접 매핑을 시킨다음 묶어주어야 했다.
JPA와 문제 해결
- 저장기능
persist()
- 조회기능
find()
- 수정 : 메소드는 별도로 없지만 객체를 변경하게 되면 적절한 update sql쿼리가 실행
- 연관된 객체 조회기능 : 연관된 객체를 사용하는 시점에 적절한 select sql 실행
상속
객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능을 지원하기는 하지만 객체의 상속과 다르다.
JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다. 개발자는 마치 자바 컬렉션에 객체를 저장하듯이 JPA에게 객체를 저장하면 된다.
연관관계
객체는 참조를 사용하여 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다.
반면에 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을
조회한다.
→ 객체를 테이블에 맞춰 모델링 한다 이렇게 객체지향 모델링을 해도 무척 번거롭고 어려움
→ 이런 패러다임 불일치는 객체지향 모델링을 포기하게 만들정도로 극복하기에 어려움
반면 JPA는 이런 문제를 손쉽게 해결해준다.
객체 그래프 탐색
참조를 사용하여 연관된 객체를 탐색하는 것이다.
SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해짐
반면 JPA는 자유롭게 마음껏 탐색이 가능함. 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행하기 때문에 자유로운 것.
- 지연로딩 : 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미루는 것
- JPA는 연관된 객체를 즉시 함께 조회할지 아니면 실제 사용되는 시점에 지연해서 조회할지 간단한 설정으로 정의할 수 있다.
비교
DB의 경우 같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하는것은 쉽지 않다.
반면에 JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.
정리
객체 모델과 관계형 데이터베이스 모델은 지향하는 패러다임이 다르기 때문에 차이를 극복하려고 개발자가 너무 많은 시간과 코드를 소비한다. 그래서 정교한 객체 모델링을 할수록 불일치 문제는 더 커진다. 이러다보니 데이터 중심의 모델로 변하는 과정이 되버린다.
그리고 이것을 해결해주는 것이 바로 JPA다. 불일치 문제를 해결해주고 정교한 객체 모델링을 유지하게 도와준다.
📕 ORM(Object Relational Mapping)
ORM(Object Relational Mapping)은 이름 그대로 객체와 관계형 데이터베이스를 매핑한다는 뜻.
이 프레임워크는 객체와 테이블을 매핑하여 패러다임 불일치 문제를 개발자 대신 해결해준다.
ORM
이란 것은 애플리케이션과 데이터베이스
사이에 가상의 데이터베이스(JPA
에서 영속성 컨텍스트
라고 불리는)라는 인디렉션
을 만드는 것이다.
즉, OOP(Object-Oriented Programming)
를 하기위한 프로그래밍 기법이다.
원래는 데이터베이스
에 종속적이었던 코드를 역으로 데이터베이스
를 추상화하여 코드레벨에 맞춰주며
그렇게 함으로써 데이터에 종속적인 프로그래밍이 아닌 OOP
를 할 수 있게 된다.
내가 실무를 보며 느끼기에 ORM
이란 것은 결국 사전적인 정의대로 OOP
를 위한 기술이다.
SQL
에 종속적이다, 똑같은 코드가 반복된다 등의 잡다한 이유가 있지만
이러한 문제들의 본질은 OOP
를 하지 못하기 때문에 발생하는 것이다.
애플리케이션이 데이터베이스
와 연동되면 모든 코드가 자연스럽게 데이터 기반으로 돌아가기 때문에
자연스럽게 OOP
5대 원칙, SOLID
도 대부분 위반하게 된다.
약어 | 개념 |
---|---|
SRP | 단일 책임 원칙 (Single responsibility principle) 한 클래스는 하나의 책임만 가져야 한다. |
OCP | 개방-폐쇄 원칙 (Open/closed principle) “소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.” |
LSP | 리스코프 치환 원칙 (Liskov substitution principle) “프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.” 계약에 의한 설계를 참고하라. |
ISP | 인터페이스 분리 원칙 (Interface segregation principle) “특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.” |
DIP | 의존관계 역전 원칙 (Dependency inversion principle) 프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙을 따르는 방법 중 하나다. |
간단한 예제를 보자면
Member
객체를 DB
에 저장할것이라고 했을 때,
Member
클래스를 Java Beans
규약에 맞추어 작성하고, SQL
을 작성한다.
class Member{
private String name;
private int age;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
INSERT INTO MEMBER
(NAME, AGE)
VALUES
(#name, #age);
이제 접근자를 이용해 name
과 age
를 추출하여 SQL
에 넣어줘야한다.
class InsertService {
public void insert(String name, int age);
}
...
service.insert(member.getName, member.getAge);
...
이 데이터를 뽑아내고 입력하는 과정 자체가 데이터에 종속적인 과정이며, 결국 이 과정으로 인해 모든 문제가 발생한다.
만약 이 상황에서 address
라는 필드가 하나 더 필요해진다면?
Member
에 address
필드를 추가하고, SQL
과 Service
클래스도 변경되어야만 한다.
실무에서는 절대 써먹을수조차 없는 매우 간단한 예제임에도 불구하고 즉시 OCP
가 위배된다.
이처럼 코드와 SQL
사이에 매우 강한 결합도(=내용 결합도
)가 생기기 때문에 SOLID
를 지키기가 정말 어려워진다.
나는 개인적으로 불가능하다고 생각하기도 한다.
그렇다면 ORM
을 도입하면 어떻게 변할까?
@Entity
class Member{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int age;
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
SQL은 따로 작성하지 않는다.
추상화된 가상의 데이터베이스가 알아서 처리해주기 때문이다.
이 상태에서 address
필드를 추가해야 한다면 그냥 추가하면 끝이다. 더 손댈 게 없다. 즉, OCP
를 위반하지 않게된다.
이렇게 간단한 예제만으로도 SOLID
를 지키기가 압도적으로 수월해지며, 결과적으로 OOP
의 장점을 모두 누릴 수 있게된다.
SQL에 종속적인 문제, 재사용성 증가, 유지보수성 증가, 리팩토링 용이성 등등. 결국 모두 OOP
를 하게 됨으로써 얻는 이득이다.
자바진영에 다양한 ORM프레임워크가 존재하는데 그중 Hibernate 프레임워크가 가장 많이 사용된다.