spring源码之整合mybatis原理

前言

springmybatis 进行整合时,需要使用以下这个依赖包

<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.5.0</version>
</dependency>
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>2.0.2</version>
</dependency>

spring 容器启动的时候需要使用一个 xml 配置文件

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-spring.xml");

xml 文件中就涉及到整合的配置信息

<!-- dataSource -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <property name="driverClassName" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</bean>

<!-- spring 和 mybatis 整合 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- 指定 mybatis 要连接的数据源 -->
	<property name="dataSource" ref="dataSource"/>
	<!-- mybatis 配置文件的地址 -->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
	<!-- mybatis 的 mapper 文件的地址 -->
	<property name="mapperLocations" value="classpath:com/atguigu/mapper/*.xml"/>
</bean>

<!-- 配置 Mapper 接口扫描 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<property name="basePackage" value="com.atguigu.mapper"/>
	<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

dao 层接口

public interface ProductInfoMapper {
    
    
    int deleteByPrimaryKey(Integer productId);

    int insert(ProductInfo record);

    int insertSelective(ProductInfo record);

    ProductInfo selectByPrimaryKey(Integer productId);

    int updateByPrimaryKeySelective(ProductInfo record);

    int updateByPrimaryKey(ProductInfo record);
}

对应的 mapper.xml 文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.joonwhee.open.mapper.UserPOMapper" >
    <resultMap id="BaseResultMap" type="com.joonwhee.open.po.UserPO">
        <result column="id" property="id" jdbcType="INTEGER" />
        <result column="name" property="name" jdbcType="VARCHAR" />
    </resultMap>
 
    <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from product_info
        where product_id = #{productId,jdbcType=INTEGER}
    </select>
</mapper>

spring 整合 mybatis 原理

1. 类 MapperScannerConfigurer

MapperScannerConfigurer 这个类实现了接口 BeanDefinitionRegistryPostProcessor,会在 spring 构建 IOC 容器的早期被调用重写的 postProcessBeanDefinitionRegistry 方法

参考:spring IOC:invokeBeanFactoryPostProcessors 详解

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    
    
  	if (this.processPropertyPlaceHolders) {
    
    
    	processPropertyPlaceHolders();
  	}
 
  	// 1.新建一个ClassPathMapperScanner并填充相应属性
  	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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  	
  	if (StringUtils.hasText(lazyInitialization)) {
    
    
    	// 2.设置mapper bean是否需要懒加载
    	scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  	}
  	// 3.注册Filter,因为上面构造函数我们没有使用默认的Filter,
  	// 有两种Filter,includeFilters:要扫描的;excludeFilters:要排除的
  	scanner.registerFilters();
  	// 4.扫描basePackage,basePackage可通过",; \t\n"来填写多个,
  	// ClassPathMapperScanner重写了 doScan 方法
  	scanner.scan(
      	StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
	}
}

ClassPathMapperScanner 这个类是 spring-mybatis.jar中的的类,它 extendsClassPathBeanDefinitionScanner,重写了 doScan 方法

1.1. ClassPathMapperScanner 中的 doScan 方法

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    
    

  	// 1.找出指定包名下符合bean定义的BeanDefinition并注册到容器中
  	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 {
    
    
    	// 2.对扫描到的beanDefinitions进行处理,主要4件事:
    	// 1)将bean的真正接口类添加到通用构造函数参数中
    	// 2)将beanClass直接设置为MapperFactoryBean.class,
    	//  	结合1,相当于要使用的构造函数是MapperFactoryBean(java.lang.Class<T>)
    	// 3)添加sqlSessionFactory属性,sqlSessionFactoryBeanName和
    	//  	sqlSessionFactory中,优先使用sqlSessionFactoryBeanName
    	// 4)添加sqlSessionTemplate属性,同样的,sqlSessionTemplateBeanName
    	//  	优先于sqlSessionTemplate,
    	processBeanDefinitions(beanDefinitions);
  	}
 
  	return beanDefinitions;
}

1.1.1. processBeanDefinitions 方法

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    
    

	GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
    
    
    	definition = (GenericBeanDefinition) holder.getBeanDefinition();
      	String beanClassName = definition.getBeanClassName();
      	LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");
		
		// 设置 definition 的构造函数参数值为:映射接口的类名
      	definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
      	// 改变 definition 的beanClass为MapperFactoryBean.class
      	definition.setBeanClass(this.mapperFactoryBeanClass);
		// 添加属性addToConfig为true
      	definition.getPropertyValues().add("addToConfig", this.addToConfig);

		// 分别为 definition 添加 sqlSessionFactory 和 sqlSessionTemplate属性
      	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) {
    
    
        	LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        	definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		}
		
		// 设置属性按类型注入
		definition.setLazyInit(lazyInitialization);
	}
}

