JPA는 왜 탄생했을까?
- JPA(Java Persistence API)는Java 애플리케이션에서 데이터베이스와 객체 간의 매핑 문제를 해결하기 위해 등장했다.
1. 기존 데이터베이스 연동 방식의 한계
과거에는 애플리케이션에서 데이터베이스와 상호작용하기 위해 JDBC(Java Database Connectivity)를 주로 사용했다. 하지만 이 방식에는 여러 문제가 있었다.
- 반복적인 코드: SQL 쿼리를 작성하고, 결과를 받아서 객체로 변환하는 작업을 반복해야 했다. 이는 시간이 많이 걸리고 유지보수가 어려웠다.
- 객체와 관계형 데이터의 불일치 문제: Java는 객체 지향 언어인데, 데이터베이스는 관계형 모델을 따른다. 이를 해결하기 위해 많은 개발자들이 수작업으로 매핑 코드를 작성해야 했다.
- 유지보수 어려움: 데이터베이스 스키마가 변경되면 관련된 모든 SQL과 매핑 코드를 수정해야 했다.
- 패러다임 불일치: 자바와 RDB의 구조와 동작 방식이 서로 달라서 발생하는 문제를 말한다. 데이터를 저장하고 조회하는 방식이 다르기 때문에 생겨났다.
| 특징 | OOP | RDB |
| 데이터 표현 방식 | 객체(클래스, 필드, 메서드) | 테이블, 행(row), 열(column) |
| 관계 표현 | 객체 참조(A 객체가 B 객체를 포함) | 외래 키를 통한 관계 |
| 상태 관리 | 메모리 내 객체의 상태로 관리 | SQL로 데이터 CRUD 작업 수행 |
| 상속 | 클래스 상속 구조로 표현 | 상속 개념이 없고, 테이블로 분리해서 표현 |
| 동작 | 메서드를 통해 동작 정의 | 데이터 중심, 동작은 SQL로 정의 |
2. ORM의 등장 및 ORM이 해결해야하는 문제점과 해결책
이러한 문제를 해결하기 위해 ORM(Object-Relational Mapping)이라는 개념이 등장했습니다. ORM은 객체와 관계형 데이터베이스 간의 매핑을 자동화하는 기술로, 이를 통해 개발자는 객체 지향적으로 코드를 작성하면서도 데이터베이스와 쉽게 연동할 수 있다.
상속의 문제
- 객체 : 객체간에 멤버변수나 상속관계를 맺을 수 있다.
- RDB : 테이블들은 상속관계가 없고 모두 독립적으로 존재한다.
해결방법 : 매핑정보에 상속정보를 넣어준다. (@OneToMany, @ManyToOne)
관계 문제
- 객체 : 참조를 통해 관계를 가지며 방향을 가진다. (다대다 관계도 있음)
- RDB : 외래키(FK)를 설정하여 Join 으로 조회시에만 참조가 가능하다. (즉, 다대다는 매핑 테이블 필요)
해결방법 : 매핑정보에 방향정보를 넣어준다. (@JoinColumn, @MappedBy)
탐색 문제
- 객체 : 참조를 통해 다른 객체로 순차적 탐색이 가능하며 콜렉션도 순회한다.
- RDB : 탐색시 참조하는 만큼 추가 쿼리나, Join 이 발생하여 비효율적이다.
해결방법 : 매핑/조회 정보로 참조탐색 시점을 관리한다.(@FetchType, fetchJoin())
밀도 문제
- 객체 : 멤버 객체크기가 매우 클 수 있다.
- RDB : 기본 데이터 타입만 존재한다.
해결방법 : 크기가 큰 멤버 객체는 테이블을 분리하여 상속으로 처리한다. (@embedded)
식별성 문제
- 객체 : 객체의 hashCode 또는 정의한 equals() 메소드를 통해 식별
- RDB : PK 로만 식별
해결방법 : PK 를 객체 Id로 설정하고 EntityManager는 해당 값으로 객체를 식별하여 관리 한다.(@Id, @GeneratedValue)
3. JPA를 사용하는 이유
JPA의 사용 목적은 SQL 중심적인 개발에서 객체 중심으로 개발하기 위함에 있다.
1) 생산성
//저장
jpa.persist(student);
//조회
Student student = jpa.find(Student.class, studentId);
//수정
student.setName("수정할 이름");
//삭제
jpa.remove(student);
- persist란 "영구히 저장한다"는 뜻
- 마치 컬렉션에 저장한듯 객체가 저장되어 조회가 간편하다.
- 수정하고자 할 때 꺼낸 객체에 setName() 하면 된다.
2) 유지보수성
//기존
public class Student {
private String id;
private String name;
}
//필드 수정
public class Student {
private String id;
private String name;
private Integer age;
}
- 객체 필드가 수정 되어도 SQL은 JPA가 자동으로 처리한다.
3) 성능
- 1차 캐시
Student std1 = jap.find(Student.class, studentId); //실행 결과 1차 캐시에 저장
Student std2 = jap.find(Student.class, studentId); //캐시에서 조회
std1 == std2; // true
- 쓰기 지연
// 트랜잭션 시작
transaction.begin();
jpa.persist(grade);
jpa.persist(std1);
jpa.persist(std2);
// 트랜잭션 제출, JDBC BATCH SQL
transaction.commit();
"
- ORM과 같은 중간 기술이 있으면 한번에 모아서 요청을 보내는것이 가능하다.
- 네트워크 통신이 한번만 발생하여 비용이 감소된다.
"
- 지연 로딩, 즉시 로딩
// 지연 로딩
Student student = studentRepository.find(studentId); // SELECT * FROM student
Language language = student.getLanguage();
String languageName = language.getName(); // SELECT * FROM language
// 즉시 로딩
// SELECT s.*, l.* FROM student s JOIN Language l ON
Student student = studentRepository.find(studentId);
Language language = student.getLanguage();
String languageName = language.getName();
4) 패러다임 불일치 해결
- 객체와 테이블 매핑
- JPA는 객체를 테이블과 매핑하여 객체 지향적으로 프로그래밍하면서도 관계형 데이터베이스와 연동 할 수 있도록 돕는다.
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "studnetname")
private String studnetname;
}
- 연관 관계 매핑
- 객체 간의 관계 (1:1, 1:N, N:M)를 데이터베이스의 외래 키로 매핑한다.
@OneToMany(mappedBy = "student")
private List<Language> LanguageList;
JPA는 개발자의 생산성을 높이고 객체-관계 불일치를 해결하기 위해 탄생했다. JPA 덕분에 복잡한 DB 작업에서 벗어나 애플리케이션 비즈니스 로직에 더 집중할 수 있게 되었다.
'Back-End > JPA' 카테고리의 다른 글
| JPA 연관관계 Mapping (0) | 2024.12.10 |
|---|---|
| JPA - Entity 만들기 (0) | 2024.12.10 |
| JPA의 지연 저장소, 변경 감지, 그리고 flush와 Commit (1) | 2024.12.10 |
| JPA - 영속성 컨텍스트(1차 캐시, 동일성 보장, 쓰기 지연) (0) | 2024.12.10 |
| JPA - 영속성 컨텍스트(Entity) (0) | 2024.12.10 |