[Java] Spring Boot: ORM, JPA, 하이버네이트
이전 내용
[Java] Spring Boot: 테스트 코드
이전 내용 [Java] Spring Boot: 방명록 Rest API 구현(feat. MySQL 연동)이전 내용 [Java] Spring Boot: 코드, 요청&응답 과정 이해하기이전 내용 [Java] Spring Boot: Maven Repository이전 내용 [Java] 스프링부트: Spring Initial
puppy-foot-it.tistory.com
SQL을 몰라도 데이터베이스를 조작할 수 있게 해주는 편리한 도구인 ORM.
자바로 작성된 경량의 관계형 데이터베이스 관리 시스템(RDBMS)인 H2
스프링 부트의 ORM 기술 표준인 JPA.
그리고 JPA를 구현하기 위한 하이버네이트.
ORM (Object-Relational Mapping)
객체와 데이터베이스를 연결해 자바 언어로만 데이터베이스를 다룰 수 있게 해주는 도구.
ORM은 객체 지향 프로그래밍 언어에서 사용하는 객체 모델과 관계형 데이터베이스의 데이터 구조 간의 매핑을 자동으로 수행하는 기술이다. 이 방법은 개발자가 데이터베이스의 레코드에 직접 접근하지 않고도 객체를 통해 데이터 처리할 수 있도록 한다.
ORM이 있다면 데이터베이스의 값을 마치 객체처럼 사용할 수 있어 SQL을 전혀 몰라도 자바 언어로만 데이터베이스에 접근해서 원하는 데이터를 받아올 수 있다.
[특장점]
- 코드 간결성: SQL 쿼리를 직접 작성하지 않고도 데이터베이스 작업을 수행할 수 있어 코드가 간결해진다.
- 유지보수 용이성: 객체와 관계형 모델 간의 매핑을 정의하여 데이터와 비즈니스 로직 간의 분리를 명확히 한다. ▶ 비즈니스 로직에만 집중할 수 있다.
- 생산성 향상: 개발 속도를 높이고, 성능을 최적화하는 데 유리하다.
- 데이터베이스 시스템에 대한 종속성 감소: 데이터베이스 시스템이 추상화되어 있기 때문에 MySQL에서 PostgreSQL로 전환한다고 해도 추가로 드는 작업의 거의 없다.
- ERD에 대한 의존도 낮춤: 매핑하는 정보가 명확하기 때문에 ERD에 대한 의존도를 낮출 수 있고 유지보수 때 유리.
[단점]
- 프로젝트의 복잡성이 커질수록 사용 난이도 상승
- 복잡하고 무거운 쿼리는 ORM으로 해결 불가능한 경우 있음.
H2
H2는 자바로 작성된 경량의 관계형 데이터베이스 관리 시스템(RDBMS)이다. H2 데이터베이스는 인메모리(in-memory) 데이터베이스로 사용할 수 있으며, 파일 기반 데이터베이스로도 작동할 수 있다. 즉, H2는 데이터를 다른 공간에 따로 보관하는 것이 아니라 애플리케이션 자체 내부에 데이터를 저장한다. 그렇기 때문에 애플리케이션을 다시 실행하면 데이터가 초기화되기 때문에 개발 시에 테스트 용도로 많이 사용한다.
[특장점]
- 경량성: 설치와 실행이 간편하며, 메모리 내에서 빠르게 작동.
- 임베디드 및 서버 모드 지원: 애플리케이션 내에서 임베디드로 사용할 수도 있고, 독립적인 서버로도 사용할 수 있다.
- JDBC 호환: 자바 데이터베이스 연결(JDBC) API를 지원하여 자바 애플리케이션에서 쉽게 사용할 수 있다.
[단점]
- 애플리케이션 내부에 데이터를 저장하므로, 애플리케이션 종료 시 데이터 삭제됨.
JPA (Java Persistence API)
JPA는 자바에서 ORM을 위한 인터페이스 그룹을 정의한 표준 API다. JPA는 Java EE 및 Java SE에서 사용할 수 있으며, 데이터 지속성을 위해 객체와 관계형 데이터베이스 간의 매핑을 제공한다.
JPA는 자바에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이므로, 실제 사용을 위해서는 ORM 프레임워크를 추가로 선택해야 한다. (대표적으로 하이버네이트)
[특장점]
- 표준화: 데이터베이스와의 상호작용을 위한 표준 API를 제공하여 다양한 구현체를 사용할 수 있다.
- 객체 관계 매핑: 객체 모델과 관계형 데이터베이스 모델 간의 매핑을 정의하는 방법 제공.
- 쿼리 언어 지원: JPQL(Java Persistence Query Language)을 사용하여 데이터베이스 쿼리를 객체 지향적으로 정의 가능.
◆ 엔티티 매니저(Entity Manager), 영속성 컨텍스트(Persistence Context)
JPA에서 엔티티 매니저(Entity Manager)와 영속성 컨텍스트(Persistence Context)는 매우 중요한 개념이다. 이 두 가지는 JPA를 사용하여 데이터베이스와 객체 간의 상호작용을 관리하는 핵심 요소들이다.
★ 엔티티(Entity)
데이터베이스의 테이블과 매핑되는 객체.
본질적으로는 자바 객체이므로 일반 객체와 다르지 않으나, 데이터베이스의 테이블과 직접 연결된다는 아주 특별한 특징이 있어 구분지어 부른다.
엔티티는 객체이나 데이터베이스에 영향을 미치는 쿼리를 실행하는 객체이다.
1. 엔티티 매니저 (Entity Manager)
엔티티 매니저는 JPA에서 엔티티를 관리하는 데 사용되는 인터페이스다. 엔티티 매니저는 CRUD(Create, Read, Update, Delete) 작업을 수행하고, 영속성 컨텍스트와 상호작용하여 엔티티의 상태를 관리한다.
즉, 엔티티 매니저는 엔티티를 관리해 데이터베이스와 어플리케이션 사이에서 객체를 생성, 수정, 삭제하는 등의 역할을 하며, 이런 엔티티 매니저를 만드는 곳이 엔티티 매니저 팩토리이다.
▶ 스프링 부트는 내부에서 엔티티 매니저 팩토리를 하나만 생성해서 관리하고 @PersistanceContext 또는 @Autowired 애너테이션을 사용해서 엔티티 매니저를 사용한다. 즉, 엔티티 매니저는 Spring Data JPA에서 관리하므로 개발자가 직접 생성하거나 관리할 필요가 없다.
@PersistenceContext
private EntityManager entityManager;
public void saveArticle(Article article) {
entityManager.persist(article); // 새로운 엔티티 추가
}
public Article findArticle(Long id) {
return entityManager.find(Article.class, id); // 엔티티 조회
}
- 엔티티 추가: persist() 메소드를 사용하여 새로운 엔티티를 영속성 컨텍스트에 추가.
- 엔티티 수정: merge() 메소드를 통해 기존의 엔티티 수정.
- 엔티티 삭제: remove() 메소드를 사용하여 영속성 컨텍스트에서 엔티티 삭제.
- 엔티티 조회: find() 메소드를 통해 특정 ID를 가진 엔티티 조회.
- 쿼리 실행: JPQL(Java Persistence Query Language) 또는 Criteria API를 사용하여 데이터베이스 쿼리 실행.
2. 영속성 컨텍스트 (Persistence Context)
영속성 컨텍스트는 엔티티의 상태를 관리하는 환경으로 가상의 공간이다. 이 컨텍스트 안에서는 엔티티가 관리되고, 엔티티에 대한 작업이 영속적(persistent)이 된다. 영속성 컨텍스트는 엔티티를 메모리에 유지하고, 데이터베이스와의 상호작용을 최적화한다. 이로 인해 데이터베이스에서 효과적으로 데이터를 가져올 수 있고, 엔티티를 편하게 사용할 수 있다.
▶ 스프링 부트에서는 이런 쿼리를 자바 코드로 작성하고 이를 JPA가 알아서 쿼리로 변경해준다.
[영속성 컨텍스트의 기본적인 특징]
- 엔티티의 상태 관리(변경 감지): 영속성 컨텍스트 안에 있는 엔티티는 "영속적" 상태가 된다. 즉, 데이터베이스와 연결되어 실시간으로 변경 사항이 반영된다. 이를 통해 데이터베이스 시스템의 부담을 줄일 수 있다.
- 1차 캐시: 영속성 컨텍스트는 내부에 1차 캐시를 가지고 있으며, 이때 캐시의 키는 엔티티의 @Id 애너테이션이 달린 기본키 역할을 하는 식별자이며 값은 엔티티이다. 엔티티를 메모리에 저장하여 동일한 엔티티에 대한 여러 번의 요청에 대해 데이터베이스에 쿼리를 보내지 않고도 결과를 제공하며, 이는 성능을 향상시키며 매우 빠르게 데이터를 조회할 수 있다.
- 자동 동기화: 영속성 컨텍스트에 있는 엔티티의 상태가 변경되면, 해당 엔티티는 트랜잭션이 종료될 때 자동으로 데이터베이스와 동기화된다.
- 쓰기 지연: 트랜잭션을 커밋하기 전까지는 데이터베이스에 실제로 질의문을 보내지 않고 쿼리를 모았다가 트랜잭션을 커밋하면 모았던 쿼리를 한 번에 실행하는 것을 의미한다. 적당한 묶음으로 쿼리를 요청할 수 있어 데이터베이스 시스템의 부담을 줄일 수 있다. (ex. 데이터 추가 쿼리가 4개일 경우 트랜잭션을 커밋하는 시점에 4개의 쿼리를 한 번에 전송)
- 지연 로딩: 쿼리로 요청한 데이터를 애플리케이션에 바로 로딩하는 것이 아니라 필요할 때 쿼리를 날려 데이터를 조회하는 것을 의미한다.
- 영속성 컨텍스트의 생명주기
- 생성: 영속성 컨텍스트는 EntityManager가 생성될 때 생성.
- 활성 상태: 엔티티 매니저가 사용되면서 영속성 컨텍스트에 엔티티가 영속화.
- 종료: 트랜잭션이 완료되거나 엔티티 매니저가 닫힐 때 영속성 컨텍스트가 종료.
스프링 데이터와 스프링 데이터 JPA
스프링 데이터(Spring Data)와 스프링 데이터 JPA(Spring Data JPA)는 데이터 접근을 쉽게 하고 효율적으로 만들기 위한 스프링 프레임워크의 일부분이다. 두 기술은 유사하지만 각각의 목적과 기능이 다르다.
1. 스프링 데이터 (Spring Data)
스프링 데이터는 비즈니스 로직에 더 집중할 수 있게 데이터베이스 사용 기능을 클래스 레벨에서 추상화하여 다양한 데이터 저장소에 대한 데이터 접근성을 일관되게 제공하기 위한 프로젝트다. NoSQL 데이터베이스, RDBMS, 그리고 더 다양한 데이터 소스에 대한 추상화 계층을 제공한다.
스프링 데이터에서 제공하는 인터페이스를 통해서 스프링 데이터를 사용할 수 있다. 이 인터페이스에서는 CRUD를 포함한 여러 메서드가 포함되어 있으며, 알아서 쿼리를 만들어주고 페이징 처리 기능과 메서드 이름으로 자동으로 쿼리 빌딩하는 기능이 제공된다. 또한, 각 데이터베이스의 특성에 맞춰 기능을 확장해 제공하는 기술도 제공한다.
- 데이터 접근 기술 통합: MongoDB, Redis, Neo4j 등 다양한 데이터 저장소에 대한 일관된 프로그래밍 모델 제공.
- Repository 패턴 지원: 데이터 접근 방법의 표준화와 더불어 코드의 재사용성 향상.
- 커스터마이징 가능한 쿼리 방법: 메서드 이름 규칙을 따르는 인터페이스를 정의하여 자동으로 쿼리를 생성할 수 있는 기능 제공.
2. 스프링 데이터 JPA (Spring Data JPA)
스프링 데이터 JPA는 스프링 데이터의 하위 프로젝트로, JPA를 기반으로 데이터 접근을 관리하기 위해 설계되었다. JPA와 스프링의 강력한 기능을 통합하여 데이터베이스와의 상호작용을 더 쉽게 만들어준다.
스프링 데이터 JPA는 스프링 데이터의 공통적인 기능에서 JPA의 유용한 기술이 추가된 기술로, 스프링 데이터의 인터페이스인 PagingAndSortingRepository를 상속받아 JpaRepository 인터페이스를 만들었으며, JPA를 더 편리하게 사용하는 메소드를 제공한다. 스프링 데이터 JPA를 사용하면 리포지토리 역할을 하는 인터페이스를 만들어 데이터베이스의 테이블 조회, 수정, 생성, 삭제 같은 작업을 간단히 할 수 있다.
- JPA의 편리한 사용: JPA의 복잡한 설정을 간소화하여 편리하게 사용할 수 있도록 도와준다.
- 자동으로 쿼리 생성: 메서드 이름을 기반으로 쿼리를 자동으로 생성할 수 있다.
- 스프링의 트랜잭션 관리 통합: JPA와 스프링의 트랜잭션 관리가 통합되어 데이터 일관성 보장.
JpaRepository 인터페이스를 우리가 만든 인터페이스에서 상속받고 제네릭에는 관리할 <엔티티 이름, 엔티티 기본키의 타입>을 입력하면 기본 CRUD를 위해 만든 메소드로 사용할 수 있다.
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> {
// 이메일로 사용자 조회
User findByEmail(String email);
// 사용자 이름으로 사용자 조회
List<User> findByName(String name);
// 사용자 이메일로 사용자 삭제
void deleteByEmail(String email);
// 특정 ID를 가진 사용자가 존재하는지 확인
boolean existsById(Long id);
}
- List<User> findByName(String name);
이 메소드는 주어진 이름으로 사용자를 조회. 여러 명의 사용자가 동일한 이름을 가질 수 있으므로 List<User> 형식으로 반환. - void deleteByEmail(String email);
주어진 이메일로 사용자를 삭제하는 메소드. 이메일이 유일하다고 가정하므로, 특정 사용자를 쉽게 삭제할 수 있다. - boolean existsById(Long id);
특정 ID를 가진 사용자가 존재하는지 확인하는 메소드. 이 메소드는 반환값이 boolean이므로, ID가 존재하면 true를 반환하고, 그렇지 않으면 false를 반환. 이를 통해 사용자 존재 여부를 간편하게 확인할 수 있다.
Hibernate (하이버네이트)
Hibernate는 JPA의 구현체 중 하나로, 자바에서 ORM을 위한 강력하고 유연한 프레임워크다. 데이터베이스와 객체 간의 매핑을 자동으로 처리하며, JPA의 표준을 따른다. 내부적으로는 JDBC API를 사용한다.
하이버네이트의 목표는 자바 객체를 통해 데이터베이스 종류에 상관없이 데이터베이스를 자유자제로 사용할 수 있게 하는 데 있다.
[특장점]
- 유연한 매핑: 다양한 매핑 전략을 지원하여 복잡한 데이터 모델도 쉽게 처리할 수 있다.
- 캐시 기능: 성능을 향상시키기 위한 다양한 캐싱 전략 제공.
- 자동 생성 데이터베이스 스키마: 엔티티 클래스에 기반하여 데이터베이스 스키마를 자동으로 생성할 수 있다.
- JPQL과 Criteria API 지원: 객체 지향 쿼리를 사용할 수 있도록 JPQL 및 Criteria API 지원.
예제 코드로 확인하기
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Getter
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", updatable = false)
private Long id;
@Column(name = "name", nullable = false)
private String name;
}
- 애너테이션(@)
- @Entity: 이 클래스가 JPA 엔티티임을 나타낸다. 이 애너테이션이 붙은 클래스는 데이터베이스의 테이블과 매핑된다.
- @NoArgsConstructor(access = AccessLevel.PROTECTED): 기본 생성자를 생성하는 Lombok 애너테이션이다. protected 접근 제어자를 사용하여, 기본 생성자가 생겨나지만 외부 클래스에서 직접 호출할 수 없게 설정한다. 보통 JPA에서는 엔티티 클래스를 재사용하기 위해 기본 생성자가 필요하다.
- @AllArgsConstructor: 모든 필드를 파라미터로 받는 생성자를 생성하는 Lombok 애너테이션이다. 이를 통해 객체를 손쉽게 생성할 수 있다.
- @Getter: 모든 필드에 대한 Getter 메서드를 자동 생성한다. 이를 통해 각 필드의 값을 외부에서 읽을 수 있게 된다.
- 필드 정의
- @Id: 이 필드가 엔티티의 기본 키(primary key)임을 나타낸다. JPA는 이 필드를 사용하여 엔티티의 유일성을 보장한다.
- @GeneratedValue(strategy = GenerationType.IDENTITY): 기본 키의 값을 자동으로 생성할 전략을 정의한다. IDENTITY 전략을 사용하면 데이터베이스에서 자동으로 증가하는 값을 사용하여 기본 키를 생성한다. 이는 MySQL과 같은 데이터베이스에서 일반적으로 사용된다.
- @Column(name = "id", updatable = false): 이 필드가 데이터베이스의 id라는 컬럼에 매핑됨을 나타낸다. updatable = false를 설정하여 이 필드의 값이 수정되지 않도록 한다. 엔티티가 저장될 때, 데이터베이스에서 자동으로 생성되므로 업데이트할 필요가 없다.
[자동키 생성 설정 방식]
자동키 | 설명 |
AUTO | 선택한 데이터베이스 dialect에 따라 방식을 자동으로 선택(기본값) |
IDENTITY | 기본 키 생성을 데이터베이스에 위임(=AUTO_INCREMENT) |
SEQUENCE | 데이터베이스 시퀀스를 사용해서 기본 키를 할당. (주로 오라클에서 사용) |
TABLE | 키 생성 테이블 사용 |
[@Column 애너테이션 속성]
속성 | 설명 |
name | 매핑할 데이터베이스 칼럼의 이름 지정 |
nullable | true인 경우 해당 컬럼에 null 값 허용. 기본값은 true |
unique | true인 경우 해당 컬럼에 대해 유일성 제약 조건 추가 (중복 방지). 기본값은 false |
length | 문자열 타입(String)에 대해 최대 길이 지정 |
insertable | true인 경우 INSERT 쿼리에 해당 컬럼 포함됨. 기본값은 true |
updatable | true인 경우 UPDATE 쿼리에 해당 컬럼 포함됨. 기본값은 true |
columnDefinition | 데이터베이스에서 CREATE TABLE 쿼리에서 해당 컬럼의 정의를 명시적으로 지정. |
percision | 간접적으로 BigDecimal 타입의 정확도 지정 |
scale | BigDecimal 타입에서 소수점 이하 자릿수 지정 |
insertable | 해당 컬럼이 INSERT 쿼리에 포함되는지 여부 지정. 기본값은 true |
table | 컬럼이 매핑될 테이블의 이름 지정. 주로 조인된 엔티티에서 사용. |
[참고]
스프링 부트3로 백엔드 개발자 되기
다음 내용
[Java] Spring Boot: 스프링 MVC , 디스패처 서블릿
이전 내용 [Java] Spring Boot: ORM, JPA, 하이버네이트이전 내용 [Java] Spring Boot: 테스트 코드이전 내용 [Java] Spring Boot: 방명록 Rest API 구현(feat. MySQL 연동)이전 내용 [Java] Spring Boot: 코드, 요청&응답 과정
puppy-foot-it.tistory.com