본문 바로가기
개발/스프링

[Spring] 스프링의 DB 접근 기술 간단 소개

by 카펀 2022. 3. 16.

개요

2003년에 스프링이 최초로 공개된 이래로, 스프링은 DB에 접근하는 방법을 꾸준히 발전시켜 왔습니다.

객체지향 관점에서 개발하는 스프링과, 관계를 중심으로 데이터를 저장하는 RDBMS는 접근 방식부터 차이가 있기 때문에, 이 둘을 함께 이용하여 개발하기란 쉽지 않습니다.

 

이 글에서는 Java와 스프링의 DB 접근 기술이 어떻게 발전되어 왔는지 순서대로 나열하여 소개해 보고자 합니다.

(iBatis/MyBatis는 이 글에서는 다루지 않았습니다.)

 

  1. 순수 JDBC
  2. Spring JdbcTemplate
  3. JPA
  4. 스프링 데이터 JPA

1. 순수 JDBC

JDBC는 Java Database Connectivity의 약자로, Java 환경에서 DB에 접근하기 위한 표준 API입니다.

JDBC는 Spring 프레임워크와는 독립적입니다. 즉, 스프링 프레임워크의 사용 여부는 JDBC와는 상관이 없습니다.

오늘날 실무 개발에서 사용되는 일은 거의 없으며, 약 20년 전에 흔히 사용되던 방법입니다.

작성해야 할 코드가 많고, 쿼리도 직접 작성해야 합니다.

 

특정 정보를 DB에 저장하는 코드의 예시입니다.

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();
        pstmt = conn.prepareStatement(sql,
                Statement.RETURN_GENERATED_KEYS);

        pstmt.setString(1, member.getName());

        pstmt.executeUpdate();
        rs = pstmt.getGeneratedKeys();

        if (rs.next()) {
            member.setId(rs.getLong(1));
        } else {
            throw new SQLException("id 조회 실패");
        }
        return member;
    } catch (Exception e) {
        throw new IllegalStateException(e);
    } finally {
            close(conn, pstmt, rs);
    }
}

 

딱 봐도 코드가 엄청 길어 보이지 않나요?

Java 상에서 전달받은 Member를 DB에 저장하기 위해, insert 쿼리 한 줄을 실행시키려면 위와 같이 작성해야 합니다.

연결을 설정하고, 이로부터 preparedStatement라는 값을 정한 다음, 쿼리를 수행하게 됩니다.

exception을 매우 많이 던지게 되므로, 예외 처리를 위한 복잡한 try-catch 문이 쓰였습니다.

그리고 마지막으로 연결을 종료해 줍니다.

 

그냥 이런 게 있었구나 하는 식으로만 알아두시면 됩니다.

2. Spring JdbcTemplate

스프링 JdbcTemplate란, Spring 환경에서 JDBC를 훨씬 빠르고 편리하게 사용하도록 

설정 자체는 순수 JDBC와 동일하게 하면 됩니다.

JDBC API 상의 많은 반복 코드를 제거해 주지만, JdbcTemplate 역시 쿼리는 대부분 직접 작성해야 합니다.

select문을 사용하는 경우 쿼리를 직접 작성하지만, 아래와 같이 저장하는 경우에는 따로 작성하지 않아도 되는 경우도 있는데요.

private final JdbcTemplate jdbcTemplate;

@Autowired
public JdbcTemplateMemberRepository(DataSource dataSource) {
    this.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;
}

단순 코드 길이가 훨씬 줄어든 것을 볼 수 있습니다.

연결을 설정하고, 접근하고, 연결을 종료하는 등 다양한 코드가 사라졌습니다.

member라는 테이블명, id라는 primary key, 넣는 값 name이 있ㅇ니 SimpleJdbcInsert가 알아서 insert문을 생성해서 넣어 줍니다.

 

JdbcTemplate는 실무에서도 많이 쓰므로, 내용을 잘 공부해 두어야 합니다.

 

3. JPA

JPA는 Java Persistence API의 약자입니다. 앞서 JDBC와 마찬가지로, 스프링이 아닌 Java에서 제공하는 API입니다.

앞서 소개한 방법들과는 다르게, JPA는 쿼리를 직접 작성할 필요를 없애 줍니다.

상황에 맞게 쿼리를 직접 생성하여 DB에 실행시켜 줍니다.

 

