從源碼的角度解讀Spring中 Mybatis 的執行流程

在 spring 項目中不僅要學習 mybatis 還要學習 mybatis 是如何與 spring 完美結合的,這就要歸功於 mybatis-spring.jar。今天不僅要學習 mybatis 還要學 如何與 spring 完美結合。

 首先回顧一下 mybatis 的執行流程:

//mybatis的配置文件
String resource = "hong/helloWorld/Configuration.xml";
Reader reader = Resources.getResourceAsReader(resource);
//通過SqlSessionFactoryBuilder創建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//獲取 sqlsession
SqlSession session = sqlSessionFactory.openSession();
//執行 sql
List<User> userList =sqlSession.selectList("com.dao.UserMapper.selectUser");//selectUser爲方法名
//關閉 sqlSession
sqlSession.close();


2、

mapper接口和 sql 語句書寫方式:
  1. mapper 接口和 sql語句分開,sql語句寫在 mapper.xml文件中;
  2. 通過@Select、@Delete、@Update、@Insert 註解把sql 直接寫在 mapper 接口裏方法上;
  3. 混搭方式,一部分寫在 xml 文件中,一部分寫在方法上。

不管是那一種方式,最後應該是殊路同歸。

 

2.1、sql語句在 xml 文件中:

@Configuration
public class MybatisAutoConfiguration {

    //創建 SqlSessionFectory
    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource)  {
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
       	       ……
        return factory.getObject();
    }

}

SqlSessionFactoryBean實現了 FactoryBean 接口,通對getObject 方法提供 SqlSessionFactory。

getObject方法中還做了哪些工作呢?

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean{
  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }

  @Override
  public void afterPropertiesSet() throws Exception {
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;
   //接下來就是讀取mybatis的配置文件,將配置文件中的信息封裝在Configuration中。
    ……
	 
   //解析mapper.xml
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {//遍歷所有的mapper.xml
        try {
          //把mapper.xml封裝成xmlMapperBuilder對象
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
	  //解析xmlMapperBuilder對象
          xmlMapperBuilder.parse();
        } catch (Exception e) {} 
      }
    }
    //通過sqlSessionFactoryBuilder創建sqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(configuration);
  }
}

mapper.xml文件被讀取後創建了XMLMapperBuilder對象,再通過parse方法解析。

下面是看一下XMLMapperBuilder如何解析?

public class XMLMapperBuilder extends BaseBuilder {
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      bindMapperForNamespace();
    }
  }
  
  private void bindMapperForNamespace() {
    //獲取mapper.xml文件中的namespace
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
	//namespace就是Mapper.java 接口類的全類名,所以通過nameSpace獲取Mapper接口的class。
        boundType = Resources.classForName(namespace);
      } catch () {}
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          configuration.addLoadedResource("namespace:" + namespace);
	  //將接口類添加到configuration中。
          configuration.addMapper(boundType);
        }
      }
    }
  }
}

將接口類添加到configuration中,又做了那些操作呢?

public class Configuration {
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

  public <T> void addMapper(Class<T> type) {
    //mapper接口被放入了Mapper註冊中心
    mapperRegistry.addMapper(type);
  }
}

mapper接口被放入了Mapper註冊中心,Mapper註冊中心又幹了那些事?

public class MapperRegistry {
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

    public <T> void addMapper(Class<T> type) {
      if (type.isInterface()) {
        //以接口類爲key,MapperProxyFactory爲value,存放在knownMappers 中。
        knownMappers.put(type, new MapperProxyFactory<T>(type));
     }
  }

}


 

Mapper註冊中心內部維護了一個 map集合,map 集合中 key 爲接口類的 class,value 爲 MapperProxyFactory對象。

sql語句在 xml 文件中做了哪些事:

  1. 創建了SqlSessionFactory對象
  2. 通過 xml 中的 nameSpace 找到接口類,並註冊到Mapper註冊中心。

2.2、sql 語句在方法上

上面說到 sql 語句在 mapper.xml中時,能通過 nameSpace 找到接口類。現在sql 語句在方法上意味這沒有 xml 和 nameSpace。這時有兩種方式:

  1. 在接口類上添加@mapper 註解
  2. 在 springBoot的啓動類上添加@MapperScan註解

使用@Mapper註解需要在每個接口類上加上@Mapper註解,比較麻煩,解決這個問題就用@MapperScan,它可以掃描 包下面的所有接口類。

下面演示@mapper註解的方式:

