[Spring] JPA VS MyBatis

무1민·2023년 12월 31일
1

Spring

목록 보기
8/9
post-thumbnail

트렌드 차이

Persistence(영속성)

  • 데이터들이 프로그램이 종료되어도 사라지지 않고 어떤 곳에 저장되는 개념

  • 자바에서는 데이터의 영속성을 위한 JDBC를 지원해주는데, 매핑 작업을 개발자가 일일히 수행해야 하는 번거로움이 있다.

    • Connection, Statement, ResultSet 모두 설정해야함
  • Persistence Layer에 JDBC가 있음

Persistence Framework

  • 개발자가 직접 JDBC 프로그래밍을 하지 않도록 기능을 제공해주는 것
    • SQL Mapper
    • ORM

SQL Mapper

  • Object와 SQL의 필드를 매핑하여 데이터를 객체화하는 기술
    • 객체와 테이블 간의 관계를 매핑하는 것이 아님
    • SQL문을 직접 작성하고 쿼리 수행 결과를 어떠한 객체에 매핑할지 바인딩하는 방법
    • DBMS에 종속
    • JDBCTemplate
    • MyBatis

ORM

  • Object와 DB테이블을 매핑하여 데이터를 객체화하는 기술
    • 개발자가 반복적인 SQL을 직접 작성하지 않음
      • RDB의 데이터 그 자체와 매핑
    • DBMS에 종속적이지 않음
      • 무슨 DBMS를 쓰던간에 어차피 JPA 문법을 따름
    • 복잡한 쿼리의 경우 JPQL을 사용하거나 SQL Mapper를 혼용하여 사용
    • JPA

MyBatis

  • 자바에서 SQL Mapper를 지원해주는 프레임워크
  • SQL문을 이용해서 RDB에 접근, 데이터를 객체화시켜줌
  • SQL을 직접 작성하여 쿼리 수행 결과를 객체와 매핑
  • 쿼리문을 xml로 분리 가능
    • mapper inerface와 mapper 구현체인 xml을 1대1 매칭

  • 복잡한 쿼리문 작성 가능
  • 데이터 캐싱 기능으로 성능 향상
    • JPA, Hibernate도 있음
  • 객체와 쿼리문 모두 관리해야함. CRUD 메소드 직접 다 구현해야함
  • 장점
    • SQL 쿼리를 직접 작성하므로 최적화된 쿼리를 구현할 수 있다.
    • 엔티티에 종속받지 않고 다양한 테이블을 조합할 수 있다.
    • 복잡한 쿼리도 SQL 쿼리만 작성할 수 있다면 손쉽게 작성할 수 있다.
  • 단점
    • 스키마 변경시 SQL 쿼리를 직접 수정해주어야 한다.
    • 반복된 쿼리가 발생하여 반복 작업이 있다.
    • 런타임에 오류를 확인해야 한다.
    • 테이블 필드 변경 시 이와 관련된 모든 DAO의 SQL문, 객체의 필드 등 수정해야 한다.
    • 쿼리를 직접 작성하기 때문에 데이터베이스에 종속된 쿼리문이 발생한다.
      • 데이터베이스 변경시 로직을 함께 수정해야 한다.
      • DB에 종속적이지 않기 위함이 JDBC의 목적이었는데, 의존성이 너무 강해졌다.
    • 패러다임 불일치
      • 객체지향은 추상화, 상속, 다형성을 따르는 반면,
      • RDB테이블은 데이터 중심의 테이블 구조를 따른다.
      • 각자 지향하는 목적이 다르기 때문에 사용방법과 표현 방식에 차이가 생긴다.
        • RDB 테이블은 객체의 상속개념이 없다.
        • 객체는 참조를 사용해 다른 객체와 연관관계를 가지지만, 테이블은 외래키를 사용해서 가진다.

