티스토리 뷰

Spring

[Spring] JPA

PeonyF 2023. 5. 2. 23:43
반응형

JPA란?

Java persistence API는 자바의 ORM 기술의 표준이다

 

ORM란?

Object-Relational Mapping

ORM(Object-Relational Mapping) 프레임워크는 객체 지향 프로그래밍 언어와 관계형 데이터베이스 간의 불일치를 해결하기 위한 기술입니다.

즉, 객체와 데이터베이스 간에 매핑 작업을 자동으로 수행해줌으로써, 객체 지향 프로그래밍에서 사용되는 객체와 관계형 데이터베이스의 데이터 모델 사이의 불일치를 해결해줍니다.

ORM 프레임워크를 사용하면, 객체를 데이터베이스에 저장할 때 객체를 SQL 쿼리로 변환하는 작업을 개발자가 직접 수행할 필요가 없습니다. 대신 ORM 프레임워크가 객체와 데이터베이스 간의 매핑을 자동으로 수행합니다. 이를 통해 개발자는 객체 지향적인 코드를 작성하면서도 데이터베이스와의 연동을 간편하게 할 수 있습니다.

ORM 프레임워크는 객체와 테이블 간의 매핑을 자동으로 수행하기 때문에, 객체 지향적인 개발을 할 수 있습니다. 또한, ORM 프레임워크는 객체 간의 관계를 자동으로 매핑해주기 때문에, 객체 지향적인 코드를 작성하는 데 도움을 줍니다.

이러한 방식으로 ORM 프레임워크는 개발자가 객체와 데이터베이스 간의 변환 작업을 수동으로 수행하는 것을 대신해주며, 개발자는 이를 위한 코드를 작성할 필요가 없어집니다. 이를 통해 개발자는 데이터베이스와 객체를 분리된 형태로 개발할 수 있으며, 유지보수성과 개발 생산성을 높일 수 있습니다.

@Entity
@Table(name = "user")
public class User {

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

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

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

  // getters and setters
}

 

JPA를 사용하는 이유는?

자바는 JDBC API를 사용해 SQL을 데이터 베이스에 전달하기 때문에 중요하지만 SQL 중심적인 개발을 하게 되면 많은 문제가 발생한다.

  • 성능 이슈

SQL 쿼리를 직접 작성하면서 발생할 수 있는 성능 이슈들이 있습니다. 예를 들어, SQL의 JOIN 연산은 매우 비싸기 때문에 잘못된 JOIN 연산을 사용하면 성능 문제가 발생할 수 있습니다.

 

  • 보안 문제

SQL 쿼리를 직접 작성하는 경우 SQL Injection 공격에 취약해질 수 있습니다. 이는 사용자 입력값을 직접 쿼리에 삽입하는 것으로 인해 발생할 수 있습니다. 이러한 공격을 방어하기 위해서는 입력값 검증 및 인자화 등의 방법을 사용해야 합니다.

 

  • 유지보수성 문제

SQL 쿼리를 직접 작성하면서 발생하는 유지보수성 문제들이 있습니다. SQL 쿼리를 많이 사용하는 경우, 쿼리의 변경이나 수정이 필요할 때마다 코드의 일부를 변경해야 하는 등 유지보수가 어려워질 수 있습니다.

 

  • 객체 지향적인 개발 방식과의 불일치:

자바는 객체 지향 프로그래밍 언어이지만, SQL은 관계형 데이터베이스를 위한 언어입니다. SQL 중심적인 개발 방식은 객체 지향적인 개발 방식과 다르기 때문에, 이러한 불일치가 발생할 수 있습니다.

따라서, SQL 중심적인 개발보다는 ORM(Object-Relational Mapping) 프레임워크를 사용하여 객체 지향적인 개발을 하고, SQL 쿼리를 자동으로 생성하는 방법을 사용하는 것이 좋습니다. 이를 통해 성능 문제와 보안 문제 등의 위험을 줄이고, 유지보수성과 객체 지향적인 개발 방식을 보장할 수 있습니다.

JPA 사용 이유

  • 생산성