MapperScannerConfigurer 小结

  1. 创建扫描器 ClassPathMapperScanner
  2. 使用 ClassPathMapperScanner 扫描注册 basePackage 包下的所有的 mapper 接口类,将 mapper 接口类封装成为 BeanDefinition 对象,注册到 spring 容器中
  3. 同时会将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造函数参数传入 MapperFactoryBean

在这里插入图片描述

2. 类 SqlSessionFactoryBean

对于 SqlSessionFactoryBean 来说,实现了 InitializingBeanFactoryBean 接口

  • FactoryBean 可以自定义创建实例 bean 的方法,只需要实现它的 getObject() 方法
  • InitializingBean 则是会在 bean 初始化阶段被调用

2.1 实现了 FactoryBean 接口的 getObject() 方法

@Override
public SqlSessionFactory getObject() throws Exception {
    
    

  	if (this.sqlSessionFactory == null) {
    
    
    	// 如果之前没有构建,则这边也会调用afterPropertiesSet进行构建操作
    	afterPropertiesSet();
  	}
  	return this.sqlSessionFactory;
}
 
 
@Override
public void afterPropertiesSet() throws Exception {
    
    
  	// 省略部分代码
  	// 构建sqlSessionFactory
  	this.sqlSessionFactory = buildSqlSessionFactory();
}

2.2. buildSqlSessionFactory() 方法

主要做了几件事:

  • 对我们配置的参数进行相应解析
  • 使用配置的参数构建一个 Configuration
  • 使用 Configuration 新建一个 DefaultSqlSessionFactory

这边的核心内容是对于 mapperLocations 的解析,如下代码

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    
    
 
	// 省略部分代码......
 
  	// 5.mapper处理(最重要)
  	if (this.mapperLocations != null) {
    
    
    	if (this.mapperLocations.length == 0) {
    
    
      		LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
    	} else {
    
    
      		for (Resource mapperLocation : this.mapperLocations) {
    
    
        		if (mapperLocation == null) {
    
    
          			continue;
        		}
        		try {
    
    
          			// 5.1 新建XMLMapperBuilder
          			XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              		targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
          			// 5.2 解析mapper文件
          			xmlMapperBuilder.parse();
          			
        		} catch (Exception e) {
    
    
          			throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        		} finally {
    
    
          			ErrorContext.instance().reset();
        		}
        		LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
      		}
    	}
	} else {
    
    
    	LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
  	}
 
  	// 6.使用 targetConfiguration 构建 DefaultSqlSessionFactory
  	return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

2.3. 解析 mapper 文件的 parse() 方法

public void parse() {
    
    
  	// 1.如果 resource 没被加载过才进行加载
  	if (!configuration.isResourceLoaded(resource)) {
    
    
    	// 1.1 解析 mapper 文件
    	configurationElement(parser.evalNode("/mapper"));
    	// 1.2 将 resource 添加到已加载列表
    	configuration.addLoadedResource(resource);
    	// 1.3 绑定 namespace 的 mapper  见 2.6 方法
    	bindMapperForNamespace();
  	}
 
  	parsePendingResultMaps();
  	parsePendingCacheRefs();
  	parsePendingStatements();
}

2.4. 解析 mapper 文件的 configurationElement(parser.evalNode("/mapper")) 方法

private void configurationElement(XNode context) {
    
    

  	try {
    
    
    	// 1.获取namespace属性
    	String namespace = context.getStringAttribute("namespace");
    	if (namespace == null || namespace.isEmpty()) {
    
    
      		throw new BuilderException("Mapper's namespace cannot be empty");
    	}
    	// 2.设置currentNamespace属性
    	builderAssistant.setCurrentNamespace(namespace);
    	// 3.解析parameterMap、resultMap、sql等节点
    	cacheRefElement(context.evalNode("cache-ref"));
    	cacheElement(context.evalNode("cache"));
    	parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    	resultMapElements(context.evalNodes("/mapper/resultMap"));
    	sqlElement(context.evalNodes("/mapper/sql"));
    	// 4.解析增删改查节点,封装成Statement
    	buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  	} catch (Exception e) {
    
    
    	throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  	}
}
 
 
private void buildStatementFromContext(List<XNode> list) {
    
    
  	if (configuration.getDatabaseId() != null) {
    
    
    	buildStatementFromContext(list, configuration.getDatabaseId());
  	}
  	// 解析增删改查节点,封装成Statement
  	buildStatementFromContext(list, null);
}
 
 
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    
    

  	for (XNode context : list) {
    
    
    	// 1.构建XMLStatementBuilder
    	final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    	try {
    
    
      		// 2.解析节点
      		statementParser.parseStatementNode();
    	} catch (IncompleteElementException e) {
    
    
      		configuration.addIncompleteStatement(statementParser);
    	}
  	}
}

