返回

SpringBoot——mybatis-starter解析

发布时间:2023-04-07 17:55:03 350
# java# java# 数据库# sql# 容器

mybatis-starter使用指南

mybatis-starter作用

  • 自动检测工程中的DataSource
  • 创建并注册SqlSessionFactory实例
  • 创建并注册SqlSessionTemplate实例
  • 自动扫描mappers并将其注册到BeanFactory中,这样我们就直接可以@Autowired使用

下面介绍下在SpringBoot中引入Mybatis starter和通用Mapper的步骤

Mybatis starter

  • 在pom文件中引入相关依赖

<parent>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-parent</artifactId>
	<version>2.1.10.RELEASE</version>
	<relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>

	<dependency>
		<groupId>org.mybatis.spring.boot</groupId>
		<artifactId>mybatis-spring-boot-starter</artifactId>
		<version>2.1.0</version>
	</dependency>

	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<scope>runtime</scope>
	</dependency>

	<!--jdbc起步依赖-->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-jdbc</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<scope>test</scope>
	</dependency>
</dependencies>

  • 使用mybatis官方逆向工程,现在pom中引入依赖
<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
			<configuration>
				<fork>true</fork>
			</configuration>
		</plugin>

		<!-- MyBatis 逆向工程 插件 -->
		<plugin>
			<groupId>org.mybatis.generator</groupId>
			<artifactId>mybatis-generator-maven-plugin</artifactId>
			<version>1.3.6</version>
			<configuration>
				<!--允许移动生成的文件 -->
				<verbose>true</verbose>
				<!-- 是否覆盖 -->
				<overwrite>true</overwrite>
				<!-- 自动生成的配置 -->
				<configurationFile>
					${basedir}/src/main/resources/generator/generatorConfig.xml
				</configurationFile>
			</configuration>
			<!--下面这两个可以不配置-->
			<dependencies>
				<dependency>
					<groupId>mysql</groupId>
					<artifactId>mysql-connector-java</artifactId>
					<version>8.0.18</version>
				</dependency>
				<dependency>
					<groupId>org.mybatis.generator</groupId>
					<artifactId>mybatis-generator-core</artifactId>
					<version>1.3.6</version>
				</dependency>
			</dependencies>
		</plugin>
	</plugins>
</build>

  • 在resources目录下新建generator目录,并新建generatorConfig.xml文件

