mybatis中所有Dao接口的实现类都是MapperProxy
问题
问题描述:在使用mybatis时,我们只定义了接口,然后在XxxMapper.xml中写sql,若使用Mapper接口,甚至连XxxMapper.xml都可以省略,这一切究竟是如何实现的呢?
大多数项目都会使用spring+mybatis来搭建,本文参照mybatis-spring(1.2.0)分析上述问题。
将mybatis集成到spring中,添加配置spring-dao.xml
上述配置文件为mybatis的入口,阅读源码首先要找到入口,不能漫无目的地在源码里闲逛。这里需要Spring的一点基础知识(例如FactoryBean的作用)。与Spring的集成,本质是是把自己扮成Spring的bean,然后Spring能感知到你的存在,从而对你进行管理。咱们可以看到,mybatis在这里所有的配置信息,都是在
这三个bean中,咱们挨个分析。
- id为dataSource的bean,最简单。只是封装了一些数据库的连接(包括配置)信息在里面。
- id为sessionFactory的bean,定义了SqlSessionFactoryBean,将dataSource bean作当做了一个参数。暂且不管,继续。
- 最后一个bean,可以看到,没有定义id,说明没有人显示地引用它。其实它就是spring-mybatis的入口。参数basePackage 是我们的Dao接口包,用于后面接口扫描使用。后一个参数sqlSessionFactoryBeanName,只是简单地给他赋了值。
咱们现在已经知道spring-mybatis的入口了,开干吧。
以下的分析内容,可以认为是在对image1的解释。
进入MapperScannerConfigurer,
BeanDefinitionRegistryPostProcessor,
InitializingBean,
ApplicationContextAware,
BeanNameAware
发现其实现了这四个接口。又是Spring的知识了,这些都是spring的扩展点,不多做介绍。重点看,BeanDefinitionRegistryPostProcessor。这个接口里只有一个方法postProcessBeanDefinitionRegistry,会在BeanDefinition加载完之后执行。进入该方法:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//ignore unrelated code
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
忽略非重点代码。省略的代码,是在设置属性。重点看保留下来的最后一句代码。这句话的意思是扫描包。不知各位还记得spring-dao.xml中这么一句话:
那么这里的scan(),扫描了com.foo.dao包下面的所有类,即咱们定义的DAO接口。根据scan()方法层层推进,来到了,org.mybatis.spring.mapper.ClassPathMapperScanner#doScan()。这个方法里有三句话比较重要:
① definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
② definition.setBeanClass(MapperFactoryBean.class);
③ definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
①:将我们自定义的DAO接口作为参数放入BeanDefinition中,属性名为mapperInterface;
②:设置BeanDefinition的BeanClass,MapperFactoryBean。这里对于文章开头“mybatis中所有Dao接口的实现类都是MapperProxy”的这句话,已初见端倪;
③:新增属性名为sqlSessionFactory的属性。
从这里可以看出两点:
- 咱们自定义的DAO接口,被新增了两个属性(mapperInterface与sqlSessionFactory),那么在创建bean的时候,会给这两个属性赋值,即调用这两个属性的set方法;
- DAO接口BeanDefinition的BeanClass是MapperFactoryBean。并且细心的小伙伴发现,这是一个工程bean(FactoryBean),在创建bean时,是从其getObject()方法获取真正的bean。
BeanDefinition加载完后,开始创建bean。
开始image2的分析:
由于我们自定义DAO接口的BeanClasss是MapperFactoryBean,所以创建的是MapperFactoryBean,遂进入MapperFactoryBean源码。
上文咱们提到,会调用sqlSessionFactory的set方法(setSqlSessionFactory)。发现该方法在MapperFactoryBean的父类SqlSessionDaoSupport中,进入:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
层层跟进,直至最底层构造方法:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
此处咱们看到了jdk动态代理(SqlSessionInterceptor),这也是咱们image2中的标注的第一次动态代理。跟进:
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
与数据库的操作都是通过SqlSession来完成的。这里咱们看到生成了sqlSession,然后根据具体情况,来判断是否自动提交。
setSqlSessionFactory()方法到这算基本讲完了,新建了一个SqlSessionTemplate,回到MapperFactoryBean。
由于MapperFactoryBean是FactoryBean的实现类,所以会调用其getObject()方法来获取真正的bean。进入该方法:
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
无实际内容,重点内容都在getMapper()里面。by the way,这里的mapperInterface,咱们在image1里有提到,若遗忘,往上翻。由于咱们刚才通过setSqlSessionFactory()方法,新建了一个SqlSessionTemplate,所以这里通过SqlSessionTemplate#getMapper()层层跟进,直至出现明显有特殊意义的代码,到了MapperRegistry#getMapper():
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) 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.newInstance(sqlSession):
protected T newInstance(MapperProxy mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
这两个方法,其实又是一次动态代理(image2的第二次动态代理)。
回到getMapper(),最后返回了MapperProxy 对象。
通过image2的分析,咱们知道,我们自定义的DAO接口,最后被封装成了MapperProxy。当我们在调用DAO接口里面的方法时,实际是调用了MapperProxy#invoke()。
进入MapperProxy的invoke()方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
这里通过动态代理获取到了MapperMethod方法,然后开始execute。进入:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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;
}
这一大段代码基本都是比较熟悉的代码。根据SqlCommandType判断是增删改查的哪一个,然后调用SqlSession对应的方法去执行。请记住,这里的SqlSession已经被动态代理过了,实际上是调用了SqlSessionInterceptor#invoke()方法。
As always and let me know what you think, leave the comments below. Bye :)
项目完整源码github地址