当前位置 : 主页 > 编程语言 > java >

SpringBoot——mybatis-starter解析

来源:互联网 收集:自由互联 发布时间:2023-02-04
mybatis-starter使用指南 mybatis-starter作用 自动检测工程中的DataSource 创建并注册SqlSessionFactory实例 创建并注册SqlSessionTemplate实例 自动扫描mappers并将其注册到BeanFactory中,这样我们就直接可

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<String> 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<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); // 这边就是扫描这个路径,然后通过IncludeFilter去过滤 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { Set<BeanDefinition> 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<BeanDefinitionHolder> doScan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>(); for (String basePackage : basePackages) { Set<BeanDefinition> 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<BeanDefinitionHolder> doScan(String... basePackages) { 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 { processBeanDefinitions(beanDefinitions); } return beanDefinitions; }

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

private void processBeanDefinitions(Set<BeanDefinitionHolder> 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<T> extends SqlSessionDaoSupport implements FactoryBean<T> { private Class<T> mapperInterface; private boolean addToConfig = true; public MapperFactoryBean() { // intentionally empty } public MapperFactoryBean(Class<T> 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 <T> void addMapper(Class<T> type) { this.mapperRegistry.addMapper(type); }

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

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

现在来看一下MapperProxyFactory这个类:

public class MapperProxyFactory<T> { public T newInstance(SqlSession sqlSession) { MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); } } public class MapperProxy<T> 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<T> 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

上一篇:SpringBoot——redis-starter
下一篇:没有了
网友评论