<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!--context:代码生成规则配置的上下文 id:标识 targetRuntime: MyBatis3Simple 只会生成基本的CRUD操作--> <context id="MysqlContext" targetRuntime="MyBatis3" defaultModelType="flat"> <!-- 生成的Java文件的编码 --> <property name="javaFileEncoding" value="UTF-8"/> <!-- 格式化java代码 --> <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/> <!-- 格式化XML代码 --> <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/> <!-- beginningDelimiter和endingDelimiter:指明数据库的用于标记数据库对象名的符号,比如ORACLE就是双引号,MYSQL默认是`反引号; --> <property name="beginningDelimiter" value="`"/> <property name="endingDelimiter" value="`"/> <!--生成toString--> <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/> <!-- 生成的实体Bean,将实现Serializable --> <plugin type="org.mybatis.generator.plugins.SerializablePlugin"></plugin> <!--commentGenerator:注释生成策略--> <commentGenerator> <!--suppressAllComments:是否阻止注释生成--> <property name="suppressAllComments" value="true"/> <!--suppressDate:是否阻止时间戳生成--> <property name="suppressDate" value="true"/> </commentGenerator> <!--jdbcConnection:数据库的链接属性--> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/springboot-source-code?serverTimezone=UTC" userId="root" password="yibo"> </jdbcConnection> <!--javaTypeResolver:java类型转换策略--> <javaTypeResolver > <!-- forceBigDecimals false:如果数据库中的字段类型为numeric或者decimal,在代码生成的时候根据数据库中设定的长度自动选择java类型进行转换 true:直接使用java.math.BigDecimal类型--> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!--domain生成策略;targetPackage:生成到哪个包下面,targetProject:生成到哪个项目目录下面--> <javaModelGenerator targetPackage="com.yibo.domain.entity" targetProject="src/main/java"> <!--<property name="enableSubPackages" value="true" />--> <!--表示是否修剪字符串(去掉空格--> <property name="trimStrings" value="true" /> </javaModelGenerator> <!--sqlMapGenerator:映射文件生成策略 targetPackage:生成到哪个包下面,targetProject:生成到哪个项目目录下面 --> <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!--mapper接口生成策略 type:ANNOTATEDMAPPER:注解的形式 XMLMAPPER:xml映射的形式--> <javaClientGenerator type="XMLMAPPER" targetPackage="com.yibo.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!--指定要生成代码的表 domainObjectName:设置表对应的domain实体类生成的名称 --> <table tableName="person" domainObjectName="Person"></table> </context> </generatorConfiguration>
  • 配置数据库即Mybatis属性
# Hikari连接池配置
spring.datasource.url=jdbc:mysql://localhost:3306/springboot-source-code?characterEncoding=UTF-8&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.username=root
spring.datasource.hikari.password=yibo

mybatis.type-aliases-package=com.yibo.domain.entity
mybatis.mapper-locations=classpath:mapper/*.xml
mapper.identity=MYSQL
mapper.not-empty=false
  • 在主配置类上加上包扫描
@SpringBootApplication
@MapperScan("com.yibo.mapper")
public class Application {

    public static void main(String[] args) {

        SpringApplication.run(Application.class,args);
    }
}

通用Mapper即mapper-spring-boot-starter

  • 在pom文件中引入相关依赖

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.10.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--通用Mapper--> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.1.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--jdbc起步依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.example</groupId> <artifactId>weather-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
  • 使用使用通用Mapper逆向工程,现在pom中引入依赖

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>

		<plugin>
			<groupId>org.mybatis.generator</groupId>
			<artifactId>mybatis-generator-maven-plugin</artifactId>
			<version>1.3.7</version>
			<configuration>
				<configurationFile>
					${basedir}/src/main/resources/generator/generatorConfig.xml
				</configurationFile>
				<overwrite>true</overwrite>
				<verbose>true</verbose>
			</configuration>
			<dependencies>
				<dependency>
					<groupId>mysql</groupId>
					<artifactId>mysql-connector-java</artifactId>
					<version>8.0.18</version>
				</dependency>
				<dependency>
					<groupId>tk.mybatis</groupId>
					<artifactId>mapper</artifactId>
					<version>4.1.5</version>
				</dependency>
			</dependencies>
		</plugin>
	</plugins>
</build>

  • 在resources目录下新建generator目录,并新建generatorConfig.xml文件


<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!--<properties resource="generator/config.properties"/>-->

    <context id="Mysql" targetRuntime="MyBatis3Simple" defaultModelType="flat">
        <property name="beginningDelimiter" value="`"/>
        <property name="endingDelimiter" value="`"/>

        <plugin type="tk.mybatis.mapper.generator.MapperPlugin">
            <property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
            <property name="caseSensitive" value="true"/>
            <!--集成lombok-->
            <property name="lombok" value="Getter,Setter,ToString"/>
        </plugin>

        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/springboot-source-code?serverTimezone=UTC"
                        userId="root"
                        password="yibo">
        </jdbcConnection>

        <!--实体-->
        <javaModelGenerator targetPackage="com.yibo.source.code.domain.entity"
                            targetProject="src/main/java"/>

        <!--mapper.xml-->
        <sqlMapGenerator targetPackage="mapper"
                         targetProject="src/main/resources"/>

        <!--mapper接口-->
        <javaClientGenerator targetPackage="com.yibo.source.code.mapper"
                             targetProject="src/main/java"
                             type="XMLMAPPER"/>

        <!--为哪张表生成代码-->
        <table tableName="person"></table>
    </context>
</generatorConfiguration>
  • 配置数据库即Mybatis属性
# Hikari连接池配置
spring.datasource.url=jdbc:mysql://localhost:3306/springboot-source-code?characterEncoding=UTF-8&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.hikari.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.hikari.username=root
spring.datasource.hikari.password=yibo