XNodeDebug 示意图如下

在这里插入图片描述

每个 XNode 都相当于如下的一个 SQL,下面封装的每个 MappedStatement 可以理解就是每个 SQL

<select id="queryByPrimaryKey" resultMap="BaseResultMap"
        parameterType="java.lang.Integer">
    select id, name, password, age
    from user
    where id = #{id,jdbcType=INTEGER}
</select>

2.5. 解析节点 statementParser.parseStatementNode() 方法

public void parseStatementNode() {
    
    
  	// 省略所有的属性解析......
  	
  	// 将解析出来的所有参数添加到 mappedStatements 缓存
  	builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      	fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      	resultSetTypeEnum, flushCache, useCache, resultOrdered,
      	keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
 
// MapperBuilderAssistant.java
public MappedStatement addMappedStatement(
	String id,
    SqlSource sqlSource,
    StatementType statementType,
    SqlCommandType sqlCommandType,
    Integer fetchSize,
    Integer timeout,
    String parameterMap,
    Class<?> parameterType,
    String resultMap,
    Class<?> resultType,
    ResultSetType resultSetType,
    boolean flushCache,
    boolean useCache,
    boolean resultOrdered,
    KeyGenerator keyGenerator,
    String keyProperty,
    String keyColumn,
    String databaseId,
    LanguageDriver lang,
    String resultSets) {
    
    
 
  	if (unresolvedCacheRef) {
    
    
    	throw new IncompleteElementException("Cache-ref not yet resolved");
  	}
 
  	// 1.将 id 填充上 namespace,例如:queryByPrimaryKey变成
  	// com.joonwhee.open.mapper.UserPOMapper.queryByPrimaryKey
  	id = applyCurrentNamespace(id, false);
  	boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  	// 2.使用参数构建 MappedStatement.Builder
  	MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
      	.resource(resource)
      	.fetchSize(fetchSize)
      	.timeout(timeout)
      	.statementType(statementType)
      	.keyGenerator(keyGenerator)
      	.keyProperty(keyProperty)
      	.keyColumn(keyColumn)
      	.databaseId(databaseId)
      	.lang(lang)
      	.resultOrdered(resultOrdered)
      	.resultSets(resultSets)
      	.resultMaps(getStatementResultMaps(resultMap, resultType, id))
      	.resultSetType(resultSetType)
      	.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
      	.useCache(valueOrDefault(useCache, isSelect))
      	.cache(currentCache);
 
  	ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  	if (statementParameterMap != null) {
    
    
    	statementBuilder.parameterMap(statementParameterMap);
  	}
  	// 3.使用 MappedStatement.Builder 构建 MappedStatement
  	MappedStatement statement = statementBuilder.build();
  	// 4.将 MappedStatement 添加到缓存
  	configuration.addMappedStatement(statement);
  	return statement;
}

在这里插入图片描述

该方法会将节点的属性解析后封装成 MappedStatement,放到 mappedStatements 缓存中keyid。例如:

  • key 为:com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKey
  • value 为:MappedStatement

在这里插入图片描述

2.6. 绑定 namespacemapperbindMapperForNamespace 方法

private void bindMapperForNamespace() {
    
    

  	String namespace = builderAssistant.getCurrentNamespace();
  	if (namespace != null) {
    
    
    	Class<?> boundType = null;
    	
    	try {
    
    
      		// 1.解析 namespace 对应的绑定类型
      		boundType = Resources.classForName(namespace);
    	} catch (ClassNotFoundException e) {
    
    }
    
    	if (boundType != null && !configuration.hasMapper(boundType)) {
    
    
      		// 2.boundType不为空,并且configuration还没有添加boundType,
      		// 则将namespace添加到已加载列表,将boundType添加到knownMappers缓存
      		configuration.addLoadedResource("namespace:" + namespace);
      		configuration.addMapper(boundType);
    	}
  	}
}
 
 
public <T> void addMapper(Class<T> type) {
    
    
  	mapperRegistry.addMapper(type);
}
 
 
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 {
    
    
      		// 将type和以该type为参数构建的MapperProxyFactory作为键值对,
      		// 放到knownMappers缓存中去
      		knownMappers.put(type, new MapperProxyFactory<>(type));
      		MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      		parser.parse();
      		loadCompleted = true;
      		
    	} finally {
    
    
      		if (!loadCompleted) {
    
    
        		knownMappers.remove(type);
      		}
    	}
  	}
}

