[Spring] JDBC에서 JPA, Spring Data JPA까지

라임이·2023년 12월 27일
0

Spring

목록 보기
1/3
post-thumbnail

백엔드 개발을 접해본 사람은 대부분 ORM이라는 용어를 들어본 적이 있을 것이다.
Spring Data JPA, Django ORM, TypeORM 등 다양한 ORM(Persistence) Framework가 존재하며, 최근 개발되는 대부분의 프로젝트에서 해당 기술을 사용한다.
그렇다면 이 모든 것의 중심에 있는 ORM은 도대체 무엇일까?

ORM(Object-Relational Mapping, 객체-관계 매핑)

ORM은 객체와 관계형 데이터베이스 간의 데이터 변환과 상호 작용을 도와주는 기술이다.
조금 더 쉽게 말하자면, 기존의 SQL Mapper 방식, 즉 쿼리문을 직접 생성하고 실행하는 방식에서 벗어나, 객체에 바인딩하여 자동으로 쿼리문을 작성하고 실행하는 방식이다.

오늘은 ORM 중에 Java 진영에서 주로 쓰이는 Spring Data JPA에 대해 다루어보고자 한다.
Spring Data JPA를 제대로 이해하기 위해서는 탄생하기까지의 과정을 들여다볼 필요가 있다.

JDBC(Java Database Connectivity)

Java 진영에서 처음부터 ORM 기술을 제공했던 건 아니다.
처음에는 위에서 말했듯 SQL Mapper로 시작했고, 그게 JDBC라는 기술이다.
JDBC는 자바 애플리케이션에서 데이터베이스에 접근하고 관리하기 위한 API이다.
MySQL, PostgreSQL, OracleDB 등 다양한 데이터베이스와 상호 작용할 수 있는 표준 방식을 제공한다.

다음은 JDBC를 통해 테이블 정보를 조회하는 예제 코드이다.

Member Table

FieldTypeDescription
idINT고유 ID, 기본 키
usernameVARCHAR(50)사용자 이름
emailVARCHAR(100)이메일 주소
created_atDATE계정 생성 날짜
updated_atDATE계정 정보 최종 업데이트 날짜

Member Table Data

idusernameemailcreated_atupdated_at
1user1user1@example.com2021-01-012021-06-01
2user2user2@example.com2021-02-012021-07-01
3user3user3@example.com2021-03-012021-08-01

JDBC를 이용하여 Member Table Data를 조회

import java.sql.*;

public class RetrieveMemberData {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/yourdatabase"; // 데이터베이스 URL
        String user = "yourusername"; // 데이터베이스 사용자명
        String password = "yourpassword"; // 데이터베이스 비밀번호

