Java

[Java] Persistence Framework : SQL Mapper VS ORM

cloud-grace 2024. 5. 25. 17:39

영속성(Persistence)이란?

영속성(Persistence)은 프로그램이 종료되거나 재시작 되더라도 데이터가 지속적으로 유지되는 특성을 말한다. 쉽게 말해, 데이터를 영구 저장소인 파일 시스템, 데이터베이스 등에 저장하는 것이다.

JDBC VS Persistence Framework

JDBC (Java Database Connectivity)

JDBC는 Java Database Connectivity로 Java에서 데이터베이스와 연결하여 데이터 저장 및 수정, 저장된 데이터를 사용할 수 있게 해주는 Java API이다. SQL 쿼리를 직접 작성하고 실행할 수 있는 기능을 제공한다.

JDBC 예제 코드

import java.sql.*;

public class SelectExample {
    public static void main(String[] args) {
        search("pat");
    }
    public static void search(String keyword) {
        Connection connection = null;
        Statement stmt = null;
        ResultSet rs = null;
        try {
            // 1. JDBC Driver 로딩
            Class.forName("org.mariadb.jdbc.Driver");

            // 2. 연결하기
            String url = "jdbc:mariadb://localhost:3306/employees?charset=utf8";
            connection = DriverManager.getConnection(url, "아이디", "비밀번호");

            // 3. Statement 생성하기
            stmt = connection.createStatement();

            // 4. SQL 실행
            String sql =
                            "select emp_no, first_name, last_name" +
                            " from employees" +
                            " where first_name like '%" + keyword + "%'";
            rs = stmt.executeQuery(sql);

            // 5. 결과 처리
            while (rs.next()) {
                Long empNo = rs.getLong(1);
                String firstName = rs.getString(2);
                String lastName = rs.getString(3);
                System.out.println(empNo + " : " + firstName + " : " + lastName);
            }
        } catch (ClassNotFoundException e) {
            System.out.println("드라이버 로딩 실패: " + e);
        } catch (SQLException e) {
            System.out.println("error: " + e);
        } finally {
            try {
                if (stmt!= null) {
                    stmt.close();
                }
                if (rs!= null) {
                    rs.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

JDBC 장단점

  • 장점 : SQL을 직접 작성하여 세밀하게 제어가 가능하다.
  • 단점 : 반복적인 코드 작성과 하드 코딩으로 인해 변경 시 많은 수정이 필요하다. 위 코드만 봐도 예외 처리해야 할 부분이 많다.

영속성 프레임워크(Persistence Framework)

영속성 프레임워크(Persistence Framework)는 데이터베이스와 상호 작용을 추상화하여 개발자가 더 쉽게 데이터를 저장하고 검색할 수 있게 해주는 도구이다. JDBC와 같이 복잡하고 번거로운 작업이 필요 없다. 즉, 자동화된 소프트웨어이며, DB와 맵핑하는 방식에 따라 SQL Mapper과 ORM(Object-Relational Mapping)로 나뉜다.

Persistence Framework (1) SQL Mapper

  • SQL Mapper는 SQL 쿼리를 Java 객체와 맵핑하여 관계형 데이터베이스 작업을 단순화하는 프레임워크이다.
  • 대표적으로 MyBatis가 있다.
  • SQL 쿼리를 XML 파일에 작성하여 독립적인 파일에 작성하여 가독성이 좋고 JDBC에서 제공하는 대부분의 기능을 수행할 수 있다.
  • 하지만, 객체 모델링보다 데이터 중심 모델링에 더욱 신경을 쓰는 구조이므로 객체지향 측면에서는 좋지 않다.
  • 따라서 이를 보완하기 위해 ORM이 등장하였다.

MyBatis 예제 코드

mybatis-config.xml (MyBatis 설정 파일)

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="org.h2.Driver"/>
                <property name="url" value="jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"/>
                <property name="username" value="sa"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

 

UserMapper.xml

<mapper namespace="com.example.mapper.UserMapper">
    <select id="getUserById" parameterType="int" resultType="com.example.model.User">
        SELECT * FROM users WHERE id = #{id}
    </select>
</mapper>

 

MyBatisExample.java

package com.example;

import com.example.mapper.UserMapper;
import com.example.model.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;

public class MyBatisExample {
    public static void main(String[] args) {
        try {
            // MyBatis 설정 파일 읽기
            Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
            
            // SqlSessionFactory 생성
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
            
            // SqlSession 생성
            SqlSession session = sqlSessionFactory.openSession();
            
            // UserMapper 인터페이스 구현체 생성
            UserMapper mapper = session.getMapper(UserMapper.class);
            
            // 사용자 정보 가져오기
            User user = mapper.getUserById(1);
            
            System.out.println("사용자 이름: " + user.getName());
            System.out.println("사용자 이메일: " + user.getEmail());
            
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Persistence Framework (2) ORM (Object-Relational Mapping)

  • ORM은 객체지향 프로그래밍 언어의 객체와 관계형 데이터베이스의 테이블을 자동으로 맵핑해주는 기술이다.
  • 대표적으로 Hibernate, JPA(Java Persistence API), Spring Data JPA가 있다.
  • CRUD 관련 메서드를 사용해서 쿼리 생성 및 실행을 자동으로 처리해준다.
  • 복잡한 쿼리일 경우 SQL Mapper와 함께 사용해도 된다.
  • JPA는 Java에서 ORM 기술에 대한 인터페이스 표준이며, 이를 구현한 구현체가 대표적으로 Hibernate이다.
  • 객체 모델 중심의 설계로 DBMS에 의존하지 않으며 비즈니스 로직 개발에 초점을 맞출 수 있다.

JPA

  • JPA는 Java Persistence API의 약자로 Java의 ORM 표준 명세이다.
  • ORM 기술을 사용하기 위해 인터페이스를 모아둔 것이다. 즉, 구현체가 필요하다.
  • 내부적으로 영속성 컨텍스트를 생성하여 Entity를 관리한다.
    • 영속성 컨텍스트 : 엔티티를 영구 저장하는 환경이다.
    • 애플레케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스와 같은 역할을 한다.
    • 엔티티 매니저를 통해 엔티티를 저장 or 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
  • 변경이 감지되면 Entity를 수정해도 알맞게 쿼리가 생성되며, 쓰기 지연 방식을 통해서 쿼리 저장소에 있는 쿼리를 한번에 flush하며 Connection을 최소화하여 성능을 향상시킬 수 있다.

Hibernate

  • JPA의 구현체 중 하나로, 많은 기능과 강력한 ORM 기능을 제공하는 인기있는 오픈 소스 프레임워크이다.
  • JPA 표준을 따르며, JPA에서 정의한 인터페이스를 구현한다.
  • 내부적으로 쿼리를 생성하고 JDBC API를 호출한다.
  • 하지만 모든 부분에서 쿼리가 적합할 순 없기에 개발자가 능동적으로 쿼리를 수정할 필요가 있고, 이럴 때는 Spring Data JPA를 사용한다.

Spring Data JPA

  • JPA를 한 단계 더 추상화를 한 Repository라는 것이 있으며, Repository Interface에 JPARepository를 상속하면 구현화된 객체를 주입해준다.
  • Repository Interface 내부에 작성한 메서드를 스프링이 알맞은 쿼리를 만들어 전송하게 해준다.
  • 쿼리 어노테이션을 활용하여 개발자가 만든 쿼리를 전송할 수도 있다.

예제 코드

엔티티 클래스

import javax.persistence.*;
import lombok.Data;

@Entity
@Table(name = "employees")
@Data
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String department;
}

 

Repository 인터페이스

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

public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}

 

서비스 클래스

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class EmployeeService {
    @Autowired
    private EmployeeRepository employeeRepository;

    public List<Employee> getAllEmployees() {
        return employeeRepository.findAll();
    }

    public Employee saveEmployee(Employee employee) {
        return employeeRepository.save(employee);
    }

    // 기타 메서드...
}