[Spring] JDBC와 MyBatis와 JPA 비교, 시대적 흐름에서 장단점 분석
ㅁ 들어가며
ㅇ 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