        try {
            // 1. 드라이버 로드
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 2. 연결 생성
            Connection con = DriverManager.getConnection(url, user, password);

            // 3. 쿼리 실행
            String query = "SELECT * FROM member";
            Statement stmt = con.createStatement();
            ResultSet rs = stmt.executeQuery(query);

            // 4. 결과 처리
            System.out.println("id\tusername\temail\t\t\tcreated_at\tupdated_at");
            while (rs.next()) {
                int id = rs.getInt("id");
                String username = rs.getString("username");
                String email = rs.getString("email");
                Date created_at = rs.getDate("created_at");
                Date updated_at = rs.getDate("updated_at");
                
                System.out.println(id + "\t" + username + "\t" + email + "\t" + created_at + "\t" + updated_at);
            }

            // 5. 자원 정리
            rs.close();
            stmt.close();
            con.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

결과

id	username	email			created_at	updated_at
1	user1	user1@example.com	2021-01-01	2021-06-01
2	user2	user2@example.com	2021-02-01	2021-07-01
3	user3	user3@example.com	2021-03-01	2021-08-01

위 코드를 보면 프로젝트에 사용되는 데이터베이스에 연결하고 직접 쿼리문을 작성한 뒤, 결과를 받아와 매핑하여 사용하는 걸 볼 수 있다.
SQL에 능숙한 사람의 경우 프로젝트의 상황에 맞게 적절한 최적화를 하고 복잡한 쿼리문을 작성할 수 있다는 장점이 있다.
반면, 개발자가 직접 SQL 쿼리문을 작성해야 되기에 러닝 커브가 존재하고, 오류가 발생했을 때 발견하기 어렵다는 점 또한 알아야 된다.
외에도 프로젝트 규모가 커질수록 작성된 수많은 SQL 쿼리문을 추적하고 관리하기 어렵다는 점과 데이터베이스의 스키마가 변경되었을 경우 관련된 모든 쿼리문을 찾아 수정해야된다는 치명적인 단점이 존재한다.

JPA(Java Persistence API)

JPA는 자바 애플리케이션에서 관계형 데이터베이스를 사용하는 방식을 표준화한 ORM 기술이다.
이전에 말한 JDBC가 갖는 단점을 보완하고자 개발되었으며, 데이터베이스 작업을 객체지향적으로 수행할 수 있게 해준다.
JPA의 가장 큰 특징은 테이블을 객체(Object)로, 행을 인스턴스(Instance)로 매핑해서 데이터베이스를 하나의 자바 객체처럼 조작할 수 있게 해준다는 점이다.
JPA는 데이터베이스를 조작하는 표준 인터페이스를 제공하고, 사용자가 표준 인터페이스에 따라 관계형 데이터베이스의 조작을 구현한다.
현재 Hibernate, EclipseLink 등 JPA 표준 인터페이스를 따라 구현된 ORM이 존재하고, 그 중 Hibernate를 가장 많이 사용한다.

해당 기술은 기존 JDBC의 많은 부분을 자동/간략화했다. 반복적으로 행해지는 CRUD 코드를 줄이고 트랜잭션 관리를 용이하게 하여 생산성을 대폭 향상시켰다.

다음은 위의 JDBC 코드를 JPA로 구현한 코드이다.
구현체로는 Hibernate를 사용했다.

DB 연결정보는 META-INF/persistence.xml에 정의되며, Hibernate와 같은 구현체에서 관계형 DB의 종류에 적합하게 쿼리문을 작성하여 주기 때문에 신경쓰지 않아도 된다.

META-INF/persistence.xml

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence
             http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
             version="2.2">

    <persistence-unit name="your-persistence-unit">
        <!-- JPA 제공자에 대한 설정 -->
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>

        <!-- 데이터베이스 연결 설정 -->
        <properties>
            <!-- JDBC 드라이버 -->
            <property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
            <!-- 데이터베이스 URL -->
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/yourdatabase"/>
            <!-- 데이터베이스 사용자명 -->
            <property name="javax.persistence.jdbc.user" value="yourusername"/>
            <!-- 데이터베이스 비밀번호 -->
            <property name="javax.persistence.jdbc.password" value="yourpassword"/>
            
            <!-- 추가적인 설정 (Dialect, DDL 생성 옵션 등) -->
            <!-- ... -->
        </properties>
    </persistence-unit>
</persistence>

Member Entity 정의

import javax.persistence.*;

@Entity
@Table(name = "member")
public class Member {
    @Id
    private int id;
    private String username;
    private String email;
    @Column(name = "created_at")
    private Date createdAt;
    @Column(name = "updated_at")
    private Date updatedAt;

    // Getters and setters
    // ...
}

JPA를 이용하여 Member Table Data를 조회

import javax.persistence.*;
import java.util.List;

public class RetrieveMemberDataJPA {
    public static void main(String[] args) {
        // 1. EntityManagerFactory 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("your-persistence-unit");
        EntityManager em = emf.createEntityManager();

        try {
            // 2. 트랜잭션 시작
            em.getTransaction().begin();

            // 3. 쿼리 실행 (JPQL 사용)
            String jpql = "SELECT m FROM Member m";
            TypedQuery<Member> query = em.createQuery(jpql, Member.class);
            List<Member> members = query.getResultList();

            // 4. 결과 처리
            System.out.println("id\tusername\temail\t\t\tcreated_at\tupdated_at");
            for (Member member : members) {
                System.out.println(member.getId() + "\t" + member.getUsername() + "\t" + member.getEmail() + "\t" +
                        member.getCreatedAt() + "\t" + member.getUpdatedAt());
            }

            // 5. 트랜잭션 커밋
            em.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            if (em.getTransaction().isActive()) {
                em.getTransaction().rollback();
            }
        } finally {
            // 6. 자원 정리
            em.close();
            emf.close();
        }
    }
}

결과

id	username	email			created_at	updated_at
1	user1	user1@example.com	2021-01-01	2021-06-01
2	user2	user2@example.com	2021-02-01	2021-07-01
3	user3	user3@example.com	2021-03-01	2021-08-01

ORM도 JPQL을 통한 쿼리를 지원하기 때문에 여전히 쿼리문이 남아있기는 하지만, 이전보다 코드가 간결해졌음을 알 수 있다.
또한 데이터베이스와 객체를 통해 통신하기에 쿼리에 대한 응답을 쉽게 컨트롤하며, 코드의 EntityManager는 1차 캐시와 스냅샷 등을 이용하여 CRUD 성능을 개선한다.

Member member = em.find(Member.class, memberId);
System.out.println(member);

EntityManager의 find() 메서드를 이용하여 별도의 쿼리문 작성 없이 쉽게 데이터를 가져올 수도 있다.

Spring Data JPA

Spring Data JPA는 JPA를 기반으로 한층 더 높은 수준의 추상화와 간편한 데이터 액세스 레이어를 제공한다.
Spring Project의 일부로 Spring Framework에서 주로 사용되며, JPA의 반복적인 코드와 설정을 자동화하고 메서드 이름만으로 쿼리를 생성할 수 있게 했다.

Spring Data JPA의 주요 특징

Repository 추상화

Spring Data JPA가 갖는 가상 핵심적인 기능이다.
CrudRepository<T, ID>, JpaRepository<T, ID> 등 다양한 인터페이스를 제공한다.

Query Method 생성 및 자동 구현

Spring Data JPA에서 정해놓은 명명 규칙을 따라 메서드명을 작성한다면, 자동으로 쿼리문이 생성되고 해당 메서드를 이용해 별도의 구현 없이 데이터베이스와 통신이 가능하다.

@Query Annotation 및 JPQL, SQL 지원

@Query 어노테이션을 이용하여 메서드 위에 정의한다면, JPA에서와 마찬가지로 JPQL, SQL을 작성하여 메서드를 구현/사용할 수 있다.

QueryDSL 지원

Spring Data JPA만으로 구현하기 힘든 복잡한 쿼리를 QueryDSL을 지원함으로써 가능하게 한다.

페이징 및 정렬

메서드의 반환형과 매개변수를 적절하게 설정한다면 Pageable과 Sort 인터페이스를 사용하여 페이징, 정렬을 쉽게 구현할 수 있다.

다음은 위의 JPA 코드를 Spring Data JPA로 구현한 코드이다.
구현체로는 Hibernate를 사용했다.

Member Entity 정의(동일)

import javax.persistence.*;

@Entity
@Table(name = "member")
public class Member {
    @Id
    private int id;
    private String username;
    private String email;
    @Column(name = "created_at")
    private Date createdAt;
    @Column(name = "updated_at")
    private Date updatedAt;

    // Getters and setters
    // ...
}

MemberRepository 인터페이스 생성

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MemberRepository extends JpaRepository<Member, Integer> {
    // 필요한 추가적인 쿼리 메서드를 정의할 수 있음.
}

Spring Data JPA를 이용하여 Member Table Data를 조회

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.List;

@SpringBootApplication
public class RetrieveMemberDataSpringDataJpaApplication implements CommandLineRunner {

    @Autowired
    private MemberRepository memberRepository;

    public static void main(String[] args) {
        SpringApplication.run(RetrieveMemberDataSpringDataJpaApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        // 모든 Member 조회
        List<Member> members = memberRepository.findAll();

        // 결과 출력
        System.out.println("id\tusername\temail\t\t\tcreated_at\tupdated_at");
        for (Member member : members) {
            System.out.println(member.getId() + "\t" + member.getUsername() + "\t" + member.getEmail() + "\t" +
                    member.getCreatedAt() + "\t" + member.getUpdatedAt());
        }
    }
}

JpaRepository 인터페이스를 이용하여 Member Repository를 생성하였고, 그 결과 자동 생성된 기본적인 CRUD 메서드를 얻을 수 있었다.
기존의 JPA와 마찬가지로 객체로 데이터베이스와 통신하며 findAll() 메서드 하나만으로 데이터베이스 Member 테이블의 모든 정보를 받아왔다.

외에도 findById(), findByEmail() 등 pk와 column명 등을 이용하여 간편하게 조회 가능하며,
findAllByOrderByCreatedAtAsc()와 같이 메서드명을 설정하면 생성일을 기준으로 오름차순으로 모든 멤버를 조회할 수도 있다.

profile
백엔드 개발이 즐거운 4학년 컴공생

0개의 댓글

관련 채용 정보