[Springboot] JPA 기본 정리
JPA란
JPA(Java Persistence API)는 자바 진영에서 데이터베이스와 관련된 작업을 편리하게 처리하기 위한 자바 표준 인터페이스입니다. JPA는 ORM(Object-Relational Mapping) 기술의 일종으로, 객체 지향 프로그래밍 언어인 자바와 관계형 데이터베이스 간의 매핑을 간소화하고 표준화하는 목적으로 개발되었습니다.
JPA의 주요 목표와 특징은 다음과 같습니다:
객체-관계 매핑 (ORM): JPA는 자바 객체와 데이터베이스 테이블 간의 매핑을 지원합니다. 개발자는 객체 지향적인 방식으로 데이터를 다룰 수 있으며, JPA가 이를 관계형 데이터베이스에 맞게 변환해주는 역할을 합니다.
표준 인터페이스: JPA는 자바 진영의 표준 인터페이스로, 여러 ORM 프레임워크들이 이 표준을 구현하고 있습니다. 대표적인 JPA 구현체로는 Hibernate, EclipseLink, Apache OpenJPA 등이 있습니다.
CRUD 작업 간소화: JPA를 사용하면 객체를 데이터베이스에 저장(CREATE), 조회(READ), 수정(UPDATE), 삭제(DELETE)하는 등의 작업이 간편해집니다. 복잡한 SQL 쿼리를 직접 작성하는 대신, JPA의 메서드를 사용하여 데이터를 다룰 수 있습니다.
객체 지향적인 쿼리 언어: JPA는 객체 지향적인 쿼리 언어인 JPQL(Java Persistence Query Language)을 제공합니다. 이를 사용하여 데이터베이스에 대한 쿼리를 객체 지향적으로 작성할 수 있습니다.
트랜잭션 관리: JPA는 트랜잭션을 관리하여 데이터베이스의 일관성을 보장합니다. 개발자는 명시적으로 트랜잭션을 시작하고 커밋 또는 롤백하는 등의 작업을 할 수 있습니다.
JPA는 Java EE(Java Platform, Enterprise Edition)의 일부로 시작되었지만, 지금은 Java SE(Java Platform, Standard Edition)에서도 독립적으로 사용할 수 있습니다. JPA를 사용하면 데이터베이스와의 상호작용을 더 추상화하고, 객체 지향적인 코드 작성을 강화할 수 있습니다.
ddl-auto 옵션
spring.jpa.hibernate.ddl-auto 속성은 Spring Data JPA와 Hibernate를 사용할 때, 데이터베이스 테이블의 스키마를 자동으로 생성 또는 업데이트할 때 사용되는 설정입니다. 이 속성은 application.properties 또는 application.yml 파일에 설정됩니다.
1 . create
테이블을 새로 생성합니다. 이미 존재하는 테이블은 삭제됩니다. 주의: 데이터가 모두 삭제되므로 주의해서 사용해야 합니다.
spring.jpa.hibernate.ddl-auto = create
2 . update
테이블이 이미 존재하면 변경사항만 업데이트합니다. 테이블이 없으면 새로 생성합니다.
spring.jpa.hibernate.ddl-auto = update
3 . validate
엔티티 클래스와 테이블의 스키마가 일치하는지만 검증합니다. 테이블이 없으면 에러가 발생합니다.
spring.jpa.hibernate.ddl-auto = validate
4 . create-drop
테이블을 생성한 후, 애플리케이션 종료 시에 테이블을 삭제합니다. 주로 테스트 환경에서 사용됩니다.
spring.jpa.hibernate.ddl-auto = create-drop
5 . none
자동으로 스키마를 생성하지 않습니다. 설정된대로 사용합니다.
spring.jpa.hibernate.ddl-auto = none
jpa 관련 어노테이션
@Entity: 클래스가 JPA 엔터티임을 나타냅니다. 이 어노테이션이 붙은 클래스는 데이터베이스의 테이블과 매핑됩니다.
@Table: 엔터티 클래스와 매핑될 데이터베이스 테이블의 정보를 지정합니다. 특정 테이블명, 스키마명, 인덱스 등을 설정할 수 있습니다.
@Id: 엔터티 클래스의 기본 키 필드를 나타냅니다.
@GeneratedValue: 기본 키의 값을 자동으로 생성하는 방법을 지정합니다. 주로 자동 증가(AUTO_INCREMENT) 등의 전략을 선택할 수 있습니다.
@Column: 엔터티 클래스의 필드와 데이터베이스 테이블의 컬럼 간의 매핑 정보를 지정합니다. 컬럼명, 길이, null 여부 등을 설정할 수 있습니다.
@OneToMany, @ManyToOne: 일대다 및 다대일 관계를 매핑할 때 사용됩니다.
@ManyToMany: 다대다 관계를 매핑할 때 사용됩니다.
@JoinColumn: 조인 컬럼의 설정을 지정합니다. 주로 외래 키(Foreign Key)를 지정할 때 사용됩니다.
@NamedQuery: 이름을 가진 JPQL 쿼리를 정의하고, 엔터티 클래스에서 이를 참조할 때 사용됩니다.
@NamedQueries: 여러 개의 @NamedQuery를 묶어서 정의할 때 사용됩니다.
@Transient: 특정 필드를 데이터베이스에 매핑하지 않도록 지정합니다. 즉, 이 필드는 영속성 컨텍스트에 저장되지 않습니다.
@Temporal: 날짜와 시간 타입을 매핑할 때 사용됩니다. DATE, TIME, TIMESTAMP 등을 선택할 수 있습니다.
@GeneratedValue 어노테이션 종류
JPA 엔터티 클래스의 기본 키를 자동으로 생성하는 방법을 지정하는 데 사용됩니다. 이 어노테이션을 사용할 때 strategy 속성을 이용하여 어떤 방법으로 자동 생성할지를 설정할 수 있습니다. 이 strategy 속성에 사용되는 값 중 하나가 GenerationType 열거형입니다.
여러 가지 GenerationType 값이 있지만, 대표적으로 사용되는 세 가지 값은 다음과 같습니다:
1 . GenerationType.IDENTITY:
주로 MySQL, PostgreSQL 등의 데이터베이스에서 사용됩니다. 데이터베이스의 자동 증가 기능을 이용하여 기본 키 값을 생성합니다. 예를 들어 MySQL의 AUTO_INCREMENT와 같은 기능을 사용합니다.
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
2 . GenerationType.SEQUENCE:
주로 Oracle, PostgreSQL 등의 데이터베이스에서 사용됩니다. 데이터베이스의 시퀀스를 사용하여 기본 키 값을 생성합니다. 시퀀스는 데이터베이스에서 정의하고, JPA는 이를 사용하여 값을 가져오게 됩니다.
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "my_sequence_generator")
@SequenceGenerator(name = "my_sequence_generator", sequenceName = "MY_SEQUENCE")
private Long id;
이 경우에는 SequenceGenerator를 꼭 사용해 줘야 한다.
3 . GenerationType.TABLE:
모든 데이터베이스에서 사용 가능한 방법으로, 특별한 테이블을 사용하여 기본 키 값을 생성합니다. JPA는 특별한 테이블을 생성하고 그 테이블을 사용하여 기본 키 값을 관리합니다.
@Id
@GeneratedValue(strategy = GenerationType.TABLE, generator = "my_table_generator")
@TableGenerator(name = "my_table_generator", table = "MY_GENERATOR_TABLE")
private Long id;
4 . GenerationType.AUTO
일반적으로 GenerationType.AUTO를 사용하면 JPA 구현체(Hibernate, EclipseLink 등)가 자동으로 데이터베이스 종류에 따라 올바른 전략을 선택합니다. 그리고 데이터베이스에서 제공하는 기능을 이용하여 기본 키를 생성합니다.
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
이 경우, Hibernate를 사용하는 경우에는 데이터베이스가 자동 증가(AUTO_INCREMENT)를 지원하면 GenerationType.IDENTITY로 매핑되고, 시퀀스를 지원하면 GenerationType.SEQUENCE로 매핑됩니다. 하지만 이렇게 자동으로 선택되는 방식은 사용하는 JPA 구현체에 따라 다를 수 있습니다.
GenerationType.AUTO를 사용하면 데이터베이스 종속성이 낮아지지만, 특정 데이터베이스에서 지원하는 특별한 기능을 활용하지 못할 수 있습니다. 데이터베이스에 따라 적절한 전략을 선택하고 싶다면 명시적으로 GenerationType.IDENTITY, GenerationType.SEQUENCE, 또는 GenerationType.TABLE 중 하나를 선택하는 것이 더 명확할 수 있습니다.
대부분의 경우에는 GenerationType.IDENTITY가 간편하게 사용되며, 데이터베이스 종류에 따라 맞춰서 선택하면 됩니다.
@CreationTimestamp 와 @UpdateTimestamp
@CreationTimestamp와 @UpdateTimestamp 어노테이션은 Hibernate에서 제공하는 어노테이션으로, 엔터티 클래스의 필드에 붙여 사용됩니다. 이 어노테이션들은 각각 엔터티가 생성될 때와 업데이트될 때의 타임스탬프를 자동으로 관리하는데 사용됩니다
1 . @CreationTimestamp:
엔터티가 생성될 때의 타임스탬프를 자동으로 설정합니다. 보통 데이터베이스에 새로운 레코드가 삽입될 때 해당 필드에 현재 시간을 자동으로 설정하는데 사용됩니다.
import org.hibernate.annotations.CreationTimestamp;
// ...
@CreationTimestamp
@Column(name = "created_at")
private LocalDateTime createdAt;
이렇게 사용하면 엔터티가 저장되기 전에 createdAt 필드에 현재 시간이 자동으로 설정됩니다.
2 . @UpdateTimestamp:
엔터티가 업데이트될 때의 타임스탬프를 자동으로 설정합니다. 데이터베이스에 이미 존재하는 레코드가 업데이트될 때 해당 필드에 현재 시간을 자동으로 설정하는데 사용됩니다.
import org.hibernate.annotations.UpdateTimestamp;
// ...
@UpdateTimestamp
@Column(name = "updated_at")
private LocalDateTime updatedAt;
이렇게 사용하면 엔터티가 업데이트되기 전에 updatedAt 필드에 현재 시간이 자동으로 설정됩니다.
이러한 어노테이션들은 주로 데이터베이스에 기록된 이벤트의 시간을 추적하고, 생성일자와 수정일자를 편리하게 관리하기 위해 사용됩니다
LomBok
Lombok은 자바 개발자들이 반복적이고 상투적인 코드를 줄이고 간결한 코드 작성을 도와주는 오픈 소스 라이브러리입니다. 주로 DTO(Data Transfer Object), 엔터티 클래스, 빌더 패턴, 게터(Getter), 세터(Setter) 등을 생성하는데 사용되며, 프로젝트의 유지보수성을 향상시키고 코드의 가독성을 개선하는데 도움이 됩니다.
Lombok이 제공하는 주요 기능들 중 몇 가지를 살펴보겠습니다:
1 . @Data:
@Data 어노테이션은 게터(Getter), 세터(Setter), equals(), hashCode(), toString() 등의 메서드를 자동으로 생성해줍니다.
import lombok.Data;
@Data
public class Person {
private String name;
private int age;
}
2 . @Getter / @Setter:
@Getter 어노테이션은 게터를 생성하고, @Setter 어노테이션은 세터를 생성합니다.
import lombok.Getter;
import lombok.Setter;
public class Person {
@Getter @Setter private String name;
@Getter @Setter private int age;
}
3 . @NoArgsConstructor, @AllArgsConstructor, @RequiredArgsConstructor:
@NoArgsConstructor는 파라미터 없는 기본 생성자를 생성합니다. @AllArgsConstructor는 모든 필드를 파라미터로 받는 생성자를 생성합니다. @RequiredArgsConstructor는 final이나 @NonNull 어노테이션이 붙은 필드들만을 파라미터로 받는 생성자를 생성합니다.
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
public class Person {
private String name;
private int age;
}
4 . @Builder:
빌더 패턴을 생성하는데 사용됩니다. 객체를 생성할 때 각 필드에 대한 메서드를 제공하여 빌더 형태로 객체를 생성할 수 있습니다.
import lombok.Builder;
@Builder
public class Person {
private String name;
private int age;
}
Lombok은 IDE 플러그인을 통해 지원되기 때문에, 프로젝트에 Lombok을 추가하고 IDE에 Lombok 플러그인을 설치하면 IDE에서 코드를 더 간편하게 작성할 수 있습니다. Lombok을 사용하면 반복적인 코드 작성을 줄여주어 코드의 양을 줄이고, 개발자가 핵심 로직에 더 집중할 수 있도록 도와줍니다.
JPA 리포지토리 인터페이스
Spring Data JPA는 리포지토리 인터페이스를 사용하여 Spring 애플리케이션에서 데이터베이스와 상호 작용하는 편리하고 강력한 방법을 제공합니다. 이 인터페이스를 사용하면 실제 구현 코드를 작성하지 않고도 일반적인 데이터베이스 작업을 수행할 수 있습니다. Spring Data JPA는 리포지토리 인터페이스에 정의된 메서드 시그니처를 기반으로 런타임에 필요한 코드를 생성합니다.
Spring Data JPA 리포지토리 인터페이스와 관련된 계층 구조와 주요 개념을 알아보겠습니다:
1 . Repository 인터페이스:
계층 구조의 최상위에 위치한 인터페이스로, 마커 인터페이스입니다. 어떠한 메서드도 제공하지 않고 모든 리포지토리 인터페이스의 기본 인터페이스 역할을 합니다.
2 . CrudRepository 인터페이스:
Repository 인터페이스를 확장하며 CRUD (생성, 읽기, 업데이트, 삭제) 작업을 제공합니다. save, findById, findAll, delete 등의 일반적으로 사용되는 메서드를 포함합니다. 엔터티 타입과 엔터티 기본 키 타입으로 매개변수화됩니다.
import org.springframework.data.repository.CrudRepository;
public interface UserRepository extends CrudRepository<User, Long> {
// 추가적인 사용자 정의 쿼리를 여기에 추가할 수 있습니다.
}
3 . PagingAndSortingRepository 인터페이스:
CrudRepository 인터페이스를 확장하며 결과를 페이징하고 정렬하는 추가 메서드를 제공합니다. findAll(Pageable pageable) 및 findAll(Sort sort)와 같은 메서드를 포함합니다.
import org.springframework.data.repository.PagingAndSortingRepository;
public interface UserRepository extends PagingAndSortingRepository<User, Long> {
// 추가적인 사용자 정의 쿼리를 여기에 추가할 수 있습니다.
}
4 . JpaRepository 인터페이스:
PagingAndSortingRepository 인터페이스를 확장하며 JPA 특화 기능을 제공합니다. 플러시, 배치 작업 및 메서드 이름에서 쿼리 파생을 위한 추가 메서드가 포함됩니다.
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
// 추가적인 사용자 정의 쿼리를 여기에 추가할 수 있습니다.
}
5 . 사용자 지정 리포지토리 인터페이스:
기존 리포지토리 인터페이스를 확장하여 사용자 지정 리포지토리 인터페이스를 만들 수 있습니다. 메서드 이름 규칙 또는 @Query 어노테이션을 사용하여 사용자 정의 쿼리 메서드를 정의할 수 있습니다.
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface CustomUserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.username = :username")
User findByUsername(@Param("username") String username);
}
6 . 리포지토리 인터페이스 네이밍 규칙:
Spring Data JPA는 메서드 이름 규칙을 따라 메서드 이름에서 쿼리를 자동으로 파생합니다. 예를 들어, User 엔터티에 대한 findByLastName 메서드는 자동으로 성(last name)으로 사용자를 찾는 쿼리를 생성합니다.
Spring Data JPA는 리포지토리 인터페이스를 사용하여 런타임에 쿼리 구현을 생성하므로, 개발자는 일반적인 CRUD 작업에 대한 별도의 코드를 작성하지 않고도 사용자 지정 쿼리나 메서드를 정의할 수 있습니다. 어떤 인터페이스를 확장할지는 애플리케이션의 특정 요구사항에 따라 결정됩니다