Mybatis Mapper 源码分析

天天都在用的Mybatis,为啥调用一个Mapper接口就能执行SQL,你有没有想过这个问题?

这一切都得从 @MapperScan 这个注解开始说起。打开这个注解定义可以看到:

1
2
3
4
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({MapperScannerRegistrar.class})

上面的元注解中重点关注@Import(MapperScannerRegistrar.class),这是Spring的一个注解,允许导入@Configuration类、 ImportSelector和ImportBeanDefinitionRegistrar实现。很明显MapperScannerRegistrar.class 应该是ImportBeanDefinitionRegistrar的实现:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

private ResourceLoader resourceLoader;

/**
* {@inheritDoc}
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

// this check is needed in Spring 3.1
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}

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);
}

Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}

Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}

scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
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));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}

/**
* {@inheritDoc}
*/
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}

}

可以看到上面代码在做一件事,就是初始化ClassPathMapperScanner,最后调用了doScan

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

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;
}

首先调用了父类的doScan,里面的主要逻辑是查找指定的basePackages下的所有Components,即声明了@Component以及将@Component作为元注解的注解,例如@Repository
感兴趣的可以看看:

  • org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
  • org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(org.springframework.core.type.classreading.MetadataReader)
  • org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#registerDefaultFilters
  • org.springframework.core.type.filter.AnnotationTypeFilter#hasAnnotation
  • org.springframework.core.annotation.AnnotationUtils#getAnnotation(java.lang.reflect.AnnotatedElement, java.lang.Class)

接下来调用了processBeanDefinitions:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());

definition.getPropertyValues().add("addToConfig", this.addToConfig);

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);
}
}
}

重点看看这两行代码:

1
2
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 
definition.setBeanClass(this.mapperFactoryBean.getClass());

似乎已经可以看出点蛛丝马迹,这里将Mapper的BeanDefinition的BeanClass替换为了this.mapperFactoryBean.getClass():

1
private MapperFactoryBean<?> mapperFactoryBean = new MapperFactoryBean<Object>();

并且将原本的BeanClass添加为了构造函数参数:

1
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 

熟悉Spring的应该都知道,Spring加载完BeanDefinition后会通过BeanClass来去实例化Bean,这里的BeanClass被替换为了一个FactoryBean:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

private Class<T> mapperInterface;

private boolean addToConfig = true;

public MapperFactoryBean() {
//intentionally empty
}

public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

/**
* {@inheritDoc}
*/
@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();
}
}
}

/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

/**
* {@inheritDoc}
*/
@Override
public Class<T> getObjectType() {
return this.mapperInterface;
}

/**
* {@inheritDoc}
*/
@Override
public boolean isSingleton() {
return true;
}

//------------- mutators --------------

/**
* Sets the mapper interface of the MyBatis mapper
*
* @param mapperInterface class of the interface
*/
public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

/**
* Return the mapper interface of the MyBatis mapper
*
* @return class of the interface
*/
public Class<T> getMapperInterface() {
return mapperInterface;
}

/**
* If addToConfig is false the mapper will not be added to MyBatis. This means
* it must have been included in mybatis-config.xml.
* <p/>
* If it is true, the mapper will be added to MyBatis in the case it is not already
* registered.
* <p/>
* By default addToCofig is true.
*
* @param addToConfig
*/
public void setAddToConfig(boolean addToConfig) {
this.addToConfig = addToConfig;
}

/**
* Return the flag for addition into MyBatis config.
*
* @return true if the mapper will be added to MyBatis in the case it is not already
* registered.
*/
public boolean isAddToConfig() {
return addToConfig;
}
}

可以看到之前那个被注册的构造函数参数应该就是在这被使用的:

1
2
3
 public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}

Spring 关于这个Bean构造函数的处理:

1
2
3
4
5
6
7
8
9
  org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#autowireConstructor
// Need to determine the constructor...
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}

总之,Spring通过构造函数创建了一个 MapperFactoryBean 对象。接下来Spring去初始化这个Bean,Spring对FactoryBean有特殊处理,实际会调用getObject方法返回具体的Bean。
感兴趣的可以看:

1
org.springframework.beans.factory.support.FactoryBeanRegistrySupport#doGetObjectFromFactoryBean

这下又回到了MapperFactoryBean中,来看看getObject:

1
2
3
4
5
6
7
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

调用了父类getSqlSession()方法获得SqlSession,然后调用getMapper,传入了当前实际的Mapper。通过:

  • org.apache.ibatis.session.SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)
  • org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource

可以知道SqlSession实际的类型应该是DefaultSqlSession,看看getMapper:

1
2
3
4
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}

又调用了configuration的getMapper,configuration初始化可以看看org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
我们写的SQL就是在这个里面被解析的。到这里已经基本很清晰了。接着看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//configuration中的getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

//MapperRegistry中的getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}

关键就是 MapperProxyFactory了,这个是在 org.apache.ibatis.binding.MapperRegistry#addMapper 时会为每个Mapper绑定一个。
看newInstance:

1
2
3
4
5
6
7
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

也就是说最终Mapper注册到BeanFactory中的是一个代理类MapperProxy,我们知道被代理的方法最终都会被转发到代理类的invoke方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

如果不是Object类的方法,也不是Mapper接口的default(默认)方法,那么调用的是mapperMethod.execute(sqlSession, args):

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
33
34
35
36
37
38
39
40
41
42
43
44
45
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}

到这就差不多了吧?后面已经没啥好说了,基本就是通过statement name找到MappedStatement然后去执行sql了。

另外一个小知识,Mybatis是怎么在插入之后返回自增主键的?答案就在:

  • org.apache.ibatis.executor.statement.PreparedStatementHandler#update
  • org.apache.ibatis.executor.keygen.KeyGenerator#processAfter

写在最后

Spring代码实在太难看了,一环套一环,各种递归,必须debug才能看清。

作者

太阳当空赵先生

发布于

2021-09-22

更新于

2022-02-22

许可协议

评论