Programming/Spring

[Spring] JDBC와 MyBatis와 JPA 비교, 시대적 흐름에서 장단점 분석

기록하는 백앤드개발자 2024. 6. 11. 10:10
반응형

ㅁ 들어가며

ㅇ JPA와 MyBatis는 Spring에서 인기있는 프레임워크이다.

ㅇ 이 프레임워크들의 장단점을 이해하기 위해, 근간이 되는 JDBC를 이해해야 한다.

ㅇ 개인적으로 JDBC와 MyBatis, JPA를 사용하면서 시대적 상황에 따라 두 프레임워크의 장단점을 정리해 보았다.

ㅇ 참고로 11년차 개발자인 나는 JSP+JDBC, Spring + JDBC, Spring + iBatis, Spring + Mybatis, Springboot + JPA의 경험을 가지고 있다.

 

ㅁ JSP에서 JDBC로 SQL을 구현

ㅇ JDBC(Java Database Connectivity)는 Java에서 데이터베이스와 연결하고 상호 작용하는 데 사용하는 표준 API이다.

ㅇ JSP에서 JDBC를 이용해여 데이터를 조회하였는데, 그 과정은 다음과 같다.

<%@ page import="java.sql.*" %>

<%
  // 1. JDBC 드라이버 로드
  Class.forName("com.mysql.cj.jdbc.Driver");

  // 2. 데이터베이스 연결
  Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "user", "password");

  // 3. SQL 쿼리 실행
  Statement stmt = conn.createStatement();
  ResultSet rs = stmt.executeQuery("SELECT * FROM users");

  // 4. 결과 처리
  while (rs.next()) {
    out.println("ID: " + rs.getInt("id"));
    out.println("NAME: " + rs.getString("name"));
    out.println("EMAIL: " + rs.getString("email"));
    out.println("<br>");
  }

  // 5. 데이터베이스 연결 종료
  rs.close();
  stmt.close();
  conn.close();
%>

ㅇ JDBC 드라이버 로드 -> 데이터베이스 연결 -> SQL 쿼리 실행 -> 결과 처리 -> 데이터베이스 연결 종료

ㅇ 가장 기본적인 방법이지만, 개발자가 직접 SQL 쿼리를 작성하고 결과를 처리해야 하기 때문에 복잡하고 코드 작성량이 많아지는 단점이 있다.

ㅇ 요즘 흔한 백엔드 + 프론트가 하나의 JSP 소스에 들어가 있다. 

ㅇ 결과처리 시 JSON 형태로 데이터를 파싱하면 jQuery에서 JSP를 호출하여 비동기로 구현할 수도 있었다.

ㅇ 프레임워크 없이 디비작업부터 화면 출력까지 간단히 만들 수 있다는 장점이 있지만, 코드의 재사용성이 떨어져 코드량이 많다.

 

ㅁ Mybatis

ㅇ JAVA 객체와 SQL문 사이를 자동으로 매핑(Mapping)해주는 ORM(Obiect Relation Mapping) 프레임워크이다.
ㅇ SQL 파일을 별도로 분리하여 관리할 수 있고, 객체-SQL 사이의 파라미터를 자동으로 매핑해주기 때문에 JDBC를 사용했던 나의 입장에서는 대단히 편리하였다.
ㅇ DB에서 조회했던 SQL쿼리문을 그대로 사용하면서 VO를 중심으로 개발이 용이하였다.

ㅇ 로그에 SQL를 직접 출력하여 데이터 디버깅도 직접적으로 가능하였다.

ㅇ JDBC보다 코드 작성량이 줄고 개발 편의성이 향상되지만, XML 파일 관리가 필요하고 SQL 지식이 필수적이다.

 

<?xml version="1.0" encoding="UTF-8" ?> 
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="test">
	<select id="selectToday" resultType="java.util.Date">
		select sysdate from dual
	</select>
    
	<select id="selectUser" parameterType="UserVO" resultType="UserVO">
		select 
          name,
          id,
          email
		from 
          USER
        where 1=1
        <if test="name !=null  and name.equals('')">
          and name = #{name}
         </if>  
	</select>
