Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

임도현의 성장

[Spring-Boot] 스프링 데이터 JPA 본문

Spring Boot

[Spring-Boot] 스프링 데이터 JPA

림도현 2025. 2. 4. 21:19

👻 JPA 란?

JPA(Java Persistence API)는 자바 진영에서 ORM(Object-Relational Mapping)을 통해 관계형 데이터베이스(RDBMS)와 객체(Entity)를 쉽게 다룰 수 있도록 도와주는 표준 API이다.

🍄 ORM(객체 관계 매핑) 란?

  • 객체(Entity)와 데이터베이스 테이블을 자동으로 매핑하는 기술
  • SQL을 직접 작성하지 않아도 객체를 저장, 조회, 수정, 삭제 가능

👉 Entity 란?

  • 데이터베이스의 테이블과 매핑되는 자바 객체
  • @Entity 어노테이션을 사용해 JPA가 관리하도록 설정 

😮 JPQL 란?

  • JPQL은 SQL과 비슷하지만 객체를 대상으로 하는 쿼리 언어이다.
  • JPA에서 SQL이 제공하지 않는 기능을 필요로 할 때 주로 사용한다.
  • @Query 어노테이션을 붙히고 JPA 엔티티 객체를 대상으로 쿼리를 작성 해야합니다.

🦖 JPA를 사용하는 이유?

  • SQL을 직접 작성할 필요 없이 객체 지향적으로 데이터 관리 가능
  • 변경 감지, 지연 로딩 같은 기능 지원
  •  DB 변경 시 코드 수정 최소화 (유지보수가 좋음)

🥥 JPA의 동작 

JPA는 어플리케이션과 JDBC API 사이에서 동작하는 API로 어플리케이션의 객체를 관계형 데이터베이스의 테이블에 매핑하여 JPA 프로바이더(Hibernate)가 내부적으로 JDBC를 사용해 데이터베이스와 데이터를 주고받습니다.

😎 JPA ORM 매핑

@Entity : JPA가 사용하는 객체라는 뜻이다. 이 에노테이션이 있어야 JPA가 인식할 수 있다.

@Id : 테이블의 PK와 해당 필드를 매핑한다.

@GeneratedValue(strategy = GenerationType.IDENTITY) : PK 생성 값을 데이터베이스에서 생 성하는 IDENTITY 방식을 사용한다.

@Column : 객체의 필드를 테이블의 컬럼과 매핑한다.

@OneToMany : 하나의 엔티티와 여러개의 엔티티와 1 : N 관계를 맺을 때 사용하는 JPA 어노테이션이다. 

@ManyToOne : 반대로 N : 1 관계를 맺을 때 사용하는 JPA 어노테이션이다. 

@JoinColumn : 외래 키로 사용될 컬럼을 지정하는 JPA 어노테이션이다.

@NoArgsConstructor @AllArgsConstructor
@Builder @Getter @Setter
@Entity
@Table(name = "school")
public class School {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "school_id")
    private Long id;

    @Column(name = "school_name")
    private String name;

    @OneToMany(mappedBy = "school")
    private List<Student> studentList = new ArrayList<>();
}

@NoArgsConstructor @AllArgsConstructor
@Builder @Getter @Setter
@Entity
@Table(name = "student")
public class Student {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "student_id")
    private Long id;

    @Column(name = "student_name")
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "school_id") // 외래 키 컬럼 이름
    private School school;
}

👩‍💻 Service에서 JPA호출

studentRepository.findById(id) : id를 기준으로 Student 엔티티를 데이터베이스에서 조회합니다. 만약 해당 id에 맞는 학생이 없다면 RuntimeException 을 던져 오류를 처리합니다.

반환 할 때는 조회된 Student 엔티티를 StudentDto로 변환하여 리턴합니다.

@Service
@RequiredArgsConstructor
public class StudentService {
    private final StudentRepository studentRepository;

    public StudentDto findStudent(Long id) {
        long count = studentRepository.countStudent();
        Student student = studentRepository.findById(id).orElseThrow(() -> new RuntimeException("Member not found!"));
        return new StudentDto(student);
    }
}

@ToString
@Getter
public class StudentDto {
    private Long id;
    private String name;
    private String schoolName;

    public StudentDto(Student student) {
        this.id = student.getId();
        this.name = student.getName();
        this.schoolName = student.getSchool().getName();
    }
}

🙊 JpaRepository 상속 받기 

JpaRepository<T, ID>를 상속받으면 기본적인 데이터 처리 메서드(CRUD, 페이징, 정렬 등)를 자동으로 사용할 수 있어즉, SQL 쿼리를 직접 작성하지 않아도 기본적인 DB 작업이 가능하다는 장점이 있습니다.

countStudent 인테페이스는 SQL이 제공하지 않는 기능이여서 JPQL로 작성하였습니다. @Query 어노테이션을 붙히고 JPA 엔티티 객체를 대상으로 쿼리를 작성 해야합니다.

@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {

    @Query("SELECT COUNT(m) FROM Student m")
    long countStudent();
}

💩 내 생각 정리

JPA를 사용하면 CRUD, 페이징 처리 같은 기능들도 제공을해주어 증복되는 코드도 개선해주며  객체 지향적인 접근 방식으로 데이터를 다룰 수 있어 유연한 설계가 가능해 졌다. 하지만 JPA는 공부해야 할게 많은거 같다. ORM에 대해서 확실하게 알아야 하며 엔티티 또한 잘 설정을 해주어야 데이터베이스 변경에 대한 유연한 대응이 가능하게 되어 엔티티를 최소한의 수정을 통해 유지보수가 좋아지는거 같다.