mybatis.type-aliases-package=com.yibo.source.code.domain.entity
mybatis.mapper-locations=classpath:mapper/*.xml
mapper.identity=MYSQL
mapper.not-empty=false
  • 在主配置类上加上包扫描
@SpringBootApplication
@MapperScan("com.yibo.source.code.mapper")//扫描Mapper接口
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

mybatis-starter原理解析

配置类引入原理

  • 1、点开mybatis-spring-boot-starter的maven的pom文件,发现里面包含如下依赖
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </dependency>
  </dependencies>
  • 2、那么和自动配置相关的为mybatis-spring-boot-autoconfigure,该jar里面的resources/META-INF目录下肯定包含一个spring.factories文件,根据spring.factories文件中的配置,找到需要自动配置的类

点开spring.factories文件,发现,里面有两个EnableAutoConfiguration实现

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

MybatisLanguageDriverAutoConfiguration MybatisLanguageDriverAutoConfiguration主要是用来加载XMLLanguageDriver相关的配置,主要是用来协助使用注解版的SQL语句,观察其类,发现很多报红,不满足Condition条件。通常我们使用xml方式配置sql语句,这种方式相比注解方式会更加灵活。

MybatisAutoConfiguration

@org.springframework.context.annotation.Configuration//表示该类为配置类
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
	......
}
  • 数据源的相关配置
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
	......
}
  • MybatisAutoConfiguration会给容器中注入两个关键的bean,SqlSessionFactory和SqlSessionTemplate SqlSessionFactory是MyBatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜像,SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象类获得,而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例,每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心.同时SqlSessionFactory也是线程安全的,SqlSessionFactory一旦被创建,应该在应用执行期间都存在,在应用运行期间不要重复创建多次,建议使用单例模式,SqlSessionFactory是创建SqlSession的工厂。

 

SqlSession也是MyBatis的关键对象,是执行持久化操作的独享,类似于JDBC中的Connection.它是应用程序与持久层之间执行交互操作的一个单线程对象,也是MyBatis执行持久化操作的关键对象,SqlSession对象完全包含以数据库为背景的所有执行SQL操作的方法,它的底层封装了JDBC连接,可以用SqlSession实例来直接执行被映射的SQL语句。每个线程都应该有它自己的SqlSession实例,SqlSession的实例不能被共享,同时SqlSession也是线程不安全的,绝对不能将SqlSeesion实例的引用放在一个类的静态字段甚至是实例字段中,也绝不能将SqlSession实例的引用放在任何类型的管理范围中,比如Servlet当中的HttpSession对象中使用完SqlSeesion之后关闭Session很重要,应该确保使用finally块来关闭它。

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
	SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
	......
}

SqlSessionTemplate是Mybatis为了接入Spring引入的,其内部也是封装了SqlSession和SqlSessionFactory

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
	ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
}

SqlSessionTemplate探究

public class SqlSessionTemplate implements SqlSession, DisposableBean {
    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
	// 里面的方法是通过SqlSessionProxy来执行的
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
    }

    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        Assert.notNull(executorType, "Property 'executorType' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
		// 发现sqlSessionProxy是SqlSession的代理对象,其方法会被SqlSessionInterceptor代理执行
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
    }
}

通过源码我们何以看到 SqlSessionTemplate 实现了SqlSession接口,也就是说我们可以使用SqlSessionTemplate 来代理以往的DefaultSqlSession完成对数据库的操作,但是DefaultSqlSession这个类不是线程安全的,所以这个类不可以被设置成单例模式的。

 

如果是常规开发模式 我们每次在使用DefaultSqlSession的时候都从SqlSessionFactory当中获取一个就可以了。但是与Spring集成以后,Spring提供了一个全局唯一的SqlSessionTemplate示例 来完成DefaultSqlSession的功能,问题就是:无论是多个dao使用一个SqlSessionTemplate,还是一个dao使用一个SqlSessionTemplate,SqlSessionTemplate都是对应一个sqlSession,当多个web线程调用同一个dao时,它们使用的是同一个SqlSessionTemplate,也就是同一个SqlSession,那么它是如何确保线程安全的呢?让我们一起来分析一下。

 

首先,通过上面一段代码创建代理类,表示创建SqlSessionFactory的代理类的实例,该代理类实现SqlSession接口,定义了方法拦截器,如果调用代理类实例中实现SqlSession接口定义的方法,该调用则被导向SqlSessionInterceptor的invoke方法

核心代码就在 SqlSessionInterceptor的invoke方法当中。

private class SqlSessionInterceptor implements InvocationHandler {
	private SqlSessionInterceptor() {
	}

	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//获取SqlSession
		SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

		Object unwrapped;
		try {
			//调用真实SqlSession的方法
			Object result = method.invoke(sqlSession, args);
			//然后判断一下当前的sqlSession是否被Spring托管 如果未被Spring托管则自动commit
			if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
				sqlSession.commit(true);
			}

			unwrapped = result;
		} catch (Throwable var11) {
			//如果出现异常则根据情况转换后抛出
			unwrapped = ExceptionUtil.unwrapThrowable(var11);
			if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
				SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
				sqlSession = null;
				Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
				if (translated != null) {
					unwrapped = translated;
				}
			}

			throw (Throwable)unwrapped;
		} finally {
			//关闭sqlSession
            //它会根据当前的sqlSession是否在Spring的事物上下文当中来执行具体的关闭动作
			if (sqlSession != null) {
				SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
			}

		}

		return unwrapped;
	}
}

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
	Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
	Assert.notNull(executorType, "No ExecutorType specified");
	//通过TransactionSynchronizationManager获取具体的资源
	SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
	SqlSession session = sessionHolder(executorType, holder);
	if (session != null) {
		return session;
	} else {
		LOGGER.debug(() -> {
			return "Creating a new SqlSession";
		});
		//如果没有获取到sqlSession的话就会从新创建sqlSession
		session = sessionFactory.openSession(executorType);
		//通过registerSessionHolder在将sqlSession存入到ThreadLocal<Map<Object, Object>>中从而保证session线程安全
		registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
		return session;
	}
}
public abstract class TransactionSynchronizationManager {
	private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
	@Nullable
	public static Object getResource(Object key) {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		//调用doGetResource获取资源
		Object value = doGetResource(actualKey);
		if (value != null && logger.isTraceEnabled()) {
			logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
		}

		return value;
	}

	@Nullable
	private static Object doGetResource(Object actualKey) {
		//resources为ThreadLocal<Map<Object, Object>>,里面保存了对应的SqlSession,从而实现session的线程安全。
		Map<Object, Object> map = (Map)resources.get();
		if (map == null) {
			return null;
		} else {
			Object value = map.get(actualKey);
			if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
				map.remove(actualKey);
				if (map.isEmpty()) {
					resources.remove();
				}

				value = null;
			}

			return value;
		}
	}
}

public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
	Assert.notNull(session, "No SqlSession specified");
	Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
	SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
	if (holder != null && holder.getSqlSession() == session) {
		LOGGER.debug(() -> {
			return "Releasing transactional SqlSession [" + session + "]";
		});
		//这里释放的作用,不是关闭,只是减少一下引用数,因为后面可能会被复用 
		holder.released();
	} else {
		LOGGER.debug(() -> {
			return "Closing non transactional SqlSession [" + session + "]";
		});
		//如果不是被spring管理,那么就不会被Spring去关闭回收,就需要自己close 
		session.close();
	}
}

public void released() {
	--this.referenceCount;
}

注解扫描原理

@Mapper注解扫描

MybatisAutoConfiguration里面有两个静态内部类

@org.springframework.context.annotation.Configuration
//发现这个类主要是给容器中导入了一个组件,是AutoConfiguredMapperScannerRegistrar,也就是下面的类
@Import(AutoConfiguredMapperScannerRegistrar.class)
// 条件是容器中没有这个bean,MapperScannerConfigurer
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

@Override
public void afterPropertiesSet() {
  logger.debug(
	  "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
}

}

AutoConfiguredMapperScannerRegistrar

  • 这个类是ImportBeanDefinitionRegistrar实现,之前已经分析过了,可以给容器中导入一些bean定义
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {
    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

      if (!AutoConfigurationPackages.has(this.beanFactory)) {
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
        return;
      }

      logger.debug("Searching for mappers annotated with @Mapper");
	  // 获取的就是主配置类所在包
      List packages = AutoConfigurationPackages.get(this.beanFactory);
      if (logger.isDebugEnabled()) {
        packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
      }
	  // 生成一个BeanDefinition,并设置相关属性,后面要用到	
      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
      builder.addPropertyValue("processPropertyPlaceHolders", true);
      builder.addPropertyValue("annotationClass", Mapper.class);
      builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
      BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
      Stream.of(beanWrapper.getPropertyDescriptors())
          // Need to mybatis-spring 2.0.2+
          .filter(x -> x.getName().equals("lazyInitialization")).findAny()
          .ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
	  // 将这个BeanDefinition注册到容器中
      registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
      this.beanFactory = beanFactory;
    }
}
  • 上面注册的bean是MapperScannerConfigurer,发现它是BeanDefinitionRegistryPostProcessor的实现
public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	  // 上面设置的,为true,这边会做属性占位符替换
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
	// 创建一个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)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
	// 关注一下这个方法
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
}
  • 跟进scanner.registerFilters()
public void registerFilters() {
    boolean acceptAllInterfaces = true;

    // if specified, use the given annotation and / or marker interface
    if (this.annotationClass != null) {
		// 如果使用了@Mapper注解,添加一个IncludeFilter,实现注解扫描
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

    // override AssignableTypeFilter to ignore matches on the actual marker interface
    if (this.markerInterface != null) {
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          return false;
        }
      });
      acceptAllInterfaces = false;
    }

    if (acceptAllInterfaces) {
      // default include filter that accepts all classes
      addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
    }

    // exclude package-info.java
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    });
}
  • 跟进scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS))
public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
	//最终调用此方法
	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
  • 跟进doScan(basePackages)
protected Set doScan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	// 这边就是扫描这个路径,然后通过IncludeFilter去过滤
	Set beanDefinitions = new LinkedHashSet<>();
	for (String basePackage : basePackages) {
		Set candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			}
			if (candidate instanceof AnnotatedBeanDefinition) {
				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
			}
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				definitionHolder =
						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
				beanDefinitions.add(definitionHolder);
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

@MapperScan注解扫描

上面我们分析了@Mapper实现扫描,现在看下@MapperScan,发现它注解上通过@Import引入了一个

@SpringBootApplication
@MapperScan("com.yibo.mapper")
public class Application {

    public static void main(String[] args) {

        SpringApplication.run(Application.class,args);
    }
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
// MapperScannerRegistrar.class,而上面我们@Mapper生效的条件就是容器中没有这个bean
// 那么我们可以知道,如果指定了@MapperScan扫描,@Mapper扫描就失效了,如果两者共存,@Mapper负责的Mapper不在
// @MapperScan扫描的包路径下,这个Mapper就不会注入到容器中,使用就会报错
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
	......
}
@org.springframework.context.annotation.Configuration
@Import(AutoConfiguredMapperScannerRegistrar.class)
// 条件是容器中没有这个bean,MapperScannerConfigurer
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
	......
}
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	  // 获取@MapperScan注解上的属性
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
	// 该方法最关键的就是往容器中注入了一个MapperScannerConfigurer,这个我们上面已经分析过了
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    ......

    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }
}

mapper类生成原理

MapperScannerConfigurer这个类是负责扫描mapper接口所在的包的,它把扫描到的接口解析成一个个的bean定义(BeanDefinition),来看一下源码

public class MapperScannerConfigurer
    implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

MapperScannerConfigurer实现了两个重要接口,如图所示。 实现BeanDefinitionRegistryPostProcessor接口,我们可以自定义注册bean过程,要实现的方法是 postProcessBeanDefinitionRegistry() 实现InitializingBean接口的afterPropertiesSet()方法,可以在bean创建之后初始化的时候做一些操作。 现在进入MapperScannerConfigurer的postProcessBeanDefinitionRegistry()方法

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

该方法内部把扫描mapper接口的工作委托给了ClassPathMapperScanner类,该类继承自ClassPathBeanDefinitionScanner,进入它的scan()方法:

public int scan(String... basePackages) {
	int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

	doScan(basePackages);

	// Register annotation config processors, if necessary.
	if (this.includeAnnotationConfig) {
		AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
	}

	return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

ClassPathBeanDefinitionScanner中的doScan()方法:

protected Set doScan(String... basePackages) {
	Assert.notEmpty(basePackages, "At least one base package must be specified");
	Set beanDefinitions = new LinkedHashSet<>();
	for (String basePackage : basePackages) {
		Set candidates = findCandidateComponents(basePackage);
		for (BeanDefinition candidate : candidates) {
			ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
			candidate.setScope(scopeMetadata.getScopeName());
			String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
			if (candidate instanceof AbstractBeanDefinition) {
				postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
			}
			if (candidate instanceof AnnotatedBeanDefinition) {
				AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
			}
			if (checkCandidate(beanName, candidate)) {
				BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
				definitionHolder =
						AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
				beanDefinitions.add(definitionHolder);
				registerBeanDefinition(definitionHolder, this.registry);
			}
		}
	}
	return beanDefinitions;
}

doScan方法真正的负责生成bean定义。 ClassPathMapperScanner重载的doScan()方法:

@Override
public Set doScan(String... basePackages) {
    Set beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
          + "' package. Please check your configuration.");
    } else {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

重载的doScan方法对生成的mapper的bean定义做了进一步处理,进入processBeanDefinitions()方法:

  private void processBeanDefinitions(Set beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
		// 遍历这些BeanDefinitions,修改其beanClass
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
	  // 比如com.yibo.mapper.UserMapper,将其替换为MapperFactoryBean.class,这个是Mapper接口加载定义阶段最重要的一步。是生成代理类的关键。
	  // mapperFactoryBeanClass = MapperFactoryBean.class
	  // MapperFactoryBean是FactoryBean的实现,那么以后我们创建UserMapper的实例时,实际上会调用MapperFactoryBean的
	  // getObject方法
      definition.setBeanClass(this.mapperFactoryBeanClass);

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      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);
    }
  }

mapperInterface设置的值是mapper接口的带包名的路径名称; definition.setBeanClass()把原来的BeanClass的类型替换成了MapperFactoryBean类型,这个是Mapper接口加载定义阶段最重要的一步。是生成代理类的关键。 查看MapperFactoryBean的定义:

public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {

  private Class mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    // intentionally empty
  }

  public MapperFactoryBean(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  ......
}
public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;

  /**
   * Set MyBatis SqlSessionFactory to be used by this DAO. Will automatically create SqlSessionTemplate for the given
   * SqlSessionFactory.
   *
   * @param sqlSessionFactory
   *          a factory of SqlSession
   */
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }
}
public abstract class DaoSupport implements InitializingBean {
    protected final Log logger = LogFactory.getLog(this.getClass());