</mapper>

ㅇ 가장 고마웠던 기능은 if문을 사용한 동적 쿼리를 작성해주는 점이었다.

ㅇ 당시 admin이나 업무지원시스템, 통계 배치를 위한 개발 시 mybatis를 많이 사용했었다.

ㅇ DB에 집중된 작업을 할 때에는 SQL문 직관적으로 인식되는 mybatis가 훨씬 편하게 느껴졌다.

ㅇ JDBC 방식에 비해 SQL의 생성과 결과처리하여 DTO로 출력하는 부분을 Mybatis가 처리해주어 소스코드량이 줄어들었다.

 

ㅁ JPA

ㅇ 역사적으로 보면 JPA는 오래 전인 이미 2006년에 출시되어 있었다.

ㅇ 이후 JPA 2.0 (2011년), JPA 2.1 (2013년) 등 버전 업데이트를 거치며 지속적으로 발전하고 Spring Framework에서 기본 ORM 솔루션으로 사용하면서 더욱 대중적인 인기를 얻었다.

ㅇ 표준 ORM API로, Java 객체와 데이터베이스 테이블을 자동으로 매핑하여 객체 지향적인 방식으로 데이터를 관리할 수 있다.

ㅇ 코드 작성량이 가장 적고 유지보수가 용이하지만, 성능 저하 가능성이 있고, JPA 구현체(예: Hibernate)에 대한 이해가 필요하다.

ㅇ 개인적으로 이 때 당시 MSA 구조의 대량 트래픽을 처리하는 백엔드 개발자로서 너무 다양한 객체를 개별적으로 SQL Mapper로 관리하기에는 불편하였다.

 

ㅇ SQL 중심적

 ㄴ 반복적인 SQL 작성

 ㄴ JAVA 객체 필드가 변경되면 SQL 수정

 ㄴ 컴파일 때가 아니라 실제 DB와 통신을 해야 SQL의 문제점을 인지하는 문제

 

ㅇ 패러다임의 불일치: 객체와 테이블 간의 불일치로 인한 불편함.

 ㄴ 상속: JAVA에는 상속개념이 있다.

 ㄴ 연관관계: JAVA는 VO에 선언하여 직접 참조하지만 DB는 외부키로 연결

// 객체의 참조가 JAVA가 더 직관적임
public class User{
	private Long id;
    private String name;
    private Group group;
    private School school;
}

// SQL을 위해서는
public class User{
	private Long id;
    private String name;
    private Long groupId;  // Group과 join
    private Long schoolId; // School과 join
}

ㅇJPA는 DB와 객체의 패러다임 불일치로 발생하는 불편한 점을 해결해 준다. 

관련동영상: [10분 테코톡] 도이의 JDBC vs SQL Mapper vs ORM

 

@Entity
@Getter
@Setter
public class Member extends BaseEntity {
   @Id    
   @GeneratedValue(strategy = GenerationType.IDENTITY)    
   private Long id;
   
   @Column(name = "name")    
   private String username;
   
   private Integer age;
   
   @Enumerated(EnumType.STRING)
   private RoleType roleType;    
   
   @Lob    
   private String description;    
  
   // 패치 타입 LAZY 설정    
   @ManyToOne(fetch = FetchType.LAZY)    
   @JoinColumn(name = "team_id", insertable = false, updatable = false)    
   private Team team;​    
   @OneToOne    
   @JoinColumn(name = "locker_id")    
   private Locker locker;​    
   @OneToMany(mappedBy = "member")    
   private List<MemberProduct> memberProducts = new ArrayList<>();​    
   public void changeTeam(Team team) {        
     this.team = team;        
     this.team.getMembers().add(this);    
 }
}
// 출처: https://ict-nroo.tistory.com/132 [개발자의 기록습관:티스토리]

 

ㅇ 개인적인 소견으로는 JPA는 MSA 구조에 특화되어 있다.

