mybatis 멀티 모듈 셋팅시 Invalid bound statement (not found) 오류

mybatis 멀티 모듈 셋팅시 Invalid bound statement (not found) 오류

gradle 멀티 모듈을 셋팅하고 slice test로 mybatis 모듈이 정상적으로 기동이 되는데
여러 모듈에 mybatis xml이 나눠줘있고 root 프로젝트에서 모듈을 추가 받아서 처리 하면 아래 와 같이 해당 mapper를 찾을수 없다고 나온다

1
2
3
4
5
6
7
8
9
org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): persistence.rdbms.mybatis.arena.excel.repository.ArenaExcelMybatisRepository.selectKartRushArenaApplyUserList
at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:229)
at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:53)
at org.apache.ibatis.binding.MapperProxy.lambda$cachedInvoker$0(MapperProxy.java:96)
at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
at org.apache.ibatis.util.MapUtil.computeIfAbsent(MapUtil.java:36)
at org.apache.ibatis.binding.MapperProxy.cachedInvoker(MapperProxy.java:94)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:86)

스프링 부트를 사용해서 mybatis 의 spring-boot-starter 를 사용해서 아래처럼 설정을 했다

1
2
mybatis:
mapper-locations: classpath:mybatis/mapper/**/**/**.xml

아무리 해도 보이지 않아서 디버깅을 했는데 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 코드의 this.properties.resolveMapperLocations를 보면 되는데 아래의 코드 이다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
....
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
.....
Resource[] mapperLocations = this.properties.resolveMapperLocations();
if (!ObjectUtils.isEmpty(mapperLocations)) {
factory.setMapperLocations(mapperLocations);
}
...
return factory.getObject();
}
}

보면 리소스 패턴을 찾는데 spring의 PathMatchingResourcePatternResolver 를 사용해서 찾는다 그래서 PathMatchingResourcePatternResolver 코드를 보니

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

public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {
@Override
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
// Generally only look for a pattern after a prefix here,
// and on Tomcat only after the "*/" separator for its "war:" protocol.
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface ResourcePatternResolver extends ResourceLoader {

/**
* Pseudo URL prefix for all matching resources from the class path: "classpath*:"
* <p>This differs from ResourceLoader's classpath URL prefix in that it
* retrieves all matching resources for a given name (e.g. "/beans.xml"),
* for example in the root of all deployed JAR files.
* @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
*/
String CLASSPATH_ALL_URL_PREFIX = "classpath*:";

/**
* Resolve the given location pattern into {@code Resource} objects.
* <p>Overlapping resource entries that point to the same physical
* resource should be avoided, as far as possible. The result should
* have set semantics.
* @param locationPattern the location pattern to resolve
* @return the corresponding {@code Resource} objects
* @throws IOException in case of I/O errors
*/
Resource[] getResources(String locationPattern) throws IOException;

}

특별한 프리픽스를 사용하는데 classpath*: 이다
스프링 6.0 부터 추가 되었다고 하고 classpath: 이것은 제일 처음 매칭되는 값을 하나만 가지고 오고 classpath*: 는 전체를 모두 가지고 오게 된다

1
2
mybatis:
mapper-locations: classpath*:mybatis/mapper/**/**/**.xml

이렇게 사용하면 전체 모듈을 스캔해서 리소스를 찾게 된다

참조