主要是将刚刚解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,keynamespace 对应的 classvalueMapperProxyFactory

SqlSessionFactoryBean 小结

  1. 解析处理所有属性参数构建 Configuration ,使用 Configuration 新建 DefaultSqlSessionFactory
  2. 解析 mapperLocations 属性的 mapper 文件,将 mapper 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存中,keyid,例如:com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKeyvalueMappedStatement
  3. 将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,keynamespace 对应的 classvalueMapperProxyFactory

3. 解析 DAO 文件

DAO 文件,也就是 basePackage 指定的包下的文件,也就是上文的 ProductInfoMapper

上文 doScan 中说过,basePackage 包下所有 bean 定义的 beanClass 会被设置成 MapperFactoryBean.class,而 MapperFactoryBean 也实现了 FactoryBean 接口,因此直接看 MapperFactoryBeangetObject 方法

@Override
public T getObject() throws Exception {
    
    
  	// 1.从父类中拿到sqlSessionTemplate,这边的sqlSessionTemplate也是doScan中添加的属性
  	// 2.通过mapperInterface获取mapper
  	return getSqlSession().getMapper(this.mapperInterface);
}
 
// SqlSessionTemplate
@Override
public <T> T getMapper(Class<T> type) {
    
    
  	return getConfiguration().getMapper(type, this);
}
 
// Configuration.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    
    
  	return mapperRegistry.getMapper(type, sqlSession);
}
 
// MapperRegistry.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    
    
  	// 1.从knownMappers缓存中获取
  	final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  	if (mapperProxyFactory == null) {
    
    
    	throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  	}
  	try {
    
    
    	// 2.新建实例
    	return mapperProxyFactory.newInstance(sqlSession);
  	} catch (Exception e) {
    
    
    	throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  	}
}
 
// MapperProxyFactory.java
public T newInstance(SqlSession sqlSession) {
    
    
  	// 1.构造一个MapperProxy
  	final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  	// 2.使用MapperProxy来构建实例对象
  	return newInstance(mapperProxy);
}
 
protected T newInstance(MapperProxy<T> mapperProxy) {
    
    
  	// 使用 JDK 动态代理来代理要创建的实例对象,InvocationHandler为 mapperProxy,
  	// 因此当我们真正调用时,会走到 mapperProxy 的 invoke 方法
  	return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
    
     mapperInterface }, mapperProxy);
}

解析 DAO 文件小结

  1. 通过 mapperInterfaceknownMappers 缓存中获取到 MapperProxyFactory 对象
  2. 通过 JDK 动态代理创建 mappper 接口代理对象,InvocationHandlerMapperProxy

4. DAO 接口被调用

DAO 中的接口被调用时,因为 MapperProxyimplementsInvocationHandler 接口,所以会调用到 MapperProxyinvoke 方法

@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 {
    
    
      		// 1.创建MapperMethodInvoker
      		// 2.将method -> MapperMethodInvoker放到methodCache缓存
      		// 3.调用MapperMethodInvoker的invoke方法
      		return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
    	}
  	} catch (Throwable t) {
    
    
    	throw ExceptionUtil.unwrapThrowable(t);
  	}
}
 
// MapperProxy.java
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    
    
  	try {
    
    
    	// 1.放到methodCache缓存,key为method,value为MapperMethodInvoker
    	return methodCache.computeIfAbsent(method, m -> {
    
    	
      		if (m.isDefault()) {
    
    
        		// 2.方法为默认方法,Java8之后,接口允许有默认方法
        		try {
    
    
          			if (privateLookupInMethod == null) {
    
    
            			return new DefaultMethodInvoker(getMethodHandleJava8(method));
          			} else {
    
    
	            		return new DefaultMethodInvoker(getMethodHandleJava9(method));
          			}
        		} catch (IllegalAccessException | InstantiationException | InvocationTargetException
            | NoSuchMethodException e) {
    
    
          			throw new RuntimeException(e);
        		}
      		} else {
    
    
        		// 3.正常接口会走这边,使用mapperInterface、method、configuration
        		// 构建一个MapperMethod,封装成PlainMethodInvoker
        		return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
      		}
    	});
  	} catch (RuntimeException re) {
    
    
    	Throwable cause = re.getCause();
    	throw cause == null ? re : cause;
	}
}

