JPA vs MyBatis 완벽 비교와 JNI 동작 원리: ORM vs SQL Mapper, 네이티브 인터페이스까지

JPA vs MyBatis 완벽 비교와 JNI 동작 원리: ORM vs SQL Mapper, 네이티브 인터페이스까지

“JPA와 MyBatis의 차이는?”, “프로젝트에서 왜 JPA를 선택했나요?”, “JNI가 뭔가요?” — JPA와 MyBatis 비교는 신입 면접 단골이고, JNI는 Java가 네이티브 코드와 상호작용하는 핵심 메커니즘이다.


1. ORM vs SQL Mapper

1.1 개념 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌─────────────────────────────────────────────────────────────────────┐
│                 ORM vs SQL Mapper 개념                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  [ORM - Object Relational Mapping]                                  │
│  = 객체와 테이블을 자동으로 매핑                                    │
│                                                                     │
│  Java 객체 ←──── 자동 매핑 ────→ DB 테이블                         │
│                                                                     │
│  • SQL을 직접 작성하지 않음 (자동 생성)                             │
│  • 객체 중심 개발 (도메인 모델에 집중)                               │
│  • 대표: JPA (Hibernate)                                            │
│                                                                     │
│  ──────────────────────────────────────────────────────             │
│                                                                     │
│  [SQL Mapper]                                                       │
│  = SQL 결과를 객체에 매핑                                           │
│                                                                     │
│  개발자가 직접 SQL 작성 → 결과를 객체에 매핑                        │
│                                                                     │
│  • SQL을 직접 작성 (완전한 제어)                                    │
│  • SQL 중심 개발                                                    │
│  • 대표: MyBatis                                                    │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

1.2 동일 기능의 코드 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ==================== JPA ====================

// Entity
@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private String email;
}

// Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
    List<Member> findByName(String name);  // 메서드 이름으로 쿼리 자동 생성
}

// Service
Member member = new Member("김개발", "dev@example.com");
memberRepository.save(member);  // INSERT 자동 생성
Member found = memberRepository.findById(1L).orElseThrow();
found.setName("박개발");  // Dirty Checking → UPDATE 자동 실행
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ==================== MyBatis ====================

// DTO
public class Member {
    private Long id;
    private String name;
    private String email;
}

// Mapper Interface
@Mapper
public interface MemberMapper {
    Member findById(Long id);
    List<Member> findByName(String name);
    void insert(Member member);
    void update(Member member);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- MemberMapper.xml -->
<mapper namespace="com.example.mapper.MemberMapper">

    <select id="findById" resultType="Member">
        SELECT id, name, email FROM member WHERE id = #{id}
    </select>

    <select id="findByName" resultType="Member">
        SELECT id, name, email FROM member WHERE name = #{name}
    </select>