JPA는 객체와 데이터베이스 간의 매핑을 자동으로 처리해주기 때문에, 개발자는 데이터베이스와 관련된 지루하고 반복적인 코드 작성을 줄일 수 있습니다. 객체를 자바 컬렉션에 저장하듯이 JPA에게 저장할 객체를 전달하면 JPA가 알아서 데이터베이스에 저장하고 필요한 SQL 쿼리를 자동으로 생성해주기 때문입니다.

 

  • 유지보수

필드를 추가하거나 수정하는 등의 작업을 수행할 때, JPA는 자동으로 SQL 쿼리를 생성하고 JDBC 코드를 작성해줍니다. 따라서 개발자는 이러한 작업을 수동으로 수행할 필요가 없으며, 이를 통해 유지보수성이 향상됩니다.

 

  • 패러다임의 불일치 해결

연관된 객체를 사용할 때 SQL을 전달하고, 같은 트랜잭션 내에서 조회할 때는 동일성을 보장함으로써, 다양한 패러다임의 불일치 문제를 해결합니다.

객체 지향 프로그래밍에서는 객체들이 서로 연관되어 있습니다. 예를 들어, 학교 시스템에서 학생은 여러 개의 과목을 수강할 수 있고, 각 과목은 여러 명의 학생을 받을 수 있습니다. 이러한 연관성은 객체 간의 참조로 표현됩니다.

반면, 관계형 데이터베이스에서는 이러한 관계를 표현하기 위해서는 외래키를 사용해야 합니다. 즉, 학생과 과목의 관계를 표현하기 위해서는 학생 테이블과 과목 테이블 사이에 외래키가 필요합니다. 이러한 차이로 인해 객체 지향 프로그래밍과 관계형 데이터베이스 간의 패러다임의 불일치가 발생합니다.

JPA는 이러한 패러다임의 불일치 문제를 해결하기 위해 연관된 객체를 사용할 때 SQL을 전달하고, 같은 트랜잭션 내에서 조회할 때는 동일성을 보장합니다. 예를 들어, 학생과 과목 간의 관계를 표현하고 있는 객체를 조회할 때, JPA는 학생 객체와 과목 객체를 연결하는 SQL 쿼리를 생성하여 데이터베이스에서 조회한 후, 학생 객체와 과목 객체를 연결하여 반환합니다.

또한, 같은 트랜잭션 내에서 조회할 때는 동일성을 보장합니다. 즉, 같은 엔티티를 조회하면 항상 동일한 객체를 반환합니다. 이는 데이터베이스와의 통신 횟수를 최소화하고, 객체 간의 참조를 유지하면서도 데이터베이스와의 일관성을 보장하기 위한 방법입니다.

 

  • 성능

애플리케이션과 데이터베이스 사이에서 성능 최적화 기회를 제공한다. 같은 트랜잭션안에서는 같은 엔티티를 반환하기 때문에 데이터 베이스와의 통신 횟수를 줄일 수 있다. JPA에서는 같은 엔티티를 여러 번 조회할 경우, 첫 번째 조회 시에는 데이터베이스에서 조회하고, 이후 조회 시에는 메모리에 캐시된 데이터를 사용합니다. 이를 통해 데이터베이스와의 통신 횟수를 최소화하고 성능을 향상시킬 수 있습니다.

또한 트랜잭션을 commit하기 전까지 메모리에 쌓고 한번에 SQL을 전송한다.

 

JPA동작과정

JPA는 애플리케이션과 JDBC 사이에서 동작한다. JPA 내부에서 JDBC API를 사용하여 SQL을 호출하여 DB와 통신한다.

개발자가 ORM 프레임워크에 저장하면 적절한 INSERT SQL을 생성해 데이터베이스에 저장해주고, 검색을 하면 적절한 SELECT SQL을 생성해 결과를 객체에 매핑하고 전달해 준다.

 

영속성이란?

데이터를 생성한 프로그램이 종료되더라도 데이터가 영구적으로 저장되어 있는 상태를 말합니다. JPA에서 영속성은 영속성 컨텍스트(Persistence Context)를 통해 관리되며, 이를 통해 객체의 생명주기를 관리합니다.

 