4.1. method.invoke(this, args) 方法

@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
    
    
  	return mapperMethod.execute(sqlSession, args);
}
 
// MapperMethod.java
public Object execute(SqlSession sqlSession, Object[] args) {
    
    
  	Object result;
  	// 1.根据命令类型执行来进行相应操作
  	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);
        		if (method.returnsOptional()
            		&& (result == null || !method.getReturnType().equals(result.getClass()))) {
    
    
          			result = Optional.ofNullable(result);
        		}
      		}
      		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;
}

根据不同的操作类型执行相应的操作,最终将结果返回

4.2. 增删改查

// 1.insert
@Override
public int insert(String statement, Object parameter) {
    
    
  	return update(statement, parameter);
}
 
// 2.update
@Override
public int update(String statement, Object parameter) {
    
    
  	try {
    
    
    	dirty = true;
    	// 从mappedStatements缓存拿到对应的MappedStatement对象,执行更新操作
    	MappedStatement ms = configuration.getMappedStatement(statement);
    	return executor.update(ms, wrapCollection(parameter));
  	} catch (Exception e) {
    
    
    	throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
  	} finally {
    
    
    	ErrorContext.instance().reset();
  	}
}
 
// 3.delete
@Override
public int delete(String statement, Object parameter) {
    
    
  	return update(statement, parameter);
}
 
// 4.select,以executeForMany为例
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    
    
  	
  	List<E> result;
  	// 1.参数转换成sql命令参数
  	Object param = method.convertArgsToSqlCommandParam(args);
  	if (method.hasRowBounds()) {
    
    
    	RowBounds rowBounds = method.extractRowBounds(args);
    	result = sqlSession.selectList(command.getName(), param, rowBounds);
  	} else {
    
    
    	// 2.执行查询操作
    	result = sqlSession.selectList(command.getName(), param);
  	}
  	
  	// 3.处理返回结果
  	if (!method.getReturnType().isAssignableFrom(result.getClass())) {
    
    
    	if (method.getReturnType().isArray()) {
    
    
      		return convertToArray(result);
    	} else {
    
    
      		return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
    	}
  	}
  	return result;
}
 
@Override
public <E> List<E> selectList(String statement, Object parameter) {
    
    
  	return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
 
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    
    

  	try {
    
    
    	//从mappedStatements缓存中拿到对应的MappedStatement对象,执行查询操作
    	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();
  	}
}

可以看出,最终都是从 mappedStatements 缓存中拿到对应的 MappedStatement 对象,执行相应的操作

spring 整合 mybatis 原理流程步骤

  1. 扫描注册 basePackage 包下的所有的 mapper 接口类,将 mapper 接口类封装成为 BeanDefinition 对象,注册到 spring 容器中,同时会将 basePackage 包下的所有 bean 进行一些特殊处理:beanClass 设置为 MapperFactoryBean、bean 的真正接口类作为构造函数参数传入 MapperFactoryBean
  2. 解析 mapperLocations 配置的 mapper 文件,将 mapper 文件中的每个 SQL 封装成 MappedStatement,放到 mappedStatements 缓存中,keyid,例如: id 为 :com.atguigu.mapper.ProductInfoMapper.selectByPrimaryKeyvalueMappedStatement。并且将解析过的 mapper 文件的 namespace 放到 knownMappers 缓存中,keynamespace 对应的 classvalueMapperProxyFactory
  3. 创建 DAObean 时,通过 mapperInterfaceknownMappers 缓存中获取到 MapperProxyFactory 对象,通过 JDK 动态代理创建 mapper 接口代理对象,实现了 InvocationHandler 接口的类为 MapperProxy
  4. DAO 中的接口被调用时,通过 mapper 接口代理对象,调用 MapperProxyinvoke 方法,执行相应的增删改查操作

参考:https://joonwhee.blog.csdn.net/article/details/105932248

猜你喜欢

转载自blog.csdn.net/weixin_38192427/article/details/113725808
今日推荐