[Springboot] JPA 완전 정리 — 엔티티 매핑·영속성 컨텍스트·Repository 쿼리 메서드
목차
JPA / ORM 개념
**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를 사용하면 데이터베이스와의 상호작용을 더 추상화하고, 객체 지향적인 코드 작성을 강화할 수 있습니다.
Spring Data JPA는 이 JPA를 기반으로 하는 데이터 액세스를 지원하는 Spring 프레임워크의 일부입니다. JPA Repository는 데이터베이스와 상호 작용하기 위한 메서드를 정의하는 인터페이스로, 이를 통해 개발자는 데이터베이스 조작을 위한 쿼리를 명시적으로 작성하지 않고도 데이터를 조회, 삽입, 갱신, 삭제할 수 있습니다.
ddl-auto 옵션
spring.jpa.hibernate.ddl-auto 속성은 Spring Data JPA와 Hibernate를 사용할 때, 데이터베이스 테이블의 스키마를 자동으로 생성 또는 업데이트할 때 사용되는 설정입니다. 이 속성은 application.properties 또는 application.yml 파일에 설정됩니다.
| 값 | 동작 |
|---|---|
create | 테이블을 새로 생성합니다. 이미 존재하는 테이블은 삭제됩니다. (데이터가 모두 삭제되므로 주의) |
update | 테이블이 이미 존재하면 변경사항만 업데이트하고, 없으면 새로 생성합니다. |
validate | 엔티티 클래스와 테이블의 스키마가 일치하는지만 검증합니다. 테이블이 없으면 에러가 발생합니다. |
create-drop | 테이블을 생성한 후, 애플리케이션 종료 시에 테이블을 삭제합니다. 주로 테스트 환경에서 사용됩니다. |
none | 자동으로 스키마를 생성하지 않습니다. 설정된대로 사용합니다. |
spring.jpa.hibernate.ddl-auto = create
spring.jpa.hibernate.ddl-auto = update
spring.jpa.hibernate.ddl-auto = validate
spring.jpa.hibernate.ddl-auto = create-drop
spring.jpa.hibernate.ddl-auto = none
엔티티 매핑
JPA 관련 어노테이션
| 어노테이션 | 설명 |
|---|---|
@Entity | 클래스가 JPA 엔터티임을 나타냅니다. 이 어노테이션이 붙은 클래스는 데이터베이스의 테이블과 매핑됩니다. |
@Table | 엔터티 클래스와 매핑될 데이터베이스 테이블의 정보를 지정합니다. 특정 테이블명, 스키마명, 인덱스 등을 설정할 수 있습니다. |
@Id | 엔터티 클래스의 기본 키(PK) 필드를 나타냅니다. |
@GeneratedValue | 기본 키의 값을 자동으로 생성하는 방법을 지정합니다. 주로 자동 증가(AUTO_INCREMENT) 등의 전략을 선택할 수 있습니다. |
@Column | 엔터티 클래스의 필드와 데이터베이스 테이블의 컬럼 간의 매핑 정보를 지정합니다. 컬럼명, 길이, null 여부 등을 설정할 수 있습니다. |
@OneToMany, @ManyToOne | 일대다 및 다대일 관계를 매핑할 때 사용됩니다. |
@ManyToMany | 다대다 관계를 매핑할 때 사용됩니다. |
@JoinColumn | 조인 컬럼의 설정을 지정합니다. 주로 외래 키(Foreign Key)를 지정할 때 사용됩니다. |
@NamedQuery | 이름을 가진 JPQL 쿼리를 정의하고, 엔터티 클래스에서 이를 참조할 때 사용됩니다. |
@NamedQueries | 여러 개의 @NamedQuery를 묶어서 정의할 때 사용됩니다. |
@Transient | 특정 필드를 데이터베이스에 매핑하지 않도록 지정합니다. 즉, 이 필드는 영속성 컨텍스트에 저장되지 않습니다. |
@Temporal | 날짜와 시간 타입을 매핑할 때 사용됩니다. DATE, TIME, TIMESTAMP 등을 선택할 수 있습니다. |
@GeneratedValue 전략
@GeneratedValue는 JPA 엔터티 클래스의 기본 키를 자동으로 생성하는 방법을 지정하는 데 사용됩니다. 이 어노테이션을 사용할 때 strategy 속성을 이용하여 어떤 방법으로 자동 생성할지를 설정할 수 있으며, 이 값은 GenerationType 열거형입니다.
| 전략 | 설명 |
|---|---|
AUTO (default) | JPA 구현체(Hibernate, EclipseLink 등)가 데이터베이스 종류에 따라 생성 방식을 자동 결정 |
IDENTITY | DB의 자동 증가 기능으로 키 생성. 주로 MySQL/MariaDB/PostgreSQL 등에서 사용(예: auto_increment) |
SEQUENCE | DB의 sequence를 이용해 키 생성. 주로 Oracle/PostgreSQL에서 사용. @SequenceGenerator와 함께 사용 |
TABLE | 키 생성 전용 테이블을 별도 생성해 사용. 모든 DB에서 사용 가능. @TableGenerator와 함께 사용 |
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 — 게터(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을 사용하면 반복적인 코드 작성을 줄여주어 코드의 양을 줄이고, 개발자가 핵심 로직에 더 집중할 수 있도록 도와줍니다.
영속성 컨텍스트 (Persistence Context)
JPA를 사용할 때 반드시 이해해야 하는 개념입니다.
- DB가 있고 Java 애플리케이션이 있을 때, 그 사이에 **컨텍스트(Context)**라는 울타리가 존재합니다.
- 앱마다 자신이 사용하는 공간이 있고, 이를 애플리케이션 컨텍스트라고 합니다.
- MyBatis는 매핑 후 바로 결과를 가져다 주지만, JPA는 메모리(컨텍스트) 상의 엔티티와 DB의 데이터를 동일하게 유지합니다.

JPA가 사용하는 메모리 공간에 100L이 있는지 먼저 확인하고, 없으면 DB에 SELECT를 날립니다. MyBatis에서는 만들어진 객체를 그냥 가져다 쓰는 것과 대조됩니다.
Repository 인터페이스 계층
Spring Data JPA는 리포지토리 인터페이스를 사용하여 Spring 애플리케이션에서 데이터베이스와 상호 작용하는 편리하고 강력한 방법을 제공합니다. 이 인터페이스를 사용하면 실제 구현 코드를 작성하지 않고도 일반적인 데이터베이스 작업을 수행할 수 있습니다. Spring Data JPA는 리포지토리 인터페이스에 정의된 메서드 시그니처를 기반으로 런타임에 필요한 코드를 생성합니다. 인터페이스만 작성하면 동적 프록시(Proxy) 방식으로 객체가 자동 생성됩니다.
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> {
// 추가적인 사용자 정의 쿼리를 여기에 추가할 수 있습니다.
}
JpaRepository<엔티티타입, ID타입> 형태로 선언하면 됩니다. 앞에 @Entity 대상 클래스, 뒤에 @Id 타입을 지정한다고 생각하면 쉽습니다.

package org.zerock.ex2.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.zerock.ex2.entity.Memo;
public interface MemoRepository extends JpaRepository<Memo, Long> {}
이 인터페이스 선언 하나로 기본 CRUD가 모두 완성된다. 이것이 Spring Boot + JPA의 강점이다.
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 작업에 대한 별도의 코드를 작성하지 않고도 사용자 지정 쿼리나 메서드를 정의할 수 있습니다. 어떤 인터페이스를 확장할지는 애플리케이션의 특정 요구사항에 따라 결정됩니다.
JpaRepository 기본 CRUD 메서드
Spring Data JPA의 JpaRepository 인터페이스는 다양한 CRUD (Create, Read, Update, Delete) 작업을 지원하는 여러 메서드를 기본적으로 제공합니다. 테스트 코드를 통해 살펴보면 다음과 같이 매핑됩니다.
| 작업 | 메서드 |
|---|---|
| INSERT | save(엔티티 객체) |
| SELECT | findById(키 타입), getOne(키 타입) |
| UPDATE | save(엔티티 객체) |
| DELETE | deleteById(키 타입), delete(엔티티 객체) |
MemoRepositoryTests 클래스
package org.zerock.ex2.repository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTestpublic class MemoRepositoryTests {
@Autowired
MemoRepository memoRepository;
@Test
public void testClass(){
System.out.println(memoRepository.getClass().getName());
}
}
실제 클래스의 이름과 패키지 이름이 출력됩니다. 우리가 직접 만들지 않은 동적으로 생성된 이름은 프록시(Proxy) 형태의 이너클래스로 생성됩니다.
save()
save() 메서드는 엔터티를 저장하거나 갱신하는데 사용되며, 다음과 같은 두 가지 시나리오에서 사용됩니다:
1. 새로운 엔터티 저장 — 새로운 엔터티를 생성하고, 이를 데이터베이스에 저장할 때 사용합니다. 엔터티에는 기본 키(auto-generated ID)가 없을 수 있습니다. save() 메서드는 이를 자동으로 생성하고 엔터티를 영구 저장소에 저장합니다.
// Person 엔터티를 저장하는 예
Person person = new Person();
person.setFirstName("John");
person.setLastName("Doe");
personRepository.save(person);
2. 기존 엔터티 갱신 — 이미 데이터베이스에 저장된 엔터티의 값을 변경하고, 이를 데이터베이스에 갱신할 때 사용합니다. 엔터티의 ID가 존재하는 경우, 해당 ID를 기반으로 데이터베이스에서 엔터티를 찾아 업데이트합니다.
// 이미 저장된 Person 엔터티를 갱신하는 예
Person existingPerson = personRepository.findById(1L).orElse(null);
if (existingPerson != null) {
existingPerson.setFirstName("UpdatedFirstName");
existingPerson.setLastName("UpdatedLastName");
personRepository.save(existingPerson);
}
save() 메서드는 영속성 컨텍스트를 통해 엔터티를 추적하고, 상황에 따라 INSERT 또는 UPDATE 쿼리를 실행합니다. 필요에 따라 ID가 이미 존재하는지 여부에 따라 동적으로 동작합니다.
Spring Data JPA는 여러 다양한 save() 메서드의 오버로딩을 제공하며, 다양한 유형의 저장 작업을 지원합니다. 예를 들어, 여러 엔터티를 동시에 저장하거나, 특정 조건을 기반으로 저장 여부를 결정하는 등의 작업을 수행할 수 있습니다.
findById()
findById() 메서드는 데이터베이스에서 특정 ID에 해당하는 엔터티를 조회하는데 사용됩니다. 주로 단일 엔터티를 검색할 때 활용됩니다.
메서드 시그니처는 다음과 같습니다:
Optional<T> findById(ID id);
- T: 엔터티의 타입을 나타냅니다.
- ID: 엔터티의 ID의 타입을 나타냅니다.
- Optional< T >: 조회된 엔터티를 감싼 Optional 객체를 반환합니다. 이는 조회 결과가 null인 경우에 대비한 방법으로 사용됩니다.
예를 들어, Person이라는 엔터티가 있다고 가정하고, 해당 엔터티의 ID를 사용하여 데이터베이스에서 조회하는 코드는 다음과 같습니다:
import java.util.Optional;
public class PersonService {
private final PersonRepository personRepository;
public PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
public void exampleFindById(Long personId) {
// findById 메서드를 사용하여 ID에 해당하는 엔터티를 조회
Optional<Person> personOptional = personRepository.findById(personId);
// 조회 결과를 확인하고 작업 수행
if (personOptional.isPresent()) {
Person person = personOptional.get();
System.out.println("Found person: " + person);
} else {
System.out.println("Person not found with ID: " + personId);
}
}
}
위 예제에서 findById() 메서드는 주어진 ID에 해당하는 Person 엔터티를 Optional로 감싸서 반환합니다. Optional은 Java 8 이후에서 null을 방지하고자 하는 목적으로 도입된 클래스입니다. 만약 조회 결과가 존재하지 않는 경우, Optional은 빈 상태를 나타내게 됩니다.
saveAll()
saveAll() 메서드는 여러 개의 엔터티를 한 번에 데이터베이스에 저장하거나 갱신하는 데 사용됩니다. 보통 컬렉션을 인자로 받아 해당 컬렉션에 포함된 여러 엔터티를 일괄적으로 저장합니다.
메서드 시그니처는 다음과 같습니다:
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
여기서:
- T: 엔터티의 타입을 나타냅니다.
- S: T의 하위 타입이거나 같은 타입을 나타냅니다.
- Iterable< S > (파라미터): 여러 개의 엔터티를 담고 있는 Iterable입니다.
- Iterable< S > (반환): 저장된 엔터티들을 나타내는 Iterable을 반환합니다.
예를 들어, Person 엔터티를 저장하는데 사용할 수 있는 saveAll() 메서드의 예제는 다음과 같습니다:
import java.util.Arrays;
public class PersonService {
private final PersonRepository personRepository;
public PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
public void exampleSaveAll() {
// 여러 Person 엔터티를 생성
Person person1 = new Person("John", "Doe");
Person person2 = new Person("Jane", "Doe");
Person person3 = new Person("Alice", "Smith");
// 엔터티들을 리스트에 담아서 saveAll() 메서드로 일괄 저장
Iterable<Person> savedPeople = personRepository.saveAll(Arrays.asList(person1, person2, person3));
// 저장된 엔터티들에 대한 추가 작업 수행
savedPeople.forEach(savedPerson -> System.out.println("Saved person: " + savedPerson));
}
}
위의 예제에서 saveAll() 메서드는 Person 엔터티를 담은 리스트를 인자로 받아 일괄적으로 저장하고, 저장된 엔터티들을 반환합니다.
findAll()
findAll() 메서드는 데이터베이스에 저장된 모든 엔터티를 조회하여 반환하는 데 사용됩니다. 메서드 시그니처는 다음과 같습니다:
Iterable<T> findAll();
- T: 엔터티의 타입을 나타냅니다.
- Iterable< T >: 조회된 모든 엔터티를 나타내는 Iterable을 반환합니다.
예를 들어, Person 엔터티를 조회하는데 사용할 수 있는 findAll() 메서드의 예제는 다음과 같습니다:
import java.util.List;
public class PersonService {
private final PersonRepository personRepository;
public PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
public void exampleFindAll() {
// findAll() 메서드를 사용하여 모든 Person 엔터티를 조회
Iterable<Person> allPeople = personRepository.findAll();
// 조회된 엔터티들에 대한 추가 작업 수행
for (Person person : allPeople) {
System.out.println("Found person: " + person);
}
}
}
findAll() 메서드는 데이터베이스에 저장된 모든 엔터티를 조회하여 Iterable로 반환합니다. 이후에는 해당 Iterable을 순회하거나 다른 방식으로 엔터티들에 대한 작업을 수행할 수 있습니다. 만약 엔터티가 많을 경우, findAll()을 사용할 때 성능상의 고려가 필요할 수 있습니다.
deleteById(), delete(), deleteAll()
JpaRepository 인터페이스의 삭제 관련 메서드들로, 엔터티를 데이터베이스에서 삭제하는데 사용됩니다.
deleteById() — Delete a Single Entity from the Database: 주어진 ID에 해당하는 엔터티를 데이터베이스에서 삭제합니다.
void deleteById(ID id);
여기서 ID는 엔터티의 ID 타입을 나타냅니다.
// 예: ID가 1인 Person 엔터티를 삭제
personRepository.deleteById(1L);
delete() — Delete an Entity From the Database Table: 주어진 엔터티를 데이터베이스에서 삭제합니다.
void delete(T entity);
여기서 T는 엔터티의 타입을 나타냅니다.
// 예: Person 엔터티를 삭제
Person person = personRepository.findById(1L).orElse(null);
if (person != null) {
personRepository.delete(person);
}
deleteAll() — Delete All the Entities From the Database Table: 데이터베이스에 저장된 모든 엔터티를 삭제합니다.
void deleteAll();
해당 메서드는 모든 엔터티를 삭제하므로 주의하여 사용해야 합니다.
// 예: 모든 Person 엔터티를 삭제
personRepository.deleteAll();
이러한 메서드들을 사용하여 엔터티를 삭제하면 데이터베이스에서 해당 엔터티들이 삭제됩니다. 삭제 작업은 주로 엔터티의 일부를 정리하거나 더 이상 필요하지 않은 데이터를 정리하는 데 사용됩니다.
count()
count() 메서드는 데이터베이스 테이블에 저장된 레코드의 총 개수를 조회하는 데 사용됩니다.
메서드 시그니처는 다음과 같습니다:
long count();
여기서 long은 데이터베이스 테이블에 저장된 레코드의 총 개수를 나타냅니다.
예를 들어, Person 엔터티를 사용하는 경우 count() 메서드는 데이터베이스에 저장된 모든 Person 레코드의 개수를 반환합니다:
public class PersonService {
private final PersonRepository personRepository;
public PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
public void exampleCount() {
// count() 메서드를 사용하여 Person 엔터티의 총 개수 조회
long personCount = personRepository.count();
System.out.println("Total number of persons: " + personCount);
}
}
이 메서드는 특정 조건에 맞는 레코드의 개수를 세는 것이 아니라, 테이블에 저장된 전체 레코드의 총 개수를 조회합니다. 이를 통해 특정 테이블에 저장된 데이터의 크기를 파악할 수 있습니다.
existsById()
existsById() 메서드는 주어진 ID에 해당하는 엔터티가 데이터베이스에 존재하는지 여부를 확인하는 데 사용됩니다.
메서드 시그니처는 다음과 같습니다:
boolean existsById(ID id);
여기서:
- ID: 엔터티의 ID 타입을 나타냅니다.
- boolean: 주어진 ID에 해당하는 엔터티가 존재하면 true를, 그렇지 않으면 false를 반환합니다.
예를 들어, Person 엔터티를 사용하는 경우 existsById() 메서드는 데이터베이스에 해당 ID를 가진 Person 엔터티가 존재하는지 여부를 확인합니다:
public class PersonService {
private final PersonRepository personRepository;
public PersonService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
public void exampleExistsById(Long personId) {
// existsById() 메서드를 사용하여 주어진 ID에 해당하는 Person 엔터티의 존재 여부 확인
boolean personExists = personRepository.existsById(personId);
if (personExists) {
System.out.println("Person with ID " + personId + " exists in the database.");
} else {
System.out.println("Person with ID " + personId + " does not exist in the database.");
}
}
}
이 메서드를 사용하면 엔터티의 존재 여부를 확인하여 추가적인 작업을 수행할 수 있습니다.
쿼리 메서드와 @Query
JPA Repository에서 사용할 수 있는 메서드들은 다양하며, 일반적으로 다음과 같은 패턴을 따릅니다:
1. 쿼리 메서드(Query Methods) — 메서드 이름으로 쿼리를 생성할 수 있습니다. 메서드 이름 자체가 질의(query)문이 되며, findBy..., getBy...로 메서드 이름을 시작하고 컬럼명과 키워드를 연결해 작성합니다.
// 예: 이름을 기반으로 엔터티 찾기
List<Person> findByFirstName(String firstName);
2. DSL(Domain Specific Language) — Spring Data JPA는 QueryDSL과 함께 사용될 수 있습니다. 이를 통해 유연하고 타입 안전한 쿼리를 작성할 수 있습니다.
// QueryDSL을 사용한 예
List<Person> findByFirstNameAndLastName(String firstName, String lastName);
3. @Query 어노테이션 사용 — JPQL(Java Persistence Query Language)이나 네이티브 쿼리를 직접 작성하여 메서드에 적용할 수 있습니다.
// JPQL을 사용한 예
@Query("SELECT p FROM Person p WHERE p.firstName = ?1 AND p.lastName = ?2")
List<Person> findPeopleByFirstNameAndLastName(String firstName, String lastName);
4. 파라미터 정의 — 메서드에 파라미터를 추가하여 동적 쿼리를 작성할 수 있습니다.
List<Person> findByFirstNameAndAge(String firstName, int age);
5. 정렬과 페이징 — 정렬과 페이징을 지원하는 메서드를 작성할 수 있습니다.
// 정렬된 결과를 가져오는 예
List<Person> findByLastNameOrderByFirstNameAsc(String lastName);
// 페이징 및 정렬 예
Page<Person> findByLastName(String lastName, Pageable pageable);
쿼리 메서드 리턴 타입
| 리턴 타입 | 사용 조건 |
|---|---|
List<E> 또는 배열 | 일반 select 작업 |
Page<E> | 파라미터에 Pageable을 넣는 경우 필수 |
- 쿼리 메서드는 정렬 조건 없이
Pageable파라미터로 처리하는 경우가 많습니다.
@Query 어노테이션
쿼리 메서드보다 직관적으로 JPQL을 작성할 수 있습니다.
| 특징 | 내용 |
|---|---|
| 유연한 조건 | 다양한 조건이나 필요한 컬럼만 추출 가능 |
| 순수 SQL | 필요 시 native SQL 그대로 사용 가능 |
| DML 처리 | 쿼리 메서드가 select 중심인 반면, DML 처리에 더 수월 |
@Query 파라미터 바인딩 방식
| 방식 | 예시 |
|---|---|
| 순서 기반 | ?1, ?2 (1부터 시작) |
| 이름 기반 | :파라미터이름 |
| 자바 빈 스타일 | :#{...} (실무에서는 QueryDSL을 더 많이 씀) |
Object[] 리턴
필요한 컬럼(엔티티 속성)만 지정해서 추출하는 기능입니다. 주로 JPQL 함수 사용 시에 유용합니다.
페이징 / 정렬 처리
Spring Data JPA는 페이지 처리와 정렬을 API 수준에서 지원합니다. 별도의 count 쿼리도 자동으로 실행됩니다.
Pageable인터페이스 →PageRequest클래스로 생성PageRequest.of(페이지번호, 개수)— 페이지 번호는 0부터 시작
MemoRepositoryTests 클래스의 일부
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
…생략
@Test
public void testPageDefault() {
//1페이지 10개
Pageable pageable = PageRequest.of(0,10); //페이지 (0,10)
Page<Memo> result = memoRepository.findAll(pageable);
System.out.println(result);
}