首先mybatis 有两个关键的类SqlSessionFactoryBean和MapperScannerConfigurer
先简单的说一下它们的作用:
SqlSessionFactoryBean :根据mapper.xml生成代理对象,和创建mapperstatement对象
MapperScannerConfigurer:扫描mapper.java文件生成实例注入spring容器。
目录
SqlSessionfactoryBean 生成 mapperStatement 对象
MapperScannerConfigurer 扫描注入spring
接下来再详细看看它们是如何和做到的:
SqlSessionFactoryBean 生产代理对象
SqlSessionFactoryBean实现了 InitializingBean 接口并重写了afterPropertiesSet()方法,意思就是,在spring初始化的某个阶段会调用此方法,去构建SqlSessionFactory。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
-------------------------------------------------------------------------------------------------------------------------------
然后再来说说整个buildSqlSessionFactionry()方法的逻辑,就是根据全局配置文件,找到对象的Mapper.xml文件,并根据namespace生产代理对象,底层原理就是动态代理及反射机制。根据spring注入的configLocation找到全局配置文件并生成configuration对象。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:context/sql-config.xml" />
<property name="dataSource" ref="DataSource" />
</bean>
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
XMLConfigBuilder xmlConfigBuilder = null;
Configuration configuration;
//根据configuration获取configuration对象
if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (logger.isDebugEnabled()) {
logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
xmlConfigBuilder.parse() 解析configuration对象。
if (xmlConfigBuilder != null) {
try {
//解析configuration对象
xmlConfigBuilder.parse();
if (logger.isDebugEnabled()) {
logger.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception var23) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var23);
} finally {
ErrorContext.instance().reset();
}
}
解析configuration节点。
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
//解析全局文件的/configuration节点
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
解析mapper节点。
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.settingsElement(root.evalNode("settings"));
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
//解析mapper节点
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
解析mapper得到namespace。
public void parse() {
if (!this.configuration.isResourceLoaded(this.resource)) {
this.configurationElement(this.parser.evalNode("/mapper"));
this.configuration.addLoadedResource(this.resource);
this.bindMapperForNamespace(); //解析namespace
}
this.parsePendingResultMaps();
this.parsePendingChacheRefs();
this.parsePendingStatements(); //解析sql语句节点
}
根据mapper.xml里面的namespace路径拿到类对象(反射机制)
private void bindMapperForNamespace() {
String namespace = this.builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class boundType = null;
try {
boundType = Resources.classForName(namespace); //解析根据namespace拿到类对象
} catch (ClassNotFoundException var4) {
;
}
if (boundType != null && !this.configuration.hasMapper(boundType)) {
this.configuration.addLoadedResource("namespace:" + namespace);
this.configuration.addMapper(boundType);//添加到configuration的map里
}
}
}
addMapper方法,添加的是proxy对象。添加到configuration对象 knowMappers(它是一个hashmap)里面
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));//添加的是mapperpoxyfactory对象。
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
我们再来看看这个MapperProxyFactory类,前面说了mybatis创建代理对象用的是动态代理可以看到 newInstance()方法,代理逻辑就是对sqlsession的api调用,sqlsession底层就是对excutor对象的调用,此处就不展开说了。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return this.mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return this.methodCache;
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//动态代理
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
MapperMethod mapperMethod = this.cachedMapperMethod(method);
//调用sqlsesiion的api 去执行sql
return mapperMethod.execute(this.sqlSession, args);
}
}
SqlSessionfactoryBean 生成 mapperStatement 对象
生成mapper对象的关键代码parsePendingStatements(),它会去扫描mapper.xml文件下面的sql标签节点去解析
private void parsePendingStatements() {
Collection<XMLStatementBuilder> incompleteStatements = this.configuration.getIncompleteStatements();
synchronized(incompleteStatements) {
Iterator iter = incompleteStatements.iterator();
while(iter.hasNext()) {
try {
((XMLStatementBuilder)iter.next()).parseStatementNode(); //解析节点
iter.remove();
} catch (IncompleteElementException var6) {
;
}
}
}
}
根据mapper的namespace 加sqlid拼接key 并添加到configuration的mapperstatement里,由于key是namespace + sqlid 所以,mybatis不存在方法重载。
//代码已删减
public void parseStatementNode() {
String id = this.context.getStringAttribute("id");
String databaseId = this.context.getStringAttribute("databaseId");
//databaseIdMatchesCurrent()方法 ,拼接key
if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
this.context, parameterTypeClass);
String resultSets = this.context.getStringAttribute("resultSets");
String keyProperty = this.context.getStringAttribute("keyProperty");
String keyColumn = this.context.getStringAttribute("keyColumn");
String keyStatementId = id + "!selectKey";
keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
//放进configuration的mapperstatement里
this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
MapperScannerConfigurer 扫描注入spring
接下来我们说说MapperScannerConfigurer,
MapperScannerConfigurer 实现了 BeanDefinitionRegistryPostProcessor,也就是说在注册beandifinition的时候会调用 postProcessBeanDefinitionRegistry()方法。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
spring配置
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.*.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
//扫描在spring注入的包下面mapper.java类
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (this.processPropertyPlaceHolders) {
this.processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
//扫描在spring注入的包
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
然后看看scan方法,它做了哪些事情,设置bean的class对象,这里设置了MapperFactoryBean对象,此对象实现了FactoryBean类,重写了getObject();
GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
//关键:
//设置bean的class对象,这里设置了MapperFactoryBean对象,此对象实现了FactoryBean类,重写了getObject();
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//注入sqlsessionfactory
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;
}
我们来看看getObject()方法
public T getObject() throws Exception {
//调用sqlsession拿去mapper
return this.getSqlSession().getMapper(this.mapperInterface);
}
最终它会去configuration对象里面的knownMappers拿到MapperProxyFactory 并调用 newInstance 方法实例化这个代理对象。这里对newInstance就是上面SqlSessionFactoryBean说的动态代理创建代理对象,然后放进spring容器里。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
//实例化代理对象。
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
总结:
SqlSessionFactoryBean 扫描全局文件,根据全局文件里的配置找到mapper.xml,然后根据mapper.xml的namespace 创建代理对象,根据mapper.xml的sql节点创建mapperstatement对象,两者都保存在configuration的hashmap里。
MapperScannerConfigurer 根据配置扫描mapper.java类,并对它们的beandifition进行修改,将BeanClass换成了MapperFactoryBean,然后这样就可以获得代理对象,并注入spring容器。
这里由于篇幅的关系,没有展开讲executor底层、动态sql、还有mybatis的缓存的一二级缓存,有兴趣的同学可以更深入的去挖掘一下。