정글에서 온 개발자

Spring 데이터 관리 라이브러리 변화와 비교 본문

정리

Spring 데이터 관리 라이브러리 변화와 비교

dev-diver 2024. 1. 5. 15:39

개요

  • JDBC(Java Database Connectivity) - 1990 중반. 자바 프로그래밍 언어의 일부
  • JdbcTemplate - 2000년 초. Spring Framework의 일부. Spring과 함께 등장
  • JPA(Java Persistence API) - 2000년대 중반. 자바 EE 표준. ORM(Object-Relational Mapping)을 위한 API 제공. Hibernate는 JPA의 구현체
  • Spring Data JPA - 2010년 초. Spring Data 프로젝트의 일부. JPA를 더 쉽게 사용 리포지토리 계층 쉽게 구현하는 추상화 제공
  • Spring Data JDBC - 2010년 후반. Spring Data JDBC. JPA의 복잡성을 줄이고자 등장. 도메인 중심 설계 촉진

Data JDBC가 최신이니까 제일 좋은가?

  • 꼭 그런건 아니다. 추상화 수준으로 보면 JPA의 추상화 수준이 더 높다.
  • JPA는 ORM 제공을 주 목적으로 하고
  • JDBC는 도메인 중심 설계 지원을 주 목적으로 한다.

코드 비교

JDBC

Interface

public interface MemberRepository {
     Member save(Member member);
     Optional<Member> findById(Long id);
     Optional<Member> findByName(String name);
     List<Member> findAll();
}

repository (본체)

  • 직접 따로 만든 interface를 상속함
  • repository에 직접 구현된 메소드들.
  • 메소드안에서 db와 연결, 쿼리 보내기, 받은 결과 처리 하는 코드를 매번 해야 함
  • 쿼리 결과를 Entity의 메소드를 직접 호출해 처리함.
public class JdbcMemberRepository implements MemberRepository {
    private final DataSource dataSource;
    public JdbcMemberRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    @Override
    public Member save(Member member) {
        String sql = "insert into member(name) values(?)";
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        try {
            conn = getConnection(); //1.connection
						//2. sql 셋팅
            pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            pstmt.setString(1, member.getName());
            pstmt.executeUpdate();
            rs = pstmt.getGeneratedKeys(); //3.result 받음
            member.setId(rs.getLong(1));
            return member;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            //연결 종료
            close(conn, pstmt, rs);
        }
    }
    @Override
    public Optional<Member> findById(Long id) {
        //구현
    }

    private Connection getConnection() {
        return DataSourceUtils.getConnection(dataSource);
    }
    private void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
       //...
        rs.close();
    }
    private void close(Connection conn) throws SQLException {
        DataSourceUtils.releaseConnection(conn, dataSource);
    }
}

Entity

  • 객체
  • Lombock쓰면 getter setter 따로 구현 안해도 됨.
public class Member {
    private Long id;
    private String name;
    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    } 

JDBC template

  • 귀찮은 연결과, 쿼리문 작성을 해결
  • 의존성 관리
implementation 'org.springframework:spring-jdbc:5.3.10' //버전 조정 필요

repository

  • 복잡했던 connect, query보내기 과정을 없앰 (Query도 없애줌)
  • =ORM의 시작.
  • 여전히 repository interface 를 직접 구현함.
  • 여전히 member의 메소드를 직접 호출해서 로직을 처리함.
public class JdbcTemplateMemberRepository implements MemberRepository {
    private final JdbcTemplate jdbcTemplate;
    public JdbcTemplateMemberRepository(DataSource dataSource) {
        jdbcTemplate = new JdbcTemplate(dataSource);
    }
    @Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", member.getName());
        Number key = jdbcInsert.executeAndReturnKey(new
                MapSqlParameterSource(parameters));
        member.setId(key.longValue());
        return member;
    }
    @Override
    public Optional<Member> findById(Long id) {
        List<Member> result = jdbcTemplate.query("select * from member where id
                = ?", memberRowMapper(), id);
        return result.stream().findAny();
    }

}
  • Interface, entity 위와 동일

JPA

  • Entity Manager의 도입
  • 의존성 관리
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

Entity

  • annotation 활용으로 id 등 자동 생성
    • @Entity는 JPA 전용 어노테이션
  • 롬복까지 적용한 코드
    • @Getter, @Setter
@Entity
@Getter
@Setter
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

Repository

  • EntityManager 를 통해 @Enitity로 등록된 Entity를 더 쉽게 관리
    • member의 getter setter를 직접 호출하지 않음
  • EntityManager 능력 밖의 코드는 query를 직접 써서 처리
public class JpaMemberRepository implements MemberRepository {
    private final EntityManager em; //매니저
    public JpaMemberRepository(EntityManager em) {
        this.em = em;
    }
    public Member save(Member member) { //일반적으로 이렇게 쉬움
        em.persist(member); //member의 getter, setter는 매니저가 알아서 호출해줌.
        return member;
    }
    public Optional<Member> findById(Long id) {
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }
    public List<Member> findAll() { //쿼리 직접 쓰는 코드
        return em.createQuery("select m from Member m", Member.class)
    }
    public Optional<Member> findByName(String name) {
        List<Member> result = em.createQuery("select m from Member m where
                m.name = :name", Member.class)
                .setParameter("name", name)
                .getResultList();
        return result.stream().findAny();
    }
}

Service

  • @Transactional 추가
@Transactional
public class MemberService {}

Spring Data JPA

  • 기본으로 쓰는 메소드는 선언조차 사치임
  • 의존성 관리
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.6.0' //버전 조정 필요

Repository

  • 현재까지 ORM 끝판왕
  • 구현체가 아니라 Interface임
  • MemberRepository 인터페이스를 직접 구현하지 않으므로 해당 interface는 이제 없어도 됨. (제약을 주기 위해 extends 할 수는 있음)
public interface SpringDataJpaMemberRepository extends JpaRepository<Member,
 Long>, MemberRepository {
     Optional<Member> findByName(String name);
 }
  • 대부분을 JpaRepository가 알아서 해줌
  • 기본 제공 메서드
    • persist
    • merge
    • remove
    • find - 주어진 기본 키로 엔티티를 찾음
  • 추가 메소드
    • findByName같은 명명 규칙에 맞게 선언만 하면, 그 기능을 JpaRepository가 알아서 작성함.
  • 위 Interface의 구현체 자체가 필요 없음. 갓갓.

Spring Data JDBC

  • 도메인 주도 설계를 위함
  • 의존성 관리
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc:2.6.0' // Spring Boot 버전에 맞게 조정

Repository

public interface BookRepository extends CrudRepository<Book, Long> {
    // 여기에 필요한 쿼리 메소드를 추가할 수 있습니다.
}

Entity

  • JPA가 아니므로 @Entity는 없어도 됨
@Getter 
@Setter
public class Member { private Long id; private String name; }
  • 특별한 클래스 타입인 record (JAVA 16부터 도입)로도 나타낼 수 있음
public record Member(@Id Long id, String name) { }
  • 자동으로 final 선언
  • getter/setter가 필요 없음. 자동 접근 가능
  • 이외 자동 생성되는 함수들
    • equals()
    • hashcode()
    • toString()
  • 위 형태는 불변 객체라서 ORM 위주인 JPA에는 적합하지 않음
  • ORM의 변경 감지(Dirty checking), 연관 관리, 지연로딩(lazy loading) 등의 기능을 쓰려면 변경 가능한 객체여야 함.
  • JDBC 는 집합 루트 중심으로 객체를 다룬다. record가 더 적합할 수 있음