    public DaoSupport() {
    }

    public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
        this.checkDaoConfig();

        try {
            this.initDao();
        } catch (Exception var2) {
            throw new BeanInitializationException("Initialization of DAO failed", var2);
        }
    }
	......
}

MapperFactoryBean实现了FactoryBean接口,实现了FactoryBean接口的类型在调用getBean(beanName)既通过名称获取对象时,返回的对象不是本身类型的对象,而是通过实现接口中的getObject()方法返回的对象。

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

MapperFactoryBean实现了FactoryBean接口InitializingBean接口,在对象初始化的时候会调用它的afterPropertiesSet方法,该方法中首先调用了checkDaoConfig()方法,MapperFactoryBean重载的checkDaoConfig()如下:

  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

进入 configuration.addMapper(this.mapperInterface)方法中:

public  void addMapper(Class type) {
	this.mapperRegistry.addMapper(type);
}

在configuration内部对Mapper的操作都委托给了mapperRegistry对象,进入它的addMapper(type)方法,这里的参数type就是一个mapper接口的类型(如:com.yibo.mapper.UserMapper.java):

public  void addMapper(Class 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));
			MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
			parser.parse();
			loadCompleted = true;
		} finally {
			if (!loadCompleted) {
				this.knownMappers.remove(type);
			}
		}
	}
}