JPA

  • 자바 ORM의 기술 표준

  • 대표적인 오픈소스로 Hibernate

    • JPA는 단순히 명세이기 때문에 구현이 없다.

      • 그렇기 때문에 JPA만 가지고는 어떤 구현 기술을 사용할 수 없다.
    • 대부분 interface, annotation으로 이루어져 있다.

      package javax.persistence;
      
      import ... 
      
      public interface EntityManager {
           public void persist(Object entity);
           public <T> T merge(T entity);
           public void remove(Object entity);
           public <T> T find(Class<T> entityClass, Object primaryKey);
           // More interface methods...
      }
    • 그래서 JPA를 사용하기 위해서 JPA를 구현한 Hibernate같은 ORM 프레임워크를 사용해야 한다.

  • CRUD 메소드 기본 제공

  • 쿼리를 만들지 않아도 됨

  • 1차 캐싱, 변경 감지, 지연 로딩 제공

  • MyBatis는 쿼리가 수정되어 데이터 정보가 바뀌면 그에 사용되고 있던 DTO와 함께 수정해주어야 하는 반면에, JPA는 객체만 바꾸면 된다.

    • 객체 중심으로 개발 가능
  • 복잡한 쿼리는 해결이 어렵다.

  • 1차 캐시

    • EntityManager 인스턴스는 자체 1차 캐시를 가지고 있다.
      • 같은 트랜잭션 내에서 엔티티를 반복적으로 조회할 때 데이터베이스에 대한 추가 쿼리를 줄여주는 것
      • EntityManager가 엔티티를 처음 조회하면, 그 엔티티는 1차 캐시에 저장된다.
      • 이후, 같은 EntityManager 내에서 동일한 엔티티를 다시 조회하려고 할 때, JPA는 데이터베이스에 쿼리를 보내지 않고 1차 캐시에서 해당 엔티티 반환
      • 다른 EntityManager 인스턴스와 캐시 공유하지 않음
      • EntityManager 가 닫히면 1차 캐시와 같이 제거

Hibernate

  • JPA의 구현체 중 하나(EclipseLink, DataNucleus …)
  • 쓰기 지연 지원
    • JPA에 의한게 아니라 Hibernate에 의한 것
  • 2차 캐시 지원
    • EntityManager 인스턴스간에 공유 가능
    • 세션에서 엔티티를 요청할 때 Hibernate는 먼저 1차 캐시를 확인한다.
    • 1차 캐시에 데이터가 없는 경우, 2차 캐시를 확인하고, 거기에도 없으면 db조회
    • 데이터베이스에서 데이터를 가져온 후, 1차 캐시에 먼저 저장되고, 설정에 따라 2차 캐시에도 저장
    • 1차 캐시는 빈번한 데이터 접근과 변경을 위한 세션 수준에서의 최적화
    • 2차 캐시는 애플리케이션 전반의 성능 최적화
  • Hibernate는 SQL을 사용하지 않고 직관적인 코드를 사용해 데이터를 조작할 수 있다.
  • SQL을 직접 사용하지 않는다고 JDBC API를 사용하지 않는 것이 아니다.
    • Hibernate가 지원하는 메소드 내부에서 JDBC API가 동작하고 있으며, 단지 개발자가 직접 SQL을 작성하지 않을 뿐이다.

  • JPA와 Hibernate는 마치 자바의 interface와 해당 interface를 구현한 class와 같은 관계

  • JPA의 핵심인 EntityManagerFactory, EntityManager, EntityTransaction을 Hibernate에서 각각 SessionFactory, Session, Transaction으로 상속받고 각각 Impl로 구현하고 있음을 확인할 수 있다.

  • 쓰기 지연(Write-Behind)

    • 영속성 컨텍스트에 변경이 발생했을 때, 바로 데이터베이스로 쿼리를 보내지 않고 SQL쿼리를 버퍼에 모아놨다가, 영속성 컨텍스트가 flush하는 시점에 모아둔 SQL 쿼리를 데이터베이스로 보내는 기능
    • flush되는 시점
      • 직접 flush() 호출
      • jpql 실행
      • 트랜잭션 commit() 호출
  • 변경 감지(Dirty-Checking)

    • 데이터베이스에서 엔티티를 조회하면 해당 엔티티는 영속성 컨텍스트에 저장된다.
      • 이때, 영속성 컨텍스트는 엔티티의 원본 상태를 스냅샷으로 보관한다.
    • 애플리케이션에서 이 엔티티의 상태를 변경한다.
    • 애플리케이션이 트랜잭션을 커밋할 때, 영속성 컨텍스트는 모든 영속성 상태의 엔티티를 검사한다.
      • 이때, 엔티티의 현재 상태와 스냅샷을 비교한다.
    • 변경된 필드가 있는 경우, JPA는 거기에 맞는 UPDATE SQL문을 생성한다.
    • 생성된 UPDATE SQL은 쓰기 지연 SQL 저장소에서 저장되었다가 트랜잭션이 커밋되는 시점에 데이터베이스로 전송한다.
  • 지연 로딩

    • 엔티티와 관련된 다른 엔티티나 컬렉션이 실제로 사용될 때까지 로드되지 않는 기법
    • 관련 데이터의 로드를 연기함으로써 초기 로딩 시간을 단축하고, 불필요한 데이터베이스 접근을 줄인다.
    • Member조회할 때 Team도 조회되는 것을 막는 것
      @Entity
      @Getter
      public class Member extends BaseEntity {    
      	@Id    
      	@GeneratedValue(strategy = GenerationType.IDENTITY)    
      	private Long id;
      
      	// 패치 타입 LAZY 설정    
      	@ManyToOne(fetch = FetchType.LAZY)    
      	@JoinColumn(name = "team_id", insertable = false, updatable = false)    
      	private Team team;
      	public void changeTeam(Team team) {        
      		this.team = team;        
      		this.team.getMembers().add(this);    
      	}
      }
  • 장점

    • 1차 캐시, 쓰기 지연, 변경 감지, 지연로딩을 제공하여 성능상 이점을 얻을 수 있다.
    • 코드 레벨로 관리 되므로 사용하기 용이하고 생산성이 높다.
    • 컴파일 타임에 오류를 확인할 수 있다.
    • 데이터베이스에 종속적이지 않으므로 특정 쿼리를 사용하지 않아 추상적으로 기술 구현이 가능하다.
    • 엔티티로 관리되므로 스키마 변경 시 엔티티만 수정하게 되면 엔티티를 사용하는 관련 쿼리는 자동으로 변경된 내역이 반영된다.
    • 개발 초기에는 쿼리에 대한 이해가 부족해도 코드 레벨로 어느 정도 커버가 가능하다.
    • 객체 지향적으로 데이터를 관리할 수가 있다.
      • SQL과 연결되어 있는게 아니라 RDB의 데이터 자체와 연결되어 있기 때문
  • 단점

    • JPA만 사용하여 복잡한 연산을 수행하기에는 다소 무리가 있다.
      • JPQL을 사용한다.
    • 초기에는 생산성이 높을 수 있으나 사용하다 보면 성능상 이슈가 발생할 수 있다.
      • N+1, FetchType, Proxy, 연관관계
    • 고도화될수록 학습 곡선이 높아진다.
      • 성능 이슈의 해결 방안에 따라 복잡한 내부 로직을 이해해야 할 필요가 있다.

