最近打算走读一下mybatis源码,先通过spring+mybatis的项目来了解mybatis的整个框架。
整个流程以下三个部分来:
1. spring项目中mybatis实例化过程
上图描述了spring项目中,mybatis-spring中的sqlSessionTemplate实例化过程。sqlSessionTemplate它管理 session 的生命周期,包含必要的关闭,提交或回滚操作。保证当前sqlSession与spring当前事务是相关的。sqlSessionTemplate也是sqlSession接口的实现类。作为模版类,通过操作sqlsession其它的实现类完成一些功能,在此基础上做了封装。后续也会讲到,sqlsession实现类图如下:
sqlSessionTemplate是通过sqlSessionFactory来为当前线程产生sqlSession。sqlSessionFactory只是个接口。它的实现是DefaultSessionFacotry。正如图中所示就是DefaultSessionFacotry实例化过程,它是单例模式。sqlSessionFactory的实例过程,是通过工厂bean:sqlSessionFactoryBean来产生。在sqlSessionFactoryBean实例化时,通过解析mybatis配置文件,和mapper配置文件来生成configuration实例,然后传递到Builder类通过build模式生成。核心代码如下:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
//通过XMLConfigBuilder解析configLocation的文件并构建出Configuration实例,
//Configuration几乎包含了所有mybatis配置,并且会注入到每个重要的mybatis类中。
//因为它的配置会在很多类中不断的被用到。
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) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(),
null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
}
//.......省略......
//通过XMLMapperBuilder解析mapperLocations并将mapper.xml文件中的信息都保存至configuration中
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();
} //.......省略.....
//最后通过sqlSessionFactoryBuilder产生DefaultSessionFacotry实例。
return this.sqlSessionFactoryBuilder.build(configuration);
}
解析mapper.xml文件过程中,会把每一个mapper.xml文件中的方法以及该方法返回和参数信息都保存。通过一个map的形式存储,后续mapper映射器执行的时候可以定位到mapper.xml文件中的sql。解析过程可参考下面的方法:
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
map结构:key是mapper.xml文件的namespace + “.” + methodName,value是一个MappedStatement.
MappedStatement属性如下:描述了mapper.xml文件中方法的所有属性。
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource; //sql语句
private Cache cache;
private ParameterMap parameterMap; //参数map
private List<ResultMap> resultMaps; //mapper.xml中的resultMap
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType; //sql操作类型,insert/select/delete/update
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
有趣的是sqlSessionTemplate返回SqlSession实例的方式也有点特别。它不是直接通过sqlSessionFactory工厂类来产生sqlSession。而是通过它内部一个属性sqlSessionProxy来返回。sqlSessionProxy是一个代理类,并且设置了拦截器SqlSqlsessionInterceptor。拦截器内部完成了sqlSessionTemplate职责中的事情–统一管理sqlsession生命周期。
sqlSessionProxy实例化代码:
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
SqlSessionInterceptor内容:当sqlsession执行相关方法的时候就会被拦截
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//通过sqlSessionFactory获取SqlSession实例,深入内部可以发现如果有事务则会返回这个事务相关的session
//否则会new一个DefaultSqlSessionFactory实例。
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) {
.....sqlsession统一处理的操作
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
2. mapper映射器创建过程
spring项目中,在配置mybatis时,会配置MapperScannerConfigurer,实例化时会扫描做扫描,实例化所有被注解了MybatisDao的mapper映射器接口
//MapperScannerConfigurer配置示例:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<property name="basePackage" value="com.youzan.sz.beautystore.**.dao"/>
<property name="annotationClass" value="com.youzan.beauty.helper.mybatis.MybatisDao"/>
</bean>
//实例化时扫描的代码
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
//....
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));//扫描注解,实例化mapper接口。
}
我们代码中注入的mapper接口,没有实现类,其实它的实现是一个代理类。在扫描过程中会生成mapper接口的代理类注入。在扫描过程中,会调用MapperFactoryBean.getObject方法来生成mapper代理类。getObject内部时通过
sqlSessionTemplate .getMapper方法获取
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
如流程图所示。深入代码,发现mapper代理类会通过sqlSessionFactory.getConfiguration.getMapper最后在 MapperRegistry.getMapper内部获取到mapperProxyFactory代理工厂类,然后通过这个工厂类生成Mapper的代理类
//MapperRegistry.getMapper方法核心代码
@SuppressWarnings("unchecked")
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.newInstance代码就是生成一个代理类,然后拦截器是MapperProxy实例
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);
}
问题是mapperProxyFactory是什么时候被设置到Configuration下的MapperRegistry中knownMappers属性中的?回溯到原来Configuration的实例化和设置的过程可以逐渐解释。getMapper的参数是mapper接口的完整类名,例如:com.youzan.sz.beautyshop.message.dao.ShopMessageConfigDAO。knownMappers中的值是在XMLMapperBuilder.parse解析mapper.xml文件时被设置。
//org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace(); //获取到mapper.xml中的namespace全名
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);//通过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);//设置的关键代码,可以进入下一个代码段看到下层代码
}
}
}
}
//org.apache.ibatis.session.Configuration#addMapper方法
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
//mapperRegistry.addMapper方法的代码便是设置knownMappers
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 {
//key是class,value是一个MapperProxyFactory
knownMappers.put(type, new MapperProxyFactory<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
好,一切解释通了。mapper代理类生成过程在此结束。接下来就是当mapper接口调用某一方法时,查找sql并返回结果。
3. mapper映射器执行过程
当mapper接口调用某一方法时,会被MapperProxy拦截
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); //通过操作类型最后会触发sqlsession中的方法
}
以查询列表的方法sqlsession.selectList为例,最后会执行executor的query方法:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//在第一部分中有讲到configuration中有一个map对象,会存储mapper.xml文件中的每一个方法,
//和方法对应的出参入参相关对象MappedStatement,这里取出来之后,就相当取出了整个方法的sql语句和属性。
//最后执行
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
最后配合statement处理器,参数处理器,结合jdbc操作返回结果,通过结果处理器,映射成java结果对象返回,一切完成。后面将继续深入其中细节~~未完待续。