26/07/23 IL(I Learned) - JPA

Let's take a break·2026년 7월 29일

Spring Data JPA를 이용한 데이터 접근 방식 이해

이전 MyBatis 프로젝트에서 사용하던 Mapper와 XML을 이용한 데이터 접근 방식 대신 Spring Data JPA를 이용하여 데이터를 관리하였다.

이전에는 SQL을 직접 작성하고 MyBatis가 SQL과 객체를 연결해 주는 구조였다면, 이번 프로젝트에서는 JPA와 Hibernate가 SQL을 자동으로 생성하여 실행한다는 점이 가장 큰 변화였다.

프로젝트를 진행하면서 Entity를 중심으로 데이터를 관리하는 방식과 Repository 인터페이스를 활용한 CRUD 구현 방법을 새롭게 학습하였다.


Entity를 중심으로 데이터베이스를 설계

이전 프로젝트에서는 Entity가 단순히 테이블의 컬럼을 표현하는 역할을 하였다.

이번 프로젝트에서는 Entity에 다양한 JPA 어노테이션을 추가하여 데이터베이스 테이블의 구조와 제약 조건까지 함께 정의하였다.

Book Entity

@Entity
@Table(name = "books")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(
        name = "book_name",
        nullable = false,
        length = 100,
        unique = true
    )
    private String title;

    private String author;
}

가장 새롭게 배운 부분은 JPA 어노테이션이다.

@Entity

는 해당 클래스를 데이터베이스의 테이블과 연결하는 역할을 한다.

@Table(name = "books")

는 테이블 이름을 지정한다.

또한

@Id
@GeneratedValue(...)

를 사용하여 기본키를 자동으로 생성하도록 설정하였다.

이전 MyBatis에서는 SQL에서 AUTO_INCREMENT를 직접 사용하거나 Mapper를 통해 INSERT를 수행하였다.

하지만 JPA에서는 Entity에 어노테이션만 작성하면 Hibernate가 자동으로 처리해 준다는 점이 가장 큰 차이였다.


Repository 인터페이스 사용

이전 프로젝트에서는

Controller

↓

Service

↓

Mapper

↓

XML

↓

SQL

구조였다.

이번 프로젝트에서는

Controller

↓

Service

↓

Repository

↓

Hibernate

↓

Database

구조로 변경되었다.


BookRepository

public interface BookRepository
        extends JpaRepository<Book, Long> {
}

이번 프로젝트에서 가장 신기했던 부분은 Repository 안에 아무런 메서드도 작성하지 않았다는 것이다.

기존 MyBatis에서는

  • insert()
  • findAll()
  • update()
  • delete()

등의 메서드를 직접 선언하고 XML까지 작성해야 했다.

하지만

extends JpaRepository<Book, Long>

한 줄만 작성해도

  • save()
  • findAll()
  • findById()
  • deleteById()

등의 CRUD 기능을 바로 사용할 수 있었다.

즉, CRUD를 직접 구현하지 않아도 Spring Data JPA가 기본 기능을 제공한다는 점을 새롭게 배웠다.


save() 하나로 등록과 수정 처리

기존 MyBatis에서는

insert()

↓

update()

를 각각 구현하였다.

하지만 JPA에서는

bookRepository.save(book);

하나만 사용한다.

Service를 보면

public void addBook(Book book) {
    bookRepository.save(book);
}

등록에도

public void updateBook(Book book) {
    bookRepository.save(book);
}

수정에도 동일한 메서드를 사용한다.

JPA는 객체의 id 값을 확인하여

  • id가 없으면 INSERT
  • id가 존재하면 UPDATE

를 자동으로 수행한다.

이전 프로젝트에서는 등록과 수정 SQL을 각각 작성해야 했지만, JPA에서는 save() 하나로 두 기능을 처리할 수 있다는 점이 인상적이었다.


Optional을 이용한 조회

조회 방식도 이전 프로젝트와 달라졌다.

return bookRepository.findById(id)
        .orElseThrow();

MyBatis에서는 조회 결과가 없으면 null을 반환하는 경우가 많았다.

하지만 JPA의

findById()

는 Optional을 반환한다.

따라서

.orElseThrow()

를 이용하여 데이터가 존재하지 않으면 예외를 발생시키도록 구현하였다.

이를 통해 null을 직접 검사하지 않고도 안전하게 데이터를 처리하는 방법을 배울 수 있었다.


DTO를 이용한 Entity 변환

입력 데이터는 이전 프로젝트처럼 DTO를 이용하였다.

public record BookFormDTO(
        String title,
        String author
) {

    public Book toEntity() {
        return Book.builder()
                .title(title)
                .author(author)
                .build();
    }
}

사용자가 입력한 데이터를 바로 Entity로 사용하는 것이 아니라 DTO를 통해 Entity로 변환하였다.

이 구조는 이전 프로젝트에서도 사용하였지만, JPA에서도 동일한 방식으로 DTO를 활용할 수 있다는 점을 확인하였다.


Controller의 변화

Controller는 크게 달라지지 않았다.

@PostMapping
public String addBook(
        @ModelAttribute BookFormDTO dto) {

    bookService.addBook(dto.toEntity());

    return "redirect:/";
}

Controller는 사용자의 요청을 받아 DTO를 생성하고 Service를 호출하는 역할만 수행한다.

즉,

Controller

↓

DTO

↓

Service

↓

Repository

흐름은 유지되었지만,

데이터 접근 계층이 Mapper에서 Repository로 변경되었다는 점이 가장 큰 차이였다.


새롭게 배운 점

MyBatis 프로젝트JPA 프로젝트
Mapper + XMLRepository + JpaRepository
SQL 직접 작성SQL 자동 생성(Hibernate)
insert(), update() 분리save() 하나로 처리
Mapper 구현JpaRepository 상속
SQL 중심 개발Entity 중심 개발
findById() → 객체findById() → Optional

느낀 점

이번 프로젝트를 통해 가장 크게 느낀 점은 JPA는 SQL을 작성하는 기술이 아니라 객체를 중심으로 데이터를 관리하는 기술이라는 것이다. 이전에는 SQL을 직접 작성하고 MyBatis가 이를 실행하도록 구성했지만, JPA에서는 @Entity, @Id, @Column과 같은 어노테이션으로 객체를 정의하면 Hibernate가 적절한 SQL을 생성해 준다는 점이 매우 인상적이었다.

또한 JpaRepository를 상속하는 것만으로 기본 CRUD 기능을 사용할 수 있었고, save() 하나로 등록과 수정을 처리하거나 findById()Optional을 반환하는 구조를 경험하면서, SQL 중심의 개발 방식에서 객체 중심의 개발 방식으로 사고를 전환하는 계기가 되었다.

0개의 댓글