JPA는 Java의 표준 인터페이스입니다. 따라서 이를 구현하는 것은 각 단체/업체들의 몫인데요.

대표적인 구현체로 Hibernate (하이버네이트)가 있습니다.

각 구현체는 성능/편리함 등의 차이가 있고요.

 

따라서 쿼리를 직접 작성하지 않아도 되기 때문에 개발 생산성을 크게 높일 수 있습니다. 게다가 객체지향적 설계에만 집중하기 때문에, 이것 역시 개발 생산성 향상에 기여하고요.

다만 그렇다고 해서 쿼리를 잘 몰라도 되냐면, 그건 아닙니다. 오히려 쿼리를 잘 이해하고 능숙하게 작성할 수 있어야 JPA를 제대로 사용할 수 있습니다.

본인이 작성한 코드가 생성한 쿼리를 컴퓨터는 제대로 이해하는데, 본인이 제대로 이해하지 못한다면 효율적인 개발을 할 수 없겠죠?

 

흔히 사용되는 MyBatis, JdbcTemplate에 비해 확실히 학습 난이도가 있지만 (러닝 커브가 높다고도 합니다), 그만큼 배울 만한 가치가 있는 기술이고 현업에서 점차 많이 사용되고 있기도 합니다.

 

대표적인 책으로는 김영한 님의 '자바 ORM 표준 JPA 프로그래밍' 이라는 책이 있습니다. 사실 위 책이 2015년에 출간되면서 한국 국내에서 JPA가 본격적으로 사용되기 시작했다고 볼 수 있습니다.

저 역시 공부하기 위해 구매를 하였고, 스프링 기초 개념을 확실히 잡고 나서 제대로 공부해 볼 계획입니다.

 

앞서 작성한 save를 JPA를 사용할 때 구현한 모습입니다.

private final EntityManager em;

public JpaMemberRepository(EntityManager em) {
    this.em = em;
}

@Override
public Member save(Member member) {
    em.persist(member);
    return member;
}

 

다만 쿼리를 완전히 작성하지 않아도 되는 것은 아닙니다.

SQL은 아니지만 섬세한 쿼리 작성을 위해 JPQL (Java Persistence Query Language)을 작성해야 하는 경우도 있는데요.

예를 들어서 PK가 아닌 것으로 찾는 경우 (findByName)에는 JPQL을 작성하여 사용합니다.

@Override
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();
}

JPQL을 작성하여 findByName 메소드를 작성한 모습입니다.

4. 스프링 데이터 JPA

스프링 데이터 JPA는 기존의 JPA를 스프링에서 사용하기 편하도록 한 번 감싸서 제공하는 기술입니다.

가장 큰 장점은 앞서 JPA의 경우와는 달리, JPQL을 작성하지 않아도 된다는 점입니다!

 

뿐만 아니라, 앞서 소개한 세 경우는 모두 Repository에 구현 클라스를 작성하였는데요.

스프링 데이터 JPA를 사용하면 구현 클라스 없이 인터페이스만으로 개발을 마칠 수 있습니다.

개발자가 비즈니스 로직에만 집중할 수 있도록, 잡다하고 반복적인 코드가 전부 제거된 것입니다.

 

스프링 데이터 JPA는 기존의 JPA를 더욱 편리하게 사용할 수 있도록 도와주는 기술이기 때문에, 당연하지만 JPA에 대한 이해도가 필수입니다.

public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {

    @Override
    Optional<Member> findByName(String name);
}

구현체는 이게 전부입니다.

앞서 소개한 코드는 전부 class 내에 있는 특정 메소드만 보여 드렸는데, 스프링 데이터 JPA를 사용하면 기본적인 CRUD 메소드를 구현하지 않아도 됩니다.

관련 내용은 JpaRepository - PagingAndSortingRepository - CrudRepository의 구현체에 들어가 보면 확인할 수 있습니다.

findByName은 늘 공용으로 사용되는 내용이 아니므로, JpaRepository 내에는 정의되어 있지 않습니다.

그래서 interface 내에 따로 적어 줘야 합니다.

 

이렇게 스프링 데이터 JPA를 사용해도 동적 쿼리와 같은 문제는 해결하기 어려운 경우가 있는데요.

이런 경우에는 QueryDSL이라는 기술을 쓰기도 하고, 네이티브 쿼리를 작성하는 경우도 있습니다.

댓글