buildSqlSessionFactory() 这个方法比较长, 干的事情也比较多. 包括一些别名, 插件, 类型处理器等的解析.
从主流程上来看, 最主要的其实是干了两件事:
1. 对 mapper.xml 文件进行解析
2. 使用 SqlSessionFactoryBuilder 创建 sqlSessionFactory
mapper.xml的扫描工作不在这个方法里, 但是放到这里来看, 会更加清晰一点.
1. mapperLocation 的解析
@Bean
@ConditionalOnMissingBeanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throwsException {
SqlSessionFactoryBean factory= newSqlSessionFactoryBean();
......if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}returnfactory.getObject();
}
在 SqlSessionFactory 的创建方法中, 执行了一个 this.properties.resolveMapperLocations() 方法.
publicResource[] resolveMapperLocations() {
ResourcePatternResolver resourceResolver= newPathMatchingResourcePatternResolver();
List resources = new ArrayList();if (this.mapperLocations != null) {for (String mapperLocation : this.mapperLocations) {try{
Resource[] mappers=resourceResolver.getResources(mapperLocation);
resources.addAll(Arrays.asList(mappers));
}catch(IOException e) {//ignore
}
}
}return resources.toArray(newResource[resources.size()]);
}
PathMatchingResourcePatternResolver是org.springframework.core 里面的一个类, 是 spring 提供的.
可以对 mapperLocations = classpath:mapper/**Mapper.xml 进行解析, 并拿到匹配路径下的文件资源
此例中得到的结果是 SchoolMapper.xml 和 UserMapper.xml 文件的 Resource . 并将它们放入了 mapperLocations 属性中.
2. mapper.xml 文件的解析
protected SqlSessionFactory buildSqlSessionFactory() throwsIOException {......if (!isEmpty(this.mapperLocations)) {for (Resource mapperLocation : this.mapperLocations) {if (mapperLocation == null) {continue;
}try{
XMLMapperBuilder xmlMapperBuilder= newXMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
}catch(Exception e) {throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
}finally{
ErrorContext.instance().reset();
}if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
}else{if(LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}return this.sqlSessionFactoryBuilder.build(configuration);
}
这里的 this.mapperLocations 就是 SchoolMapper.xml 和 UserMapper.xml 的 Resource.
这里主要关注一下 parse() 方法.
public voidparse() {if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
2.1 解析mapper.xml配置文件
private voidconfigurationElement(XNode context) {try{
String namespace= context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}catch(Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " +e, e);
}
}
这个方法中, 对xml中的配置进行解析, 并使用 SqlSource 进行记录. 最终封装进 Configuration 的 MappedStatement mappedStatement 属性中
如例子中的 getById方法:
这里注意一个方法:
public voidaddMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
这里的id是怎么来的呢?
MapperBuilderAssistant.java
public String applyCurrentNamespace(String base, booleanisReference) {if (base == null) {return null;
}if(isReference) {//is it qualified with any namespace yet?
if (base.contains(".")) {returnbase;
}
}else{//is it qualified with this namespace yet?
if (base.startsWith(currentNamespace + ".")) {returnbase;
}if (base.contains(".")) {throw new BuilderException("Dots are not allowed in element names, please remove it from " +base);
}
}return currentNamespace + "." +base;
}
一番解析之后, UserMapper.xml 的getById 的 id = "com.study.demo.mybatis.mapper.SchoolMapper.getById"
2.2 绑定
private voidbindMapperForNamespace() {
String namespace=builderAssistant.getCurrentNamespace();if (namespace != null) {
Class> boundType = null;try{
boundType=Resources.classForName(namespace);
}catch(ClassNotFoundException e) {//ignore, bound type is not required
}if (boundType != null) {if (!configuration.hasMapper(boundType)) {//Spring may not know the real resource name so we set a flag//to prevent loading again this resource from the mapper interface//look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" +namespace);
configuration.addMapper(boundType);
}
}
}
}
从方法明上看, 此方法将 mapper 和 namespace 进行绑定.
在addMapper 方法中, 执行了一个重要方法: getSqlSourceFromAnnotations()
这个方法其实就是对 接口方法进行注解检测的.
此例中, 如果将 SchoolMapper.xml 中的配置删掉, 然后改写SchoolMapper.java 中的getById方法
@Select("select id, name from school where id = #{id}")public School getById(@Param("id") Integer id);
那么就会在这里解析并赋值.
那如果我即配置了 mapper.xml 文件, 又配置了 @Select 注解的情况下, 到底是 mapper.xml 起作用还是 @Select 起作用呢?
1. 当然, 首先不可能都起作用, 这个是很明确的, 不然出现歧义的时候, 是咋个处理呢?
2. 在解析@Select 之前, 其实已经解析过 mapper.xml 了, 如果xml已经解析并且记录, 那么即使 @Select 解析失败, 那是不是起码还有一个可以使用呢?
事实上, 不能那样使用, 会报错的. 且会导致 mapper.xml 也用不上了. 程序启动也会中断, 不能继续进行.
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.study.demo.mybatis.mapper.SchoolMapper.getById
3. SqlSessionFactory创建
//SqlSessionFactoryBuilder.java
publicSqlSessionFactory build(Configuration config) {return newDefaultSqlSessionFactory(config);
}
创建中, 主要就是持有了 Configuration 的引用.
到这里, mapper.java(还未创建实例) , mapper.xml 都已经找到了.