본문 바로가기

Back-End/JPA

JPA에 대해서

JPA는 왜 탄생했을까?

  • JPA(Java Persistence API)는Java 애플리케이션에서 데이터베이스와 객체 간의 매핑 문제를 해결하기 위해 등장했다.

1. 기존 데이터베이스 연동 방식의 한계

과거에는 애플리케이션에서 데이터베이스와 상호작용하기 위해 JDBC(Java Database Connectivity)를 주로 사용했다. 하지만 이 방식에는 여러 문제가 있었다.

  1. 반복적인 코드: SQL 쿼리를 작성하고, 결과를 받아서 객체로 변환하는 작업을 반복해야 했다. 이는 시간이 많이 걸리고 유지보수가 어려웠다.
  2. 객체와 관계형 데이터의 불일치 문제: Java는 객체 지향 언어인데, 데이터베이스는 관계형 모델을 따른다. 이를 해결하기 위해 많은 개발자들이 수작업으로 매핑 코드를 작성해야 했다.
  3. 유지보수 어려움: 데이터베이스 스키마가 변경되면 관련된 모든 SQL과 매핑 코드를 수정해야 했다.
  4. 패러다임 불일치: 자바와 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 작업에서 벗어나 애플리케이션 비즈니스 로직에 더 집중할 수 있게 되었다.