JPA(Java Persistence API) 는 Java 언어를 통해서 데이터베이스와 같은 영속 계층을 처리하고자 하는 스펙입니다. JPA를 이해하기 위해서는 우선 ORM (Object-Relational Mapping) 이라는 기술에 대해서 먼저 설명해야만 합니다.
ORM 란?
ORM (Object Relation Mapping) 은 단어에서 보는 것과 같이 객체지향과 관련이 있다. ORM 을 간단히 이해하자면 '객체지향 패러다임을 관계형 데이터베이스에 보존하는 기술'이라고 할 수 있습니다. 패러다임 입장에서는 '객체지향 패러다임을 관계형 패러다임으로 매핑(mapping)해 주는 개념'이라고 볼 수 있습니다. 기본적으로 자바와 데이터베이스가 유사한 점이 있는데, 자바의 클래스와 데이터베이스의 테이블은 각자 멤버와 컬럼을 갖는다는 점에서 유사한 점이 있습니다. 또한 '인스턴스'와 '튜플'도 상당히 유사한 면을 갖고 있습니다. 객체지향에서는 클래스에서 인스턴스를 생성해서 인스턴스라는 '공간'에 데이터를 보관하는데, 테이블에서는 하나의 'Row'에 데이터를 저장하게 됩니다. 둘의 차이점이라면 '객체'가 데이터 + 행위(메서드)라는 의미였다면 '튜플'은 데이터만을 의미한다는 점이 다를 뿐입니다. 이런 유사한 점을 '객체지향의 구조가 관계형 데이터베이스'와 유사하다고 말합니다. 이런 특징에 기초해서'객체지향을 자동으로 관계형 데이터베이스에 맞게' 처리해 주는 기법에 대한 연구가 시작되었고 그 결과물이 ORM의 시작이었습니다. ORM은 완전히 새로운 패러다임을 주장하는 것이 아닌 '객체지향'과 '관계형' 사이의 변환 기법을 의미합니다. 따라서 언어에 국한되지 않고, 관계형 패러다임을 가지고 있다면 데이터베이스의 종류를 구분하지 않습니다. 이렇게 유사한 점을 이용해서 객체지향 프로그래밍과 데이터베이스 간의 연결하는 기술을 ORM (Object Relation Mapping) 이라고 한다. 여기서 Mapping 이라고 하는 것은 1 : 1 로 연결한다는 것을 의미한다.
JPA 란?
JPA는 'Java Persistence API'의 약어로 ORM을 Java 언어에 맞게 사용하는 '스펙'입니다. 따라서 ORM이 상위의 개념이 되고 JPA는 Java라는 언어에 국한된 개념으로 볼 수 있습니다. JPA는 단순한 스펙이기 때문에 해당 스펙을 구현하는 구현체마다 회사나 프레임워크의 이름이 다릅니다. 여러 프레임워크가 있지만 그중에서 가장 유명한 것은 'Hibernate' 입니다.
구조를 보면 위의 그림과 같다.
Spring Data JPA와 JPA
스프링 부트는 JPA의 구현체 중에서 'Hibernate' 구현체를 사용한다. Hibernate 는 '오픈소스'로 ORM을 지원하는 프레임워크입니다. 다른 프레임워크도 그러하지만 Hibernate 는 단독으로 프로젝트에 적용이 가능한 독립된 프레임워크입니다. 따라서 스프링 부트가 아닌 스프링만을 이용한다고 해도 Hibernate 와 연동해서 JPA를 사용할 수 있습니다. 프로젝트 생성 시에 추가한 'Spring Data JPA'는 Hibernate 를 스프링 부트에서 쉽게 사용할 수 있는 추가적인 API 들을 제공합니다. 스프링 프레임워크 자체가 대부분의 다른 프레임워크와의 호환성을 위한 라이브러리를 제공하는 경우가 많은데 'Spring Data JPA' 역시 마찬가지의 예시입니다. 즉 Spring Data JPA를 사용하면 다음과 같은 흐름으로 프로젝트에서 데이터베이스까지 연결되게 된다.
정리하자면 ORM 중에서 JAVA 에 특화되어 개발된 것이 JPA 이다. JPA 를 사용하면 데이터베이스 종류에 상관 없이 공통된 문법을 통해 해당 데이터베이스에 맞는 쿼리를 작성해준다. 즉 자바를 통해 만든 객체에 맞는 쿼리를 데이터베이스 종류 상관없이 만들어주는 엄청난 편의성을 제공해준다.
Java Optional 클래스 예제로 이해하기
package com.example.demo.etc;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/*
* optional 은 객체를 감싸는 wrapper 클래스이다.
* 객체에 값이 있는지 없는지를 확인하기 위한 용도이다.
* optional 을 사용하면 값이 비어있는지 확인하는 코드가 더 짧아지는 효과를 가질 수 있다.
* 그리고 null 값을 사용하면서 발생하는 오류를 방지할 수 있게 된다.
*/
@SpringBootTest
public class OptionalTest {
@Test
void Optional사용하기() {
Optional<String> opt = Optional.of("apple"); // 값 저장; apple 이라는 String 을 만든 다음에 Optional 이라는 클래스 wrapper 로 감쌀 수 있게 된다.
System.out.println(opt.get()); // 값 꺼내기
System.out.println(opt.isEmpty()); // 값 없는지 확인
System.out.println(opt.isPresent()); // 값 있는지 확인
System.out.println(opt.orElse("banana")); // 값이 없으면 banana 로 대체
}
@Test
void of와ofNullable의차이() {
String str = null;
Optional<String> opt1 = Optional.of(str); // of 메소드로 null 값을 입력받으면 에러남, 그런데 컴파일 오류가 아니라 런타임 에러가 발생한다.
Optional<String> opt2 = Optional.ofNullable(str); // ofNullable 메소드는 null 값을 입력받을 수 있음
}
@Test
void 빈객체를사용하는경우() {
Optional<String> opt = Optional.ofNullable(null);
System.out.println(opt.get()); // 에러가 발생하게 된다. 값을 꺼내려고 하는데 값이 없기 때문에 에러가 발생하게 된다.
}
@Test
void if를사용하여null값체크하기() {
String str = "banana";
if(str != null) {
System.out.println("값이 존재합니다");
}
}
@Test
void optional을사용하여null값체크하기() {
String str = "banana";
Optional<String> opt = Optional.ofNullable(str);
opt.ifPresent(name -> System.out.println("값이 존재합니다")); // 람다식 사용
// 위의 if null 체크 방식을 더 간결하게 표현할 수 있다
}
}