无论是使用xml配置,注解配置,还是boot模式,我们在配置时只需指定mybatis-config配置和Mapper XML文件的路径,然后接口像引用Spring中其他的bean一样使用Mapper接口对数据库进行操作。原因当然是因为在容器初始化时,将Mapper XML文件的配置解析并注入到Spring容器中供我们需要时使用,在下面会对这部分内容进行适当剖析以了解Spring和Mybtais是如何为我们提供便利的。
Mybatis与Spring搭配使用是最常见的使用模式,我们只需要引入mybatis-spring依赖即可。
一、Mybatis原生初始化
首先,看一下Mybatis不依赖Spring的情况下是怎么使用的,参考Mybait官方文档上的例子:
// 不使用XML构建SqlsessionFactory
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
// 获取SqlSession
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}
第一步是构建SqlSessionFactory,可以通过XML构建,比不使用XML构建多了解析XML文件的步骤。(参见mybatis.org/mybatis-3/z…)
关键的流程通过上面这个demo可以看得很清楚,初始化的最终目的是构成一个SqlSessionFactory实例,实例中包含Configuration对象,Configurarion包含了所有配置的信息,包括数据源对象DataSource。
具体的初始化工作在SqlSessionFactoryBuilder中完成,如下:
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
public class XMLConfigBuilder extends BaseBuilder {
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析配置文件mybatis-config.xml,将xml配置中的内容保存在Configuration对象中
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// ...依次解析配置文件中的各类
// 解析Mapper XML配置
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 扫描包
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// 添加Mapper接口到配置中
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
}
可以看到最终也是通过Configutration.addMappers来添加Mapper接口来完成Mapper的接口的初始化。
二、mybatis-spring初始化
通过spring初始化SqlSessionFactory的过程在SqlSesssionFactoryBean中,下面示例一个以代码方式(通过xml配置加载也行)创建SqlSessionFactory的例子:
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
// 设置DataSource,此处暂时不展开
factoryBean.setDataSource(dataSource());
PathMatchingResourcePatternResolver pmrpr = new PathMatchingResourcePatternResolver();
Resource configLocation = pmrpr.getResource("classpath:META-INF/mybatis/mybatis-config.xml");
factoryBean.setConfigLocation(configLocation);
Resource[] configResource = pmrpr.getResources("classpath*:META-INF/mybatis/mapper/*.xml");
factoryBean.setMapperLocations(configResource);
return factoryBean.getObject();
}
}
在创建SqlSessionFactory时,最重要的是要设置一个DataSource。当然还要设置mybait配置文件mybatis-config.xml,以及Mapper XML文件路径。
具体初始化过程如下:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
// 初始化解析mybatis配置文件的XMLConfigBuilder
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
// 省略其他配置的加载
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
// 事务管理Factory
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// mapper xml不为空
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
// 加载解析Mapper XML文件
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(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);
}
整个加载配置的过程跟原生的过程基本上一致,只是通过SqlSesssionFactoryBean作为入口来进行适配。
三、总结
初始化的过程是为了构造一个SqlSessionFactory,用于后续创建SqlSession,而SqlSessionFactory包含的最重要的对象时Configuration,Configuration对象会将所有Mybatis的配置信息进行加载。
以我们最常使用的配置文件XML和Mapper XML文件来说明初始化几个核心的步骤:
1、根据配置mybatis-config.xml的路径和Mapper XML的包路径,加载到Resource中;
2、使用XMLConfigBuiler来解析mybatis-config.xml的配置并设置到Configuration对象中,配置包括mybatis所有配置,包括插件等;
3、使用XMLMapperBuilder来解析Mapper XML的资源,并通过Configuration的addMapper或addMappers来将Mapper接口初始化到容器中;
如果要关注具体哪个配置的细节,比如插件配置等,可以通过查看XMLConfigBuiler的parse过程,如果要关注Mapper XML文件的解析加载过程可以查看XMLMapperBuilder。