this.knownMappers.put(type, new MapperProxyFactory(type));这一步为我们创建了mapper 的代理工厂类对象,并把它放入了knownMappers这个Map中。MapperProxyFactory这个类我们下面再讲。 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); 这两句完成了mapper接口对应的xml文件的解析,xml文件中的每一个方法都被解析成了一个MappedStatement对象(代码太长,不再展开),并添加到了configuration对象中:

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 (this.unresolvedCacheRef) {
		throw new IncompleteElementException("Cache-ref not yet resolved");
	} else {
		id = this.applyCurrentNamespace(id, false);
		boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
		org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);
		ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
		if (statementParameterMap != null) {
			statementBuilder.parameterMap(statementParameterMap);
		}

		MappedStatement statement = statementBuilder.build();
		this.configuration.addMappedStatement(statement);
		return statement;
	}
}

对上面的内容的总结: mapper接口的定义在bean加载阶段会被替换成MapperFactoryBean类型,在spring容器初始化的时候会给我们生成MapperFactoryBean类型的对象,在该对象生成的过程中调用afterPropertiesSet()方法,为我们生成了一个 MapperProxyFactory类型的对象存放于Configuration里的MapperRegistry对象中,同时解析了mapper接口对应的xml文件,把每一个方法解析成一个MappedStatement对象,存放于Configuration里的mappedStatements 这个Map集合中。

 

