Spring Boot 기반의 백엔드 프로젝트에서 ORM 프레임워크인 JPA(Hibernate) 와
SQL 중심 매퍼 프레임워크인 MyBatis를 함께 사용하는 케이스가 점점 늘고 있습니다.
“둘 중 하나만 써야 하는 것 아닌가요?”라고 생각할 수 있지만,
실제로는 각자의 장점을 살려서 함께 쓰는 하이브리드 구조가 실무에서 꽤 효과적입니다.
이 글에서는 MyBatis와 JPA를 같은 프로젝트에서 어떻게 혼용할 수 있는지,
언제 어떤 걸 선택해야 하는지, 그리고 실제 예제를 기반으로 구조를 어떻게 설계하면 좋은지 알려드립니다.
왜 혼용하는가?
구분 | JPA | MyBatis |
목적 | 객체 중심의 ORM | SQL 중심의 매핑 |
장점 | 생산성, 추상화, 연관관계 관리 | 복잡한 SQL 최적화, 완전한 쿼리 제어 |
단점 | 복잡한 SQL 작성이 불편함 | 반복되는 CRUD 코드 발생 |
혼용 목적
- JPA는 도메인 중심 CRUD 처리
- 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를 쓸까?
상황 | 추천 방식 | 이유 |
단순 CRUD | JPA | 빠르고 코드량 적음 |
복잡한 검색 조건 | JPA (QueryDSL) or MyBatis | QueryDSL이나 XML에서 유연하게 |
다중 조인 + 결과 DTO | MyBatis | ResultMap 활용, 성능 최적화 쉬움 |
대용량 조회 / 리포트 | MyBatis | SQL 튜닝 용이 |
엔티티 기반 도메인 설계 | JPA | 연관관계 매핑 강점 |
실무 팁
트랜잭션 주의
JPA와 MyBatis 모두 같은 @Transactional로 묶을 수 있지만,
Datasource가 다르면 별도 트랜잭션 매니저를 설정해야 합니다.
@Transactional
public void processUser() {
userRepository.save(...);
userMapper.insertAuditLog(...);
}
Entity와 Mapper DTO는 분리 권장
JPA Entity는 ORM용으로만
MyBatis DTO는 ResultType 매핑용으로 따로 만들기
장단점 요약
항목 | JPA | MyBatis |
코드 간결성 | 좋다 | 나쁘다 |
복잡 SQL 처리 | 나쁘다 | 좋다 |
페이징/정렬 | Pageable 등 쉽게 처리 | 수동 처리 필요 |
조인 DTO 매핑 | 불편함 (Native/Projection 필요) | 매우 유연함 |
둘의 장단점을 조합하면 최적의 성능 + 유지보수성을 확보할 수 있습니다.
마무리
MyBatis와 JPA는 서로 경쟁 관계가 아닌 보완적인 도구입니다.
각 상황에 맞는 도구를 선택하고, 유지보수 가능한 코드 구조를 만들면
효율적이고 확장성 있는 백엔드 아키텍처를 구현할 수 있습니다.
댓글