TokyoAJ

도쿄아재

SPRINGBOOT 2025.03.25

실무에서 MyBatis + JPA 혼용하는 방법 – 장단점, 구조, 실전 팁

Spring Boot 기반의 백엔드 프로젝트에서 ORM 프레임워크인 JPA(Hibernate)

SQL 중심 매퍼 프레임워크인 MyBatis함께 사용하는 케이스가 점점 늘고 있습니다.

“둘 중 하나만 써야 하는 것 아닌가요?”라고 생각할 수 있지만,

실제로는 각자의 장점을 살려서 함께 쓰는 하이브리드 구조가 실무에서 꽤 효과적입니다.

이 글에서는 MyBatis와 JPA를 같은 프로젝트에서 어떻게 혼용할 수 있는지,

언제 어떤 걸 선택해야 하는지, 그리고 실제 예제를 기반으로 구조를 어떻게 설계하면 좋은지 알려드립니다.


왜 혼용하는가?

구분JPAMyBatis
목적객체 중심의 ORMSQL 중심의 매핑
장점생산성, 추상화, 연관관계 관리복잡한 SQL 최적화, 완전한 쿼리 제어
단점복잡한 SQL 작성이 불편함반복되는 CRUD 코드 발생


혼용 목적

  1. JPA는 도메인 중심 CRUD 처리
  2. MyBatis는 복잡한 조인, 보고서형 SQL, 대용량 배치에 사용



실무 예시: JPA + MyBatis 함께 사용하는 구조

디렉토리 구조

src/
├── main/
│ ├── java/
│ │ └── com/example/
│ │ ├── entity/ ← JPA Entity 클래스
│ │ ├── repository/ ← JPA Repository
│ │ ├── mapper/ ← MyBatis Mapper 인터페이스
│ │ └── service/ ← JPA + MyBatis 혼합 비즈니스 로직
│ └── resources/
│ └── mapper/ ← MyBatis XML 파일


설정 방법

1. application.yml

spring:
datasource:
url: jdbc:mysql://localhost:3306/demo
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver

jpa:
hibernate:
ddl-auto: none
show-sql: true
open-in-view: false

mybatis:
mapper-locations: classpath:mapper/**/*.xml
type-aliases-package: com.example.entity


2. JPA Entity & Repository 예시

@Entity
@Table(name = "user")
public class User {
@Id
private Long id;
private String username;
private String email;
}


public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}


3. MyBatis Mapper 예시

UserMapper.java

@Mapper
public interface UserMapper {
List<User> selectUserListByStatus(@Param("status") String status);
}

UserMapper.xml

<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUserListByStatus" resultType="User">
SELECT * FROM user WHERE status = #{status}
</select>
</mapper>


4. 서비스에서 혼용 사용 예

@Service
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;
private final UserMapper userMapper;

public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}

public List<User> getActiveUsers() {
return userMapper.selectUserListByStatus("ACTIVE");
}
}


언제 JPA, 언제 MyBatis를 쓸까?

상황추천 방식이유
단순 CRUDJPA빠르고 코드량 적음
복잡한 검색 조건JPA (QueryDSL) or MyBatisQueryDSL이나 XML에서 유연하게
다중 조인 + 결과 DTOMyBatisResultMap 활용, 성능 최적화 쉬움
대용량 조회 / 리포트MyBatisSQL 튜닝 용이
엔티티 기반 도메인 설계JPA연관관계 매핑 강점


실무 팁

트랜잭션 주의

JPA와 MyBatis 모두 같은 @Transactional로 묶을 수 있지만,

Datasource가 다르면 별도 트랜잭션 매니저를 설정해야 합니다.

@Transactional
public void processUser() {
userRepository.save(...); // JPA
userMapper.insertAuditLog(...); // MyBatis
}


Entity와 Mapper DTO는 분리 권장

JPA Entity는 ORM용으로만

MyBatis DTO는 ResultType 매핑용으로 따로 만들기


장단점 요약

항목JPAMyBatis
코드 간결성좋다나쁘다
복잡 SQL 처리나쁘다좋다
페이징/정렬Pageable 등 쉽게 처리수동 처리 필요
조인 DTO 매핑불편함 (Native/Projection 필요)매우 유연함

둘의 장단점을 조합하면 최적의 성능 + 유지보수성을 확보할 수 있습니다.


마무리

MyBatis와 JPA는 서로 경쟁 관계가 아닌 보완적인 도구입니다.

각 상황에 맞는 도구를 선택하고, 유지보수 가능한 코드 구조를 만들면

효율적이고 확장성 있는 백엔드 아키텍처를 구현할 수 있습니다.


댓글