下面看一下MapperFactoryBean的getObject()方法,看看mapper代理对象是如何生成的:

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

跟踪代码最后调用的是MapperRegistry.getMapper()方法给我们返回了mapper代理对象。

public class MapperRegistry {
	public  T getMapper(Class type, SqlSession sqlSession) {
        MapperProxyFactory 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);
            }
        }
    }
}

现在来看一下MapperProxyFactory这个类:

public class MapperProxyFactory {
    public T newInstance(SqlSession sqlSession) {
        MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }
}
public class MapperProxy implements InvocationHandler, Serializable {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (method.isDefault()) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
}

在invoke()方法中最终执行的是mapperMethod.execute(sqlSession, args);方法。 来看一下MapperMethod这个类:

public class MapperMethod {
    private final MapperMethod.SqlCommand command;
    private final MapperMethod.MethodSignature method;
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }
}

execute()这个方法最终负责执行我们mapper接口中方法,它会判断要执行的方法的类型,然后调用sqlSession对应的方法类型来执行,并放回结果。

mapper类执行原理

public class MapperProxy implements InvocationHandler, Serializable {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

            if (method.isDefault()) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
}

在invoke()方法中最终执行的是mapperMethod.execute(sqlSession, args);方法。 来看一下MapperMethod这个类:

public class MapperMethod {
    private final MapperMethod.SqlCommand command;
    private final MapperMethod.MethodSignature method;
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        Object param;
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }
}

execute()这个方法最终负责执行我们mapper接口中方法,它会判断要执行的方法的类型,然后调用sqlSession对应的方法类型来执行,并放回结果。

 

参考: https://my.oschina.net/liwanghong/blog/3168714

https://www.cnblogs.com/daxin/p/3544188.html

https://blog.csdn.net/u010841296/article/details/89367296

https://blog.csdn.net/qq_21441857/article/details/83015543

特别声明:以上内容(图片及文字)均为互联网收集或者用户上传发布,本站仅提供信息存储服务!如有侵权或有涉及法律问题请联系我们。
举报
评论区(0)
按点赞数排序
用户头像
精选文章
thumb 中国研究员首次曝光美国国安局顶级后门—“方程式组织”
thumb 俄乌线上战争,网络攻击弥漫着数字硝烟
thumb 从网络安全角度了解俄罗斯入侵乌克兰的相关事件时间线
下一篇
SpringBoot——starter解析 2023-04-07 15:13:38