Spring Data JPA란?
- Spring Data JPA는 Spring에서 JPA(Java Persistence API)를 쉽게 사용할 수 있도록 도와주는 라이브러리이다.
- SQL을 직접 작성하지 않아도 객체(Entity)와 데이터베이스를 자동으로 매핑하고, 데이터를 저장/조회/수정/삭제하는 CRUD 기능을 간단하게 구현할 수 있도록 해줌

장점
1️⃣ SQL을 직접 작성하지 않아도 된다
- @Query 없이도 자동으로 쿼리를 생성해 줌
- findById(id), save(entity), delete(entity) 같은 메서드만 호출하면 됨
2️⃣ JPA의 기능을 편하게 사용할 수 있다
- 엔티티(Entity) 기반으로 데이터베이스 테이블과 매핑
- OneToMany, ManyToOne 같은 관계 설정 가능
- 자동으로 트랜잭션을 관리해 줌
3️⃣ 페이징 & 정렬 기능이 내장되어 있다
- findAll(Pageable pageable)을 사용하면 페이징 및 정렬이 가능
기본 사용법
1️⃣ 의존성 추가 (Spring Boot 기준)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId> <!-- 예제용 H2 DB -->
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
2️⃣ Entity 클래스 생성 (DB 테이블 매핑)
import jakarta.persistence.*;
@Entity //이 클래스가 DB테이블과 연결됨
@Table(name = "users")
public class User {
@Id //기본키(PK)
@GeneratedValue(strategy = GenerationType.IDENTITY) //자동증가값 설정
private Long id;
@Column(nullable = false) //null값 허용X
private String name;
private int age;
// 기본 생성자 (필수)
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
// Getter & Setter
public Long getId() { return id; }
public String getName() { return name; }
public int getAge() { return age; }
}
3️⃣ JPA Repository 인터페이스 생성
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface UserRepository extends JpaRepository<User, Long> { //<User엔티티관리, PK타입 Long>
List<User> findByName(String name);
// 추가적인 조회 메서드 정의 가능
}
4️⃣ Service 클래스에서 데이터 처리
import org.springframework.stereotype.Service;
import java.util.List;
@Service //Spring이 자동으로 Bean을 생성
public class UserService {
private final UserRepository userRepository; //private fianl = userRepository 변하지 않도록 안정성 유지
public UserService(UserRepository userRepository) { //UserRepository 생성자 주입
this.userRepository = userRepository;
}
//UserService가 UserRepository를 주입받았기 때문에 DB에서 데이터 저장/조회 기능을 수행 가능
public User saveUser(String name, int age) {
return userRepository.save(new User(name, age)); // User 저장
}
public List<User> getUsersByName(String name) {
return userRepository.findByName(name); // 이름으로 검색
}
}
※ Spring Bean 자동 생성
UserRepository는 @Repository 어노테이션이 있어서 Spring이 자동으로 Bean을 생성
UserService는 @Service 어노테이션이 있어서 Spring이 UserRepository를 자동으로 주입(Injection)
👉 개발자가 new 키워드로 객체를 직접 생성할 필요 없이 Spring이 관리해 줌!
※ 스프링 의존성 주입(Dependency Injection, DI)
필드 주입, 생성자 주입, Setter 주입이 있지만 Spring 공식 문서에서 생성자 주입 권장!
생성자 주입 장점>
- 의존성이 명확하게 드러남 : UserService가 UserRepository를 꼭 필요로 한다는 게 확실함.
- 불변성(Immutable) 보장 : 한 번 주입된 userRepository를 변경할 수 없음 (final 사용)
- Spring이 알아서 주입해 줌 (스프링 빈 자동 주입) : @Service와 @Repository를 사용하면 Spring이 자동으로 주입함
5️⃣ Controller에서 API로 활용
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController //JSON데이터를 주고받는 api 생성
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/create")
public User createUser(@RequestParam String name, @RequestParam int age) {
return userService.saveUser(name, age);
}
@GetMapping("/search")
public List<User> searchUsers(@RequestParam String name) {
return userService.getUsersByName(name);
}
}
📌 GET vs POST 정리
| 비교 | GET 요청 | POST 요청 |
| 사용 목적 | 조회(Read) | 저장(Create), 변경(Update), 삭제(Delete) |
| 데이터 전달 방식 | URL 쿼리 파라미터 (?key=value) | HTTP Body에 데이터 포함 |
| 보안 | 약함 (URL에 데이터 노출) | 강함 (데이터가 숨겨짐) |
| 데이터 크기 | 제한 있음 (URL 길이 제한) | 제한 없음 |
| 캐싱 가능 여부 | 가능 | 불가능 |
추가 기능
1️⃣ 페이징 & 정렬 기능
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
Page<User> findAll(Pageable pageable); // 페이징 처리
List<User> findAll(Sort sort); // 정렬 처리
}
- Repository는 인터페이스로 선언만 하고, 데이터 조회는 Service에서 수행!
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Page<User> getUsersWithPagination() {
return userRepository.findAll(PageRequest.of(0, 10)); //첫 페이지에서 10개 조회
}
public List<User> getSortedUsers() {
return userRepository.findAll(Sort.by(Sort.Direction.DESC, "age")); //나이 내림차순 정렬
}
}
2️⃣ JPQL 사용 (복잡한 쿼리 작성)
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.age > :age")
//@Query → 직접 JPQL(Query Language) 작성 가능
//:age → 파라미터를 매핑하여 사용(바인딩을 사용하여 SQL Injection 방지)
List<User> findUsersOlderThan(@Param("age") int age);
}
@Query
- JPQL 또는 Native SQL을 직접 작성할 수 있도록 해주는 어노테이션
- 기본적인 findBy 메서드 외에도 복잡한 쿼리를 직접 작성(JPA에서는 findBy메서드만 사용가능)
ex. JOIN, GROUP BY, ORDER BY 등의 복잡한 SQL이 필요하면 @Query 필수
JPQL은 SQL과 비슷하지만 엔티티 객체를 대상으로 쿼리를 작성하는 언어이다.
👉 테이블이 아니라 엔티티 클래스 기준으로 작성하는 게 특징
📌 JPQL과 Native SQL의 차이점 정리
| 비교 | JPQL (JPA Query) | Native SQL (Raw SQL) |
| 쿼리 대상 | 엔티티 객체 (User) | DB 테이블 (users) |
| SQL 변환 여부 | JPA가 SQL로 변환해서 실행 | 변환 없이 그대로 실행 |
| DB 종속성 | DB에 독립적 (변환 가능) | 특정 DB에 종속 |
| 자동 매핑 | 엔티티 기준으로 데이터 매핑 | 결과를 직접 매핑해야 할 수도 있음 |
| 사용 예시 | SELECT u FROM User u | SELECT * FROM users |
| 장점 | 유지보수 쉬움, DB 변경에 유연함 | 복잡한 SQL 최적화 가능, 성능 튜닝 가능 |
| 단점 | 복잡한 SQL 작성 어려움, 일부 기능 지원 X | 유지보수 어려움, DB 종속적 |
JPA가 SQL로 변환해 실행하면 좋은 점
1️⃣ 데이터베이스 변경에 유연함
만약 데이터베이스가 MySQL에서 PostgreSQL로 바뀐다면?
- 일반 SQL을 사용하면 모든 쿼리를 다시 작성해야 함.
- 하지만 JPA는 DB 변경에 맞게 자동 변환되므로 변경할 필요 없음
📌 MySQL과 Oracle의 LIMIT 문법이 다름
-- MySQL (LIMIT 사용)
SELECT * FROM users WHERE age > 25 LIMIT 10;
-- Oracle (ROWNUM 사용)
SELECT * FROM users WHERE age > 25 AND ROWNUM <= 10;
2️⃣ 유지보수 & 코드 가독성 향상
- SQL을 직접 작성하지 않아도 됨 → 코드가 더 직관적
- 엔티티(Entity) 중심으로 로직을 작성 → 객체지향적으로 관리 가능
- 비즈니스 로직을 더 쉽게 표현 가능
// SQL 직접 작성 (가독성 떨어짐)
@Query(value = "SELECT * FROM users WHERE age > :age", nativeQuery = true)
List<User> findUsersOlderThan(@Param("age") int age);
// JPQL 사용 (더 직관적)
@Query("SELECT u FROM User u WHERE u.age > :age")
List<User> findUsersOlderThan(@Param("age") int age);
3️⃣ SQL Injection 방지
- JPQL은 파라미터 바인딩(@Param 사용) 을 통해 SQL Injection을 방지
📌 SQL Injection 예제 (위험한 코드)
@Query("SELECT u FROM User u WHERE u.name = '" + name + "'")
List<User> findUsersByName(String name);
📌 바인딩을 이용한 안전한 JPQL
@Query("SELECT u FROM User u WHERE u.name = :name")
List<User> findUsersByName(@Param("name") String name); //@Param을 사용하면 JPA가 자동으로 SQL Injection을 방지'개발인생 > Backend' 카테고리의 다른 글
| Spring vs Spring Boot (2) | 2025.03.19 |
|---|---|
| [Spring] @Transactional (0) | 2025.03.19 |
| GraphQL 입문 가이드: REST API 대체할 만할까? (0) | 2025.03.07 |
| [Spring] Spring Boot 개발자를 위한 필수 디자인 패턴 6가지 (0) | 2025.02.25 |
| [Spring] MVC 구조 설명 | Controller, Service, Repository로 역할 분리하기 (0) | 2025.02.25 |