ㅇ 서비스의 트래픽이 많지 않다면, 트래픽으로 인한 장애 포인트는 적다.

ㅇ 하지만 많은 트래픽이 있고, 일부 병목이 생기면 일부 시스템의 과부화로 전체 시스템의 장애로 전파될 수 있다.

ㅇ 이를 고려해 JPA는 DB와 연동되는 부분의 지연시간을 최소화하기 위해 최대한 입출력을 단순화 하였다.

ㅇ DB팀에서도 JOIN있는 쿼리는 분해해서 사용하도록 권장받기도 하여 대체적으로 mybatis를 사용하지 않도록 하였다.

ㅇ 일 예로, FetchType.LAZY 설정에 그 특성이 녹아 있다.

  ㄴ @ManyToOne 어노테이션의 fetch 속성을 FetchType.LAZY로 지정하면 팀 엔티티의 조회 시점을 실제 해당 객체가 사용될때로 늦출 수 있다. 이와 같이 지연로딩을 사용하여, 꼭 필요할 때만 데이터를 한번도 조회하여 DB와의 트래픽을 최소화 할 수 있다.

  ㄴ 반대로 즉시 로딩하려면, 어노테이션 fetch 속성을 FetchType.EAGER하면 된다.

  ㄴ SQL 입장에서 Join처럼 보이지만 main 테이블의 데이터를 우선 조회하고 필요에 따라 다시 해당 sub 테이블을 조회하는 방식이다.

  ㄴ 서비스 포퍼먼스를 위해 극한의 작은 트랜젝션도 아끼기 위한 방법이다.

 

ㅇ 반면, RBMS의 포퍼먼스를 위한 힌트나 다른 추가적인 JOIN을 JPA에서 구현하기는 어려운 점이 있다. DB은 단순히 데이터의 입출력만 하도록 제한하는 경향이 크다. 왜? 복잡해지면 DB가 지연이 발생하고 장애로 이어질 수 있기 때문에 NoSQL을 우선 이용한다.

 

ㅁ 비교

항목 JDBC MyBatis JPA
개념 표준 데이터베이스 API ORM 프레임워크 ORM API
SQL 작성 직접 작성 XML 또는 어노테이션 자동 생성
코드 작성량 많음 중간 적음
개발 편의성 낮음 중간 높음
유지보수 어려움 중간 용이
성능 빠름 중간 느림 (일부 경우)
학습 난이도 낮음 중간 높음
대표적인 사용 사례 간단한 데이터베이스 작업, 레거시 시스템 마이그레이션 성능이 중요한 웹 애플리케이션 엔터프라이즈 애플리케이션,
웹 애플리케이션

ㅇ 시대적으로 서비스가 다양해지고 사용자 트래픽도 많이 늘게 되었다.

ㅇ 단편적 SQL 작업에서 어드민과 같은 웹 애플리케이션, 그리고 대량의 트래픽을 분산처리하는 엔터프라이즈 애플리케이션이 구동되는 클라우드 환경까지 발전하면서 시대적인 흐름에서 프레임워크는 발전하였다.

 

ㅁ 선택을 위한 조언

ㅇ 어드민과 같은 복잡한 SQL를 사용하거나 데이터베이스 성능이 중요한 작업 시에는 Mybatis를 선택하면 좋다.

ㅇ MSA 환경에서 오케스트레이션이 필요한 규모의 프로젝트인 경우 유지보수 용이성이 중요하기 때문에 JPA가 더 적합할 수 있다.

ㅇ 어떤한 기술이라도 시대적인 배경에서 장단점이 부각되는 경우가 있다.

ㅇ 각 기술의 장단점을 이해하고 상황에 맞게 기술을 선택하는 것도 매우 중요하다. 

 

ㅁ 함께 보면 좋은 사이트

 [10분 테코톡] 도이의 JDBC vs SQL Mapper vs ORM

 [10분 테코톡] ⏰ 아마찌의 ORM vs SQL Mapper vs JDBC

JPA - 즉시로딩과 지연로딩 (FetchType.EAGER or LAZY)

반응형