    <insert id="insert" parameterType="Member">
        INSERT INTO member (name, email) VALUES (#{name}, #{email})
    </insert>

    <update id="update" parameterType="Member">
        UPDATE member SET name = #{name}, email = #{email} WHERE id = #{id}
    </update>

</mapper>

2. JPA vs MyBatis 핵심 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
┌─────────────────────────────────────────────────────────────────────┐
│                 JPA vs MyBatis 비교표                                 │
├──────────────────┬──────────────────────┬────────────────────────────┤
│ 항목             │ JPA (Hibernate)      │ MyBatis                    │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 패러다임         │ ORM (객체 중심)      │ SQL Mapper (SQL 중심)      │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ SQL 작성         │ 자동 생성 (JPQL)     │ 직접 작성 (XML/어노테이션) │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 학습 곡선        │ 높음 (영속성 컨텍스트│ 낮음 (SQL 알면 바로 사용)  │
│                  │ , 연관관계, N+1 등)  │                            │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 생산성           │ CRUD 매우 빠름       │ 단순 CRUD도 SQL 필요       │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ SQL 제어         │ 제한적 (네이티브 쿼리│ 완전한 제어                │
│                  │ 로 보완 가능)        │                            │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 복잡한 쿼리      │ QueryDSL 필요        │ XML로 자유롭게 작성        │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 동적 쿼리        │ QueryDSL / Criteria  │ <if>, <choose>, <foreach>  │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 캐시             │ 1차 캐시 (영속성 컨텍│ 별도 설정 필요             │
│                  │ 스트) + 2차 캐시     │                            │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 변경 감지        │ Dirty Checking 지원  │ 지원 안 함 (직접 UPDATE)   │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ DB 종속성        │ 낮음 (방언으로 DB변경│ 높음 (DB별 SQL 수정 필요)  │
│                  │ 쉬움)               │                            │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 국내 점유율      │ 증가 추세 (스타트업) │ 높음 (대기업, SI, 공공)    │
├──────────────────┼──────────────────────┼────────────────────────────┤
│ 적합한 프로젝트  │ 도메인 중심, CRUD 위주│ 복잡한 SQL, 레거시 DB      │
└──────────────────┴──────────────────────┴────────────────────────────┘

3. 각각의 장단점 상세

3.1 JPA 장단점

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────────┐
│                       JPA 장단점                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  [장점]                                                             │
│  ✅ CRUD 생산성 매우 높음 (save, findById 한 줄)                   │
│  ✅ 객체 중심 설계 → 도메인 모델에 집중 가능                        │
│  ✅ 영속성 컨텍스트 (1차 캐시, 변경 감지, 지연 로딩)                │
│  ✅ DB 독립적 (MySQL → PostgreSQL 전환 용이)                       │
│  ✅ Spring Data JPA로 Repository 자동 구현                          │
│                                                                     │
│  [단점]                                                             │
│  ❌ 학습 곡선 높음 (영속성 컨텍스트, 프록시, 지연로딩 이해 필수)   │
│  ❌ N+1 문제, LazyInitializationException 등 함정                  │
│  ❌ 복잡한 쿼리는 JPQL만으로 부족 → QueryDSL 추가 필요             │
│  ❌ 통계/집계 같은 복잡한 SQL은 네이티브 쿼리로 우회                │
│  ❌ 자동 생성 SQL이 비효율적일 수 있음 (성능 튜닝 어려움)          │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

3.2 MyBatis 장단점

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────────┐
│                      MyBatis 장단점                                   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  [장점]                                                             │
│  ✅ SQL을 직접 제어 → 성능 최적화 용이                              │
│  ✅ 학습 곡선 낮음 (SQL만 알면 바로 사용)                           │
│  ✅ 복잡한 쿼리 작성이 자유로움 (JOIN, 서브쿼리, 함수)             │
│  ✅ 동적 쿼리가 XML 태그로 직관적                                   │
│  ✅ 레거시 DB나 복잡한 스키마에 적합                                 │
│                                                                     │
│  [단점]                                                             │
│  ❌ 단순 CRUD도 SQL을 직접 작성해야 함 (반복 작업)                  │
│  ❌ 객체-테이블 매핑을 수동으로 관리                                 │
│  ❌ DB 변경 시 SQL 전체 수정 필요                                   │
│  ❌ 영속성 컨텍스트 없음 (캐시, 변경감지 직접 구현)                 │
│  ❌ XML 기반 설정이 장황해질 수 있음                                │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

4. 동적 쿼리 비교

4.1 MyBatis 동적 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 검색 조건이 있을 때만 WHERE 절에 추가 -->
<select id="searchMembers" resultType="Member">
    SELECT * FROM member
    <where>
        <if test="name != null">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="email != null">
            AND email = #{email}
        </if>
        <if test="status != null">
            AND status IN
            <foreach item="s" collection="status" open="(" separator="," close=")">
                #{s}
            </foreach>
        </if>
    </where>
    ORDER BY created_at DESC
    LIMIT #{offset}, #{limit}
</select>

4.2 JPA + QueryDSL 동적 쿼리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public List<Member> searchMembers(String name, String email, List<Status> status) {
    BooleanBuilder builder = new BooleanBuilder();

    if (name != null) {
        builder.and(member.name.contains(name));
    }
    if (email != null) {
        builder.and(member.email.eq(email));
    }
    if (status != null && !status.isEmpty()) {
        builder.and(member.status.in(status));
    }

    return queryFactory
        .selectFrom(member)
        .where(builder)
        .orderBy(member.createdAt.desc())
        .offset(offset)
        .limit(limit)
        .fetch();
}

5. 선택 기준

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────────────────────┐
│                 JPA vs MyBatis 선택 기준                              │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  JPA를 선택해야 할 때:                                              │
│  • 도메인 모델 중심 설계 (DDD)                                      │
│  • CRUD 위주의 서비스 (게시판, 회원 관리 등)                        │
│  • 새 프로젝트 (스키마를 직접 설계)                                 │
│  • DB 변경 가능성이 있는 경우                                       │
│  • 빠른 프로토타이핑이 필요한 스타트업                               │
│                                                                     │
│  MyBatis를 선택해야 할 때:                                          │
│  • 복잡한 SQL이 많은 서비스 (통계, 리포트, 배치)                    │
│  • 레거시 DB를 사용해야 하는 경우 (스키마 변경 불가)                │
│  • DBA가 SQL을 직접 관리하는 조직                                   │
│  • 대기업/SI/공공 프로젝트 (MyBatis 선호 문화)                     │
│  • SQL 성능 튜닝이 핵심인 프로젝트                                  │
│                                                                     │
│  혼용도 가능:                                                       │
│  • 기본 CRUD → JPA                                                 │
│  • 복잡한 통계/집계 → MyBatis 또는 네이티브 쿼리                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

6. JNI (Java Native Interface)

6.1 JNI란

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌─────────────────────────────────────────────────────────────────────┐
│                       JNI 개념                                       │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  JNI = Java에서 C/C++ 같은 네이티브 코드를 호출하는 인터페이스      │
│                                                                     │
│  ┌──────────────────┐         ┌──────────────────┐                  │
│  │   Java 코드      │  JNI    │  C/C++ 코드      │                  │
│  │                  │ ←────→  │  (네이티브)       │                  │
│  │  JVM 위에서 실행 │         │  OS 위에서 직접   │                  │
│  └──────────────────┘         └──────────────────┘                  │
│                                                                     │
│  왜 필요한가?                                                       │
│  • 하드웨어 직접 제어 (Java로는 불가)                               │
│  • 기존 C/C++ 라이브러리 재사용                                     │
│  • 성능이 극도로 중요한 연산 (암호화, 영상 처리)                    │
│  • OS 고유 기능 사용 (시스템 콜 등)                                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

6.2 JNI 동작 흐름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
┌─────────────────────────────────────────────────────────────────────┐
│                    JNI 호출 흐름                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  [1] Java에서 native 메서드 선언                                    │
│      public class NativeLib {                                       │
│          static { System.loadLibrary("mylib"); }                    │
│          public native int calculate(int a, int b);                 │
│      }                                                              │
│                                                                     │
│  [2] javac -h 로 C 헤더 파일 생성                                  │
│      → NativeLib.h 파일 생성                                       │
│                                                                     │
│  [3] C/C++로 네이티브 함수 구현                                     │
│      JNIEXPORT jint JNICALL                                         │
│      Java_NativeLib_calculate(JNIEnv *env, jobject obj,             │
│                               jint a, jint b) {                     │
│          return a + b;                                               │
│      }                                                              │
│                                                                     │
│  [4] 공유 라이브러리로 컴파일                                       │
│      → libmylib.so (Linux) / mylib.dll (Windows)                   │
│                                                                     │
│  [5] Java에서 호출                                                  │
│      NativeLib lib = new NativeLib();                                │
│      int result = lib.calculate(3, 5);  // → C 함수 호출           │
│                                                                     │
│  호출 흐름:                                                         │
│  Java 코드 → JVM → JNI 브릿지 → 네이티브 코드 → 결과 반환         │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

6.3 JNI 실사용 사례

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────────────┐
│                    JNI 실사용 사례                                    │
├──────────────────────┬──────────────────────────────────────────────┤
│ 사례                 │ 설명                                         │
├──────────────────────┼──────────────────────────────────────────────┤
│ JDBC 드라이버        │ DB 연결 시 네이티브 드라이버 호출            │
│                      │ (Type 2 JDBC Driver)                         │
├──────────────────────┼──────────────────────────────────────────────┤
│ Java AWT/Swing       │ OS GUI 컴포넌트와 연결                      │
│                      │ (윈도우 생성, 이벤트 처리)                   │
├──────────────────────┼──────────────────────────────────────────────┤
│ Android NDK          │ 게임 엔진, 영상 처리 등                     │
│                      │ 고성능 로직을 C/C++로 구현                   │
├──────────────────────┼──────────────────────────────────────────────┤
│ 암호화 라이브러리    │ OpenSSL 같은 네이티브 암호화 사용            │
│                      │ (Java 순수 구현보다 빠름)                    │
├──────────────────────┼──────────────────────────────────────────────┤
│ 파일 시스템 접근     │ OS 고유 파일 감시 (inotify 등)              │
├──────────────────────┼──────────────────────────────────────────────┤
│ JVM 내부             │ System.currentTimeMillis() 등               │
│                      │ 실제로 네이티브 메서드로 구현됨              │
└──────────────────────┴──────────────────────────────────────────────┘

6.4 JNI의 장단점과 대안

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌─────────────────────────────────────────────────────────────────────┐
│                  JNI 장단점과 대안                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  [장점]                                                             │
│  • 기존 C/C++ 코드 재사용 가능                                     │
│  • 하드웨어/OS 기능 직접 접근                                       │
│  • 성능 최적화 (연산 집약 로직)                                     │
│                                                                     │
│  [단점]                                                             │
│  • 플랫폼 의존적 (OS별 라이브러리 빌드 필요)                       │
│  • JVM 안정성 위험 (네이티브 코드 크래시 → JVM 크래시)             │
│  • 디버깅 어려움 (Java + C 혼합 디버깅)                            │
│  • GC가 네이티브 메모리를 관리하지 않음 (메모리 릭 위험)           │
│  • 개발 복잡도 높음                                                 │
│                                                                     │
│  [대안]                                                             │
│  • JNA (Java Native Access): JNI보다 간편 (헤더 생성 불필요)       │
│  • Panama (Foreign Function & Memory API): Java 21+, JNI 대체 목표│
│  • ProcessBuilder: 외부 프로세스 실행 (단순 호출 시)                │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

면접에서 자주 묻는 질문

Q1. JPA와 MyBatis의 차이는?

JPA는 ORM으로 객체와 테이블을 자동 매핑하여 SQL을 직접 작성하지 않고, 영속성 컨텍스트로 캐시·변경감지·지연로딩을 지원합니다. MyBatis는 SQL Mapper로 개발자가 SQL을 직접 작성하고 결과만 객체에 매핑합니다. JPA는 생산성과 객체 중심 설계에, MyBatis는 SQL 제어와 복잡한 쿼리에 강점이 있습니다.

Q2. 프로젝트에서 JPA를 선택한 이유는?

도메인 모델 중심으로 설계하고 싶었고, CRUD 비중이 높아 JPA의 생산성이 유리했습니다. Spring Data JPA로 Repository 구현을 자동화하고, Dirty Checking으로 업데이트 로직을 간결하게 유지할 수 있었습니다. 복잡한 조회가 필요한 부분은 QueryDSL로 보완했습니다.

Q3. JPA의 N+1 문제를 MyBatis에서는 어떻게 처리하나요?

MyBatis는 SQL을 직접 작성하므로 JOIN을 명시적으로 작성하여 한 번의 쿼리로 해결합니다. JPA의 N+1은 지연 로딩 시 연관 엔티티를 개별 조회하면서 발생하는데, MyBatis에서는 이런 자동 조회 메커니즘이 없어 N+1 자체가 발생하지 않습니다. 다만, 개발자가 비효율적인 쿼리를 작성할 수는 있습니다.

Q4. 동적 쿼리는 어떻게 처리하나요?

MyBatis는 XML의 <if>, <choose>, <foreach>, <where> 태그로 직관적으로 작성합니다. JPA는 기본적으로 동적 쿼리가 불편하므로 QueryDSLBooleanBuilderBooleanExpression으로 타입 안전하게 작성합니다. Criteria API도 있지만 가독성이 떨어져 실무에서는 QueryDSL이 표준입니다.

Q5. JNI란 무엇이고 언제 사용하나요?

JNI(Java Native Interface)는 Java에서 C/C++ 네이티브 코드를 호출하거나, 반대로 네이티브 코드에서 Java를 호출할 수 있는 인터페이스입니다. 기존 C/C++ 라이브러리 재사용, 하드웨어 직접 제어, 성능 최적화가 필요할 때 사용합니다. JDBC Type 2 드라이버, Android NDK, JVM 내부 메서드(System.currentTimeMillis()) 등이 대표적입니다.

Q6. JNI의 단점은?

플랫폼 의존적이라 OS별로 네이티브 라이브러리를 각각 빌드해야 하고, 네이티브 코드에서 크래시가 발생하면 JVM 전체가 죽을 수 있습니다. GC가 네이티브 메모리를 관리하지 않아 메모리 릭 위험이 있고, Java와 C를 넘나드는 디버깅이 어렵습니다. 최근에는 JNA나 Java 21+의 Foreign Function API(Panama)가 더 안전한 대안으로 제시됩니다.

Q7. JPA와 MyBatis를 같이 쓸 수 있나요?

네, 실무에서 혼용하는 프로젝트가 있습니다. 기본 CRUD와 도메인 로직은 JPA로 처리하고, 복잡한 통계 쿼리나 배치 작업은 MyBatis로 처리하는 방식입니다. 다만 같은 트랜잭션 내에서 JPA의 영속성 컨텍스트와 MyBatis의 직접 SQL이 충돌할 수 있으므로, flush 시점과 캐시 동기화에 주의해야 합니다.


핵심 정리: JPA는 객체 중심 ORM으로 생산성과 도메인 설계에 강하고, MyBatis는 SQL을 직접 제어하여 복잡한 쿼리와 성능 최적화에 강하다. 선택은 프로젝트 특성에 따르며, 혼용도 가능하다. JNI는 Java와 네이티브 코드의 브릿지로, 하드웨어 접근이나 기존 라이브러리 활용 시 사용하지만 안정성·이식성 비용이 크므로 대안(JNA, Panama)도 고려해야 한다.