Categories
웹개발/Mybatis
@MapperScan 설정과 Mapper 인터페이스가 Bean으로 안 잡힐 때 확인할 부분
Mapper가 Bean으로 안 잡히는 증상
Mapper 인터페이스를 서비스에서 주입받으려는데 기동 시점에 빈을 못 찾는다. 가장 흔한 메시지는 다음과 같다. @MapperScan Mapper 빈 등록이 제대로 안 됐을 때 나타나는 전형적인 오류다.
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'com.example.member.mapper.MemberMapper' available
Consider defining a bean of type 'com.example.member.mapper.MemberMapper'
in your configuration.
이 메시지는 "그 타입의 빈이 컨테이너에 없다"는 뜻이다. 즉 Mapper 인터페이스가 빈으로 등록되지 않았다는 것이다. MyBatis는 인터페이스만 있어도 내부적으로 프록시 구현체를 만들어 빈으로 올려 주는데, 그 등록 과정을 담당하는 게 @MapperScan(또는 XML의 MapperScannerConfigurer)이다. 이게 동작하지 않으면 위 오류가 난다.
주의할 점은, 빈 등록과 SQL 매핑은 별개라는 것이다. 빈은 잡혔는데 호출 시 BindingException이 난다면 그건 다음 단계(namespace/SQL id) 문제다. 증상을 먼저 구분하고 시작한다.
원인 요약
| 원인 | 증상 | 확인 방법 |
|---|---|---|
| 스캔 패키지 불일치 | NoSuchBeanDefinitionException | @MapperScan basePackages와 실제 위치 비교 |
| @MapperScan 누락 | 모든 Mapper 빈 없음 | 설정 클래스에 어노테이션 존재 확인 |
| 설정 클래스가 스캔 밖 | @MapperScan 자체가 동작 안 함 | @Configuration 위치와 컴포넌트 스캔 범위 확인 |
| SqlSessionFactory 없음 | 기동 실패 또는 빈 생성 오류 | SqlSessionFactory 빈 등록 확인 |
| namespace 불일치 | 빈은 잡히나 BindingException | XML namespace와 인터페이스 풀네임 비교 |
해결 방법
1. @MapperScan 위치와 패키지부터 확인
가장 흔한 원인이다. @MapperScan의 패키지가 실제 Mapper 인터페이스 위치와 다르면 아무것도 등록되지 않는다. 보통 설정 클래스나 메인 클래스에 붙인다.
// Java Config 방식
@Configuration
@MapperScan(basePackages = "com.example.**.mapper")
public class MyBatisConfig {
// SqlSessionFactory, DataSource 등도 여기서 정의
}
basePackages 값이 실제 인터페이스가 있는 패키지를 포함하는지 본다. com.example.member.mapper.MemberMapper가 있다면 com.example.**.mapper 또는 com.example.member.mapper가 범위에 들어와야 한다. 패키지 오타나 한 단계 어긋남이 의외로 자주 나온다.
2. XML 설정이라면 MapperScannerConfigurer 확인
레거시·공공 SI에서 흔한 XML 설정이라면 어노테이션 대신 MapperScannerConfigurer를 쓴다. 같은 역할이다.
<!-- root-context.xml -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.**.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
이 빈이 빠져 있으면 Mapper가 등록되지 않는다. basePackage 경로와 sqlSessionFactoryBeanName이 실제 등록한 SqlSessionFactory 빈 이름과 맞는지도 확인한다.
3. 설정 클래스가 컴포넌트 스캔 범위 안에 있는지 확인
@MapperScan을 붙인 @Configuration 클래스 자체가 스캔되지 않으면 어노테이션도 동작하지 않는다. 메인 클래스나 루트 설정의 컴포넌트 스캔 범위 밖에 설정 클래스가 있으면 이런 일이 생긴다.
// Spring Boot: 메인 클래스에 직접 붙이는 방법도 있다
@SpringBootApplication
@MapperScan("com.example.**.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Spring Boot라면 메인 클래스의 패키지를 기준으로 컴포넌트 스캔이 동작하므로, Mapper와 메인 클래스의 패키지 구조가 크게 어긋나 있지 않은지 본다.
4. SqlSessionFactory가 등록돼 있는지 확인
@MapperScan은 SqlSessionFactory를 기반으로 Mapper 프록시를 만든다. SqlSessionFactory 빈이 없거나 생성에 실패하면 Mapper 등록도 따라서 실패한다. DataSource → SqlSessionFactory가 정상 등록됐는지 먼저 확인한다.
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources("classpath:mappers/**/*.xml"));
return factory.getObject();
}
SqlSessionFactory가 여러 개인 멀티 DB 환경이라면, @MapperScan에 어느 팩토리를 쓸지 sqlSessionFactoryRef로 명시해야 한다. 이걸 빠뜨리면 엉뚱한 팩토리를 참조하거나 등록이 꼬인다.
5. @Mapper 어노테이션 방식과 혼용 점검
Mapper를 등록하는 방법은 두 가지다. 인터페이스마다 @Mapper를 붙이는 방식과, 패키지를 한 번에 스캔하는 @MapperScan 방식이다. 둘 중 하나면 충분하다.
// 방법 A: 인터페이스마다 @Mapper
@Mapper
public interface MemberMapper {
MemberVO selectMember(String memberId);
}
// 방법 B: 설정에서 @MapperScan 한 번 (인터페이스엔 어노테이션 불필요)
// @MapperScan("com.example.**.mapper")
@MapperScan을 쓰기로 했으면 인터페이스에 @Mapper를 굳이 안 붙여도 된다. 반대로 @Mapper만 쓰는데 인터페이스가 컴포넌트 스캔 범위 밖이면 안 잡힐 수 있다. 프로젝트에서 한 방식으로 통일하는 게 혼선을 줄인다.
빈은 잡혔는데 호출 시 BindingException이 난다면
NoSuchBeanDefinitionException이 사라졌는데 이번엔 호출 시점에 Invalid bound statement (not found)가 난다면, 빈 등록은 됐지만 SQL 매핑이 안 연결된 것이다. 이 경우는 @MapperScan 문제가 아니라 매퍼 XML 쪽을 본다.
<!-- namespace가 인터페이스 풀네임과 정확히 같아야 한다 -->
<mapper namespace="com.example.member.mapper.MemberMapper">
<select id="selectMember" parameterType="string"
resultType="com.example.member.vo.MemberVO">
SELECT member_id, member_nm FROM member WHERE member_id = #{memberId}
</select>
</mapper>
XML의 namespace가 인터페이스 풀 패키지명과 다르거나, 메서드명과 id가 안 맞거나, mapperLocations 경로에 XML이 안 잡히면 이 오류가 난다. 빌드 후 XML이 classes 아래 실제로 들어갔는지도 확인한다.
실무에서 자주 하는 실수
- @MapperScan 패키지를 한 단계 좁게 잡아(
com.example.member) 하위 mapper 패키지를 못 잡는 경우. - 설정 클래스를 메인 패키지 밖에 두고 컴포넌트 스캔이 안 닿는 경우.
- @Mapper와 @MapperScan을 섞어 쓰다 일부만 등록되는 경우.
- 멀티 DataSource인데 sqlSessionFactoryRef를 안 줘서 잘못된 팩토리를 참조하는 경우.
- 빈은 잡혔는데 namespace 불일치로 BindingException을 만나고도 같은 원인으로 착각하는 경우. 증상부터 구분한다.
FAQ
Q1. NoSuchBeanDefinitionException과 BindingException은 뭐가 다른가요?
전자는 Mapper 빈 자체가 등록 안 된 것(@MapperScan/패키지 문제), 후자는 빈은 있으나 SQL 매핑이 안 연결된 것(namespace/id/경로 문제)이다. 원인 영역이 다르다.
Q2. @MapperScan은 어디에 붙여야 하나요?
SqlSessionFactory를 정의한 @Configuration 클래스나 Spring Boot 메인 클래스에 붙인다. 단, 그 클래스가 컴포넌트 스캔 범위 안에 있어야 한다.
Q3. @Mapper와 @MapperScan을 같이 써야 하나요?
둘 중 하나면 된다. 패키지 단위로 한 번에 등록하려면 @MapperScan, 개별 인터페이스마다 명시하려면 @Mapper를 쓴다. 한 방식으로 통일하는 것이 좋다.
Q4. XML 설정 프로젝트인데 @MapperScan을 못 씁니다.
XML에서는 MapperScannerConfigurer 빈이 같은 역할을 한다. basePackage와 sqlSessionFactoryBeanName을 지정한다.
Q5. DataSource가 여러 개인데 Mapper가 꼬입니다.
각 @MapperScan에 sqlSessionFactoryRef로 어느 팩토리를 쓸지 명시한다. 패키지도 DB별로 분리하면 관리가 깔끔하다.
정리
@MapperScan Mapper 빈 등록이 안 될 때는 증상부터 나눈다. 기동 시 NoSuchBeanDefinitionException이면 빈 등록 단계, 호출 시 BindingException이면 SQL 매핑 단계다. 빈 등록 문제라면 (1) @MapperScan 패키지가 실제 위치를 포함하는지, (2) 설정 클래스가 스캔 범위 안인지, (3) SqlSessionFactory가 정상 등록됐는지, (4) 멀티 DB면 팩토리를 명시했는지를 순서대로 본다. XML 설정이면 MapperScannerConfigurer가 같은 역할을 한다. Spring 버전, Boot 사용 여부, 설정 방식(XML/Java Config)에 따라 세부가 다를 수 있으니, 패키지 구조와 빈 등록 순서를 기준으로 점검하면 대부분 원인을 좁힐 수 있다.
'웹개발 > Mybatis' 카테고리의 다른 글
| MyBatis #{} ${} 차이, SQL Injection과 Oracle 동적쿼리 기준 정리 (0) | 2026.07.08 |
|---|---|
| MyBatis SqlSessionFactory SqlSession 차이, 역할과 생명주기 정리 (0) | 2026.07.08 |
| Spring MyBatis 연동 설정 완전 정리 (Maven/Gradle) (0) | 2026.06.29 |
| MyBatis란? JDBC·JPA와 비교하고 공공 프로젝트에서 쓰는 이유 (0) | 2026.06.28 |
| MyBatis resultMap과 resultType 차이 실무 예제로 정리 (0) | 2026.06.25 |