//Import註解導入AutoConfiguredMapperScannerRegistrar
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration {

}
public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    private BeanFactory beanFactory;
    private ResourceLoader resourceLoader;


   /**
    *    這是對ImportBeanDefinitionRegistrar接口的實現。
    *    參數BeanDefinitionRegistry是BeanDefinition註冊中心,通過下面的方式可以向spring中注入bean。
    *    例:
    *    BeanDefinition beanDefinition = new GenericBeanDefinition();
    *    beanDefinition.setBeanClass(Person.class);//自定義的Person類
    *    registry.registerBeanDefinition("person", beanDefinition);//註冊一個名字叫person的bean
    */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      //搜索帶有@Mapper註釋的映射器
      logger.debug("Searching for mappers annotated with @Mapper");
      //創建Mapper的掃描器
      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
		
      try {
        //設置資源加載器
        if (this.resourceLoader != null) {
          scanner.setResourceLoader(this.resourceLoader);
        }
        //獲取要掃描的目錄,就是Spring掃描我們工程的路徑,簡單說就是我們的整個項目路徑。
        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        //設置要掃描的文件特徵,帶有@Mapper註解的文件。
        scanner.setAnnotationClass(Mapper.class);
        scanner.registerFilters();
        //開始掃描
        scanner.doScan(StringUtils.toStringArray(packages));
      } catch () {
      }
    }
  }

 

 BeanDefinitionRegistry和要掃描的包packages都給了ClassPathMapperScanner。

下面就看一下ClassPathMapperScanner是如何掃描的:

 public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider {
 
	  @Override
	  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
		//調用父類的doScan方法
		Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

		if (beanDefinitions.isEmpty()) {
		  logger.warn("No MyBatis mapper was found ");
		} else {
		  //處理beanDefinitions
		  processBeanDefinitions(beanDefinitions);
		}
		return beanDefinitions;
	  }
	 //父類的doScan方法 就直接寫在這了
 	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				……
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					……
					//註冊BeanDefinition
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
	//註冊BeanDefinition
	protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
		
		BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
		/**
		* public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry){
		*	String beanName = definitionHolder.getBeanName();
		* 	//在BeanDefinition註冊中心註冊bean
		*	registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
		* }
		*/
	}
	//處理beanDefinitions
	private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
		GenericBeanDefinition definition;
		for (BeanDefinitionHolder holder : beanDefinitions) {
		  definition = (GenericBeanDefinition) holder.getBeanDefinition();
			……
		  definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 
			……
		  //修改beanDefinition中的beanClass爲MapperFactoryBean
		  definition.setBeanClass(this.mapperFactoryBean.getClass());
			……
		}
   }
}

ClassPathMapperScanner中主要完成了,將接口類注入到BeanDefinitionRegistry,這裏值的注意的是:這裏的BeanDefinition是的 beanCalss 被修改成了 MapperFactoryBean,這意味這我從 Spring 容器中獲取接口類對象時 會獲取MapperFactoryBean,又因爲MapperFactoryBean實現了 FactoryBean 接口,最終獲取MapperFactoryBean.getObject()方法返回的對象。

Spring 容器創建 MapperFactoryBean:

public abstract class DaoSupport implements InitializingBean {
	//對InitializingBean接口的實現,bean 創建後由spring回調。
	@Override
	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		
		checkDaoConfig();
		……
	}
	protected abstract void checkDaoConfig() throws IllegalArgumentException;
	

}
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  //構造方法,傳入 Mapper 接口
  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  //如果configuration中沒有mapper接口,將其添加到configuration中。
  @Override
  protected void checkDaoConfig() {
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch () {
      } finally {
      }
    }
  }

  //對FactoryBean接口的實現,獲取MapperFactoryBean工廠bean提供的對象。
  //在這裏就是獲取@Mapper修飾的接口的實現類,這個實現類是jdk動態代理生成的。
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
}


 MapperFactoryBean 實現了 InitializingBean接口在構造方法執行後會執行,afterPropertiesSet方法中調用了configuration.addMapper(this.mapperInterface) 最終將mapper接口類註冊到Mapper註冊中心。

sql 語句在 xml 文件中還是在方法上最終mapper接口類都要註冊到Mapper註冊中心


getObject:

  //對FactoryBean接口的實現,獲取MapperFactoryBean工廠bean提供的對象。
  //在這裏就是獲取@Mapper修飾的接口的實現類,這個實現類是jdk動態代理生成的。
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

getSqlSession返回的是SqlSession的實現類SqlSessionTemplate。

class SqlSessionTemplate{
  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
}
public class Configuration {
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
}
public class MapperRegistry {

 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch () {
    }
  }
}

 

SqlSessionTemplate的 getMapper 方法 ==>Configuration類的 getMapper方法==>MapperRegistry的getMapper方法。

MapperRegistry就前面一直說的 Mapper 註冊中心,通過 Mapper 註冊中心獲取MapperProxyFactory---mapper 代理工廠,然後調用mapper 代理工廠的 newInstance方法獲取代理對象。

newInstance方法是如何獲建代理對象的:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();


  protected T newInstance(MapperProxy<T> mapperProxy) {
	//創建jdk動態代理對象:Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){……}
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
	//MapperProxy是對InvocationHandler接口的實現。 
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
public class MapperProxy<T> implements InvocationHandler {

  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  //對InvocationHandler接口的實現
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		……
		
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

 被@Mapper修飾的接口a,Spring會創建一個beanName爲a的MapperFactoryBean,MapperFactoryBean爲FactoryBean,getObject方法返回的對象纔是真實的a的實現對象;getObject方法會調用MapperProxyFactory的newInstance方法,newInstance通過jdk代理的方式生成a的實現類。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章