mybatis-spring将mybatis无缝集成到spring框架中,使得mybatis能够参与spring的事务,将mybatis mapper和sqlsession注入到其它bean中,将mybatis的异常转换成spring的DataAccessException等。本文将在代码层面,从spring容器启动到一次数据库访问结束期间,mybatis-spring的运行流程,来解析mybatis的原理及其核心技术。
配置mybatis
使用mybatis-spring,我们有多种配置bean的方式。当然无论如何配置,数据源是必不可少的:
<bean id = “dataSource” class = “org.springframework.jdbc.datasource.DriverManagerDatasource”>
<property name = “driverClassName” value = “com.mysql.jdbc.Driver”/>
<property name = “url” value = “jdbc:mysql://localhost:3306/test”/>
<property name = “username” value = “xxx”/>
<property name = “password” value = “xxx”/>
</bean>
另外,SqlSessionFactoryBean作为mybtis-spring的核心组件,同样不可缺少:
<bean id = “sqlSessionFactory” class = “org.mybatis.spring.SqlSessionFactoryBean”>
<property name = “dataSource” ref = “dataSource”/>
<property name = “configLocation” value = “classpath:mybatis.cfg.xml”/>
</bean>
其中,configLocation属性表示mybatis配置文件的路径。mybatis配置文件可以用于指定mapper文件的路径和设置类别名。
方式一:SqlSessionTemplate会话模板
<bean id = “sqlSessionTemplate” class = “org.mybatis.spring.SqlSessionTemplate”>
<constructor-arg index = “0” ref = “sqlSessionFactory”/>
</bean>
然后我们可以在DAO bean中注入SqlSessionTemplate bean进行dao操作
@Service
public class UserDaoImpl implements UserDao {
private SqlSessionTemplate sqlSession;
public void setSqlSession(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public List<User> selectUser() {
return sqlSession.selectList(“com.example.mapper.selectAll”);
}
}
方式二:dao类继承SqlSessionDaoSupport
@Service
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
@Autowired
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate){
super.setSqlSessionTemplate(sqlSessionTemplate);
}
@Override
public List<User> selectUser() {
return this.getSqlSession().selectList(“com.example.mapper.selectAll”);
}
}
由于mybatis-spring在1.2版本之后,将SqlSessionDaoSupport 中setSqlSessionTemplate 和 setSqlSessionFactory方法上的@Autowired注解移除了,因此我们需要重写其中的一个方法,自行注入SqlSessionFactory或者SqlSessionTemplate bean。方式二与方式一本质上无差别。
方式三:基于mapper接口和注解的配置
以上是使用mybatis的传统方式:在mapper文件中编写sql,将sql的id作为方法参数传入SqlSessionTemplate进行dao操作,这种方式并不符合面向对象的思想,因此mybatis引入了Mapper接口,在Mapper接口方法与Mapper xml文件sql id之间建立映射,因此可以直接通过Mapper接口来进行dao操作,原理后面讲。甚至,我们可以在接口方法上使用@Select等注解来编写sql,省去Mapper xml文件。
public interface UserMapper {
@Select(“select * from USER”)
List<user> selectUser();
}
然后创建一个对应的MapperFactoryBean
<bean id = “userMapper” class = “org.mybatis.spring.mapper.MapperFactoryBean”>
<property name = “mapperInterface” value = “com.example.mapper.UserMapper”/>
<property name = “sqlSessionFactory” ref = “sqlSessionFactory”/>
</bean>
然后我们可以在任何地方注入UserMapper进行dao操作了。事实上,在底层依然是调用SqlSessionTemplate。
手动为每一个Mapper接口创建一个MapperFactoryBean显然是低效的,Mybatis-Spring提供了一个MapperScannerConfigurer,可以自动扫描Mapper接口并创建MapperFactoryBean。只需要设置其basePackage属性即可
<bean class = “org.mybatis.spring.mapper.MapperScannerConfigurer”>
<property name = “basePackage” value = “com.example.mapper”/>
</bean>
mybatis-spring还提供了另外一种注解的方式,实现跟MapperScannerConfigurer同样的效果:
@Configuration
@MapperScan(basePackages = "org.mybatis.spring.sample.mapper")
public class MybatisConfig {
}
如果mapper xml文件和mapper接口在相同的路径下,那么MapperFactoryBean会自动去寻找和绑定对应的Mapper,此时就没有必要在mybatis配置文件中指明mapper xml文件的路径了。事实上还有另外一种方式来指定mapper xml文件的路径,即在SqlSessionFactoryBean中配置
<bean id = “sqlSessionFactory” class = “org.mybatis.spring.SqlSessionFactoryBean”>
<property name = “dataSource” ref = “dataSource”/>
<property name = “mapperLocation” value = “classpath:com/example/mapper/*.xml”/>
</bean>
这样,我们可以将mybatis配置文件也去除了,除非你需要配置类别名。
更多关于mybatis-spring的使用信息,可参考官网http://www.mybatis.org/spring/getting-started.html
原理分析
1. SqlSessionFactoryBean
SqlSessionFactoryBean实现了FactoryBean<SqlSessionFactory>接口,其getObject()方法返回的是一个DefaultSqlSessionFactory实例。这意味着我们可以从Spring容器中获取两个类型的bean:SqlSessionFactoryBean和SqlSessionFactory。关于Spring的BeanFactory在获取bean时如何处理FactoryBean,可以参考https://blog.youkuaiyun.com/zknxx/article/details/79572387和https://blog.youkuaiyun.com/zknxx/article/details/79588391。使用FactoryBean的好处是,我们可以定制bean实例的创建过程;如,实例化SqlSessionFactory是较为复杂的,需要配置大量的信息,但是如果采取编码的方式,在SqlSessionFactoryBean中封装其实例化细节,问题就变得简单多了。
SqlSessionFactoryBean还实现了InitializingBean接口,创建DefaultSqlSessionFactory实例的逻辑:
this.sqlSessionFactory = buildSqlSessionFactory()
在接口方法afterPropertiesSet()中被调用,即在SqlSessionFactoryBean bean创建过程中会初始化sqlSessionFactory属性,并将这个属性作为getObject()的返回。我们看看buildSqlSessionFactory()方法:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
// 待填充的一个Configuration变量,包含mybatis的所有配置,用作DefaultSqlSessionFactory构造器参数
// 三种填充方式:通过SqlSessionFactoryBean注入、从XML配置建造器获取、默认配置new Configuration()
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
// 通过SqlSessionFactoryBean注入
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);
}
}
// 从XML配置建造器获取
else if (this.configLocation != null) {
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);
}
}
// mybatis用来创建对象的工厂类,用户可自定义并注入
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
// 用来创建ObjectWrapper的工厂类。ObjectWrapper用于包装对象,可以是bean和集合;提供一组便于获取对象元信息的API
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
// vfs,提供一组方便操作类和资源的API
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
// 类别名
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
// 装载所有插件
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
// 装载TypeHandler。TypeHandler用于数据库查询结果集和Java对象之间的转换
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
// 装载DatabaseIdProvider。DatabaseIdProvider用于根据Datasource对象返回一个id,
// 这个id用于为不同的数据库类型创建不同的查询sql
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
// 装载二级缓存
if (this.cache != null) {
configuration.addCache(this.cache);
}
// 如果mybatis配置文件存在,对其进行解析,写进Configuration
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();
}
}
// 如果未创建事务工厂,则使用Spring的事务工厂
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
// Environment维护事务工厂、数据源以及一个值为“SqlSessionFactoryBean”的id
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
// 解析mapper文件
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
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");
}
}
// 构造SqlSessionFactory实例,实际返回的是DefaultSqlSessionFactory
return this.sqlSessionFactoryBuilder.build(configuration);
}
可以看出,SqlSessionFactoryBean存在的意义在于提供一个让用户配置mybatis的入口。
2. @MapperScan和MapperScannerConfigurer
@Mapper通过MapperScannerRegistrar来实现跟MapperScannerConfigurer同样的效果:扫描指定package下的接口,创建MapperFactoryBean。两者内部都是借助ClassPathMapperScanner进行接口的扫描和bean注册。
MapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,该接口用于Spring在处理Configuration类时向容器注册额外的bean definition。
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取@MapperScan注解的属性信息
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
// 核心组件,用于扫描指定路径下的接口,将其注册为bean definition
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
// 下面开始将注解中标记的信息注入ClassPathMapperScanner
// 设置注解类,用作扫描过滤器的过滤条件之一
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
// 设置标记接口,用作扫描过滤器的过滤条件之一
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
// bean名称生成器
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
// 设置MapperFactoryBean实例
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
// 将注册的sqlSessionTemplate bean的名称传给scanner
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
// 将注册的sqlSessionFactoryRef bean的名称传给scanner
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
// 注册扫描过滤器,根据annotationClass和markerInterface进行过滤,
// 如果两者都未设置,则扫描package下的所有class
scanner.registerFilters();
// 执行扫描
scanner.doScan(StringUtils.toStringArray(basePackages));
}
扫描Mapper接口并注册对应的bean到Spring容器,核心是ClassPathMapperScanner,继承自ClassPathBeanDefinitionScanner,该类用于扫描和检测classpath下的bean candidates,将对应的bean definetion注册到registry(BeanFactory或者ApplicationContext)中,默认的扫描过滤器包含@Component、@Repository、@Service和@Controller等注解标注的类。ClassPathMapperScanner并不使用默认的过滤器,并自己定义了一套过滤规则。
接着看ClassPathMapperScanner的doScan方法
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
调用了ClassPathBeanDefinitionScanner的doScan方法,其中的细节不在此延伸,总之返回了一个集合,包含指定package下的所有bean definition。我们看看Mybatis-Spring接着对这些bean definition做了什么处理
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
// Mapper接口class作为MapperFactoryBean的构造器参数
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
// 将bean class重新设置为MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
// 设置MapperFactoryBean的addToConfig属性
definition.getPropertyValues().add("addToConfig", this.addToConfig);
// 将sqlSessionFactory和sqlSessionTemplate bean注入到MapperFactoryBean。
// 注意,MapperFactoryBean继承自SqlSessionDaoSupport
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
至此,已经完成了为每一个Mapper接口注册一个对应的MapperFactoryBean。MapperFactoryBean同样实现了FactoryBean接口,接着看其getObject方法究竟创建了一个怎样的实例。
// MapperFactoryBean
@Override
public T getObject() throws Exception {
// 最终调用Configuration.getMapper方法
// 泛型T是MapperFactoryBean对应的接口类型,最终返回的是一个动态代理类实例
return getSqlSession().getMapper(this.mapperInterface);
}
不要忘了Configuration实例是在什么时候被组装的:在SqlSessionFactoryBean的afterPropertiesSet方法里。Configuration中维护了一个MapperRegistry实例(Configuration和MapperRegistry互为构造器参数),用来储存所有注册的Mapper,根据接口类型和sqlSession对象从中获取Mapper实例。
// Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
那么Mapper是何时被添加到MapperRegistry中的呢?有三处:
1,如果在mybatis的xml配置文件中创建了mappers节点,那么在通过XmlConfigBuilder解析配置文件时会注册所有mapper。
// SqlSessionFactoryBean.buildSqlSessionFactory
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
// XmlConfigBUilder
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");
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.");
}
}
}
}
}
2,指定了SqlSessionFactoryBean的mapperLocations时,会调用XMLMapperBuilder来解析mapper文件,将mapper注册到configuration中
// SqlSessionFactoryBean.buildSqlSessionFactory
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
xmlMapperBuilder.parse();
// XMLMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
3, MapperFactoryBean实现了Spring的DaoSupport接口,在创建MapperFactoryBean时会将对应的Mapper接口注册到Configuration中
// MapperFactoryBean
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
重点看看Configuration维护的MapperRegistry属性在添加Mapper时做了些什么事情
// MapperRegistry
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 将已经加载的接口类型放入一个map中
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 我们除了在xml中配置Mapper,还可以通过注解在接口方法中标注。
// 这里就是为了处理这些通过注解配置的Mapper,其作用等同于XmlMapperBuilder
// 核心的用途在于解析注解中配置的sql和resultmap等,生成MappedStatement放入
// Configuration中,id为“接口名.方法名"
// 同时也会使用XmlMapperBuilder解析同目录下的同名xml文件
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
// MapperAnnotationBuilder
private void loadXmlResource() {
// Spring may not know the real resource name so we check a flag
// to prevent loading again a resource twice
// this flag is set at XMLMapperBuilder#bindMapperForNamespace
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
我们回到MapperRegistry的getMapper方法,从这里能获取到什么呢?
// MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据Mapper接口类型取出对应的MapperProxyFactory实例,注意,这个实例是在addMapper时添加进去的
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 每次getMapper会从对应的mapperProxyFactory中获取一个新的MapperProxy实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
// MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
// 通过jdk的动态代理创建一个Mapper接口类型的代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
// 创建一个MapperProxy对象, MapperProxy实现了InvocationHandler接口
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
MapperProxy实现了InvocationHandler接口,所有对Mapper代理对象的调用都会被托管到MapperProxy的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是从Object对象继承的方法,则直接通过反射本MapperProxy实例调用
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// 如果是接口中的default方法,则通过MethodHandle调用
else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 对Mapper接口方法的调用,全都转嫁到这里啦。将由Executor来执行绑定的MappedStatement
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
至此我们已经进入Mybatis的核心逻辑(在此之前的步骤是mybatis与Spring集成的情况下,以Spring的方式来优雅地配置mybatis),我将在后续文章中继续解读mybatis的核心组件源码。