Spring Data JPA

  • Spring에서 제공하는 모듈 중 하나로 JPA를 쉽고 편하게 사용할 수 있도록 도와준다.
  • 기존의 JPA를 사용하려면 EntityManager를 주입받아 사용해야 하지만, Spring Data JPA는 JPA를 한 단계 더 추상화 시킨 Repository 인터페이스를 제공한다.
  • JPA
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class JpaExample {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("my-persistence-unit");
        EntityManager em = emf.createEntityManager();

        em.getTransaction().begin();
        
        // 엔티티 조회
        User user = em.find(User.class, userId);
        
        em.getTransaction().commit();
        em.close();
    }
}
  • Spring Data JPA
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    // CRUD 메서드 및 페이징/정렬 기능이 자동으로 제공됩니다.
}

// 서비스 또는 컨트롤러에서 사용
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User getUser(Long userId) {
        return userRepository.findById(userId).orElse(null);
    }
}

컴파일 타임 오류?

  • DB 테이블 컬럼에 name 컬럼이 없고 username 컬럼이 있다면…
  • JPA
//컴파일 오류
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
    
    private String name; // 데이터베이스에는 'username'이라는 컬럼이 있어야 한다.

    // 기타 getter 및 setter 생략
}
  • MyBatis
  • 메서드를 실행할 때 오류
//런타임 오류
public interface UserMapper {
    @Select("SELECT id, name FROM users")
    User findUserById(Long id);
}

⇒ JPA는 컴파일 시점에 오류가 발생하고 MyBatis는 해당 함수를 호출했을 때 런타임 오류가 발생함

코드 비교

  • MyBatis
public interface UserMapper {
  @Insert("INSERT INTO users (name, email) VALUES (#{name}, #{email})")
  @Options(useGeneratedKeys = true, keyProperty = "id")
  int insert(User user);
}
  • JPA
public interface UserRepository extends JpaRepository<User, Integer> {
  User save(User user);
}
profile
야호

0개의 댓글