영속성 컨텍스트

영속성 컨텍스트는 JPA에서 엔티티를 관리하는 논리적인 개념입니다. 엔티티를 생성하거나 조회하면 영속성 컨텍스트에 저장됩니다. 이후에 같은 엔티티를 다시 조회할 때, 영속성 컨텍스트에서 바로 조회할 수 있습니다. 즉, 영속성 컨텍스트는 엔티티를 캐시하는 역할을 합니다.

영속성 컨텍스트는 엔티티를 수정하면, 자동으로 SQL 쿼리를 생성하고 데이터베이스에 반영합니다. 이를 통해 개발자는 SQL 쿼리를 직접 작성하지 않아도 되며, 데이터베이스와의 통신 횟수를 최소화할 수 있습니다.

또한, 영속성 컨텍스트는 트랜잭션 범위에서 동작합니다. 즉, 트랜잭션이 커밋될 때, 영속성 컨텍스트가 관리하는 모든 엔티티의 상태 변경 내용이 데이터베이스에 저장됩니다. 이를 통해 데이터 일관성을 유지할 수 있습니다.

 

JPA Propagation 전파단계

REQUIRED

기존에 트랜잭션이 존재하면 참여하고, 없으면 새로 생성합니다.

SUPPORTS

기존에 트랜잭션이 존재하면 참여하고, 없으면 트랜잭션 없이 진행합니다.

MANDATORY

기존에 트랜잭션이 반드시 존재해야 합니다. 없으면 예외가 발생합니다.

REQUIRES_NEW

항상 새로운 트랜잭션을 생성하며, 기존 트랜잭션은 일시 중단됩니다.

*부모 트랙잭션에서 예외가 발생하더라도 자식 트랜잭션은 롤백되지 않는다.

그러나 자식 트랜잭션에서 예외가 발생한다면 부모 트랜잭션까지 롤백이 전파된다.

NOT_SUPPORTED

트랜잭션이 존재하는 경우 트랜잭션을 잠시 보류시키고, 트랜잭션이 없는 상태로 처리를 수행

NEVER

트랜잭션이 없어야 진행되며, 있으면 예외가 발생합니다.

NESTED

기존 트랜잭션이 존재하면 중첩 트랜잭션을 시작하고, 없으면 새로운 트랜잭션을 생성합니다. 중첩 트랜잭션은 부모 트랜잭션이 롤백되어도 독립적으로 커밋될 수 있으며, 반대로 부모 트랜잭션은 중첩 트랜잭션을 롤백시킬 수 있습니다.

 

N+1 문제(재공부 필요)

N + 1 문제는 연관관계가 설정된 엔티티 사이에서 한 엔티티를 조회하였을 때,
조회된 엔티티의 개수(N 개)만큼 연관된 엔티티를 조회하기 위해 추가적인 쿼리가 발생하는 문제를 의미합니다.

=> 엔티티 조회 쿼리(1 번) + 조회된 엔티티의 개수(N 개)만큼 연관된 엔티티를 조회하기 위한 추가 쿼리 (N 번)

해결방법

@XToOne의 경우 : fetch join을 통해 해결 (혹은 @EntityGraph)

public interface PostRepository extends JpaRepository<Post, Long> {

    @Override
    @Query("select p from Post p join fetch p.comments")
    List<Post> findAll();
}
public interface CommentRepository extends JpaRepository<Comment, Long> {

    //@Query("select c from Comment c join fetch c.post")
    @Override
    @EntityGraph(attributePaths = {"post"})
    List<Comment> findAll();
}

 

 

@XToMany의 경우 : BatchSize를 사용하여 해결 (혹은 @Fetch(FetchMode.SUBSELECT))

@Entity
public class Post {

    @BatchSize(size = 100)
    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Comment> comments = new ArrayList<>();
}
@Entity
public class Post {

    @Fetch(FetchMode.SUBSELECT)
    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Comment> comments = new ArrayList<>();
}

 

출처      

https://ttl-blog.tistory.com/1135

https://ttl-blog.tistory.com/243

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/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
글 보관함