准备一下 由于我们使用 MyBatis
的时候,很少独立使用,所以我直接从 SpringBoot
自动装配,进入 MyBatis
来看看。 直接看 org.mybatis.spring.boot:mybatis-spring-boot-starter
源码目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 ├── LICENSE ├── README.md ├── license.txt ├── mvnw ├── mvnw.cmd ├── mybatis-spring-boot-autoconfigure │ ├── format.xml │ ├── license.txt │ ├── pom.xml │ └── src ├── mybatis-spring-boot-samples │ ├── mybatis-spring-boot-sample-annotation │ ├── mybatis-spring-boot-sample-freemarker │ ├── mybatis-spring-boot-sample-freemarker-legacy │ ├── mybatis-spring-boot-sample-groovy │ ├── mybatis-spring-boot-sample-kotlin │ ├── mybatis-spring-boot-sample-thymeleaf │ ├── mybatis-spring-boot-sample-velocity │ ├── mybatis-spring-boot-sample-velocity-legacy │ ├── mybatis-spring-boot-sample-war │ ├── mybatis-spring-boot-sample-web │ ├── mybatis-spring-boot-sample-xml │ └── pom.xml ├── mybatis-spring-boot-starter │ ├── license.txt │ └── pom.xml ├── mybatis-spring-boot-starter-test │ └── pom.xml ├── mybatis-spring-boot-test-autoconfigure │ ├── format.xml │ ├── pom.xml │ └── src ├── pom.xml └── travis ├── after_success.sh └── settings.xml
在 mybatis-spring-boot-samples/mybatis-spring-boot-sample-xml
中刚好有我想要的示例,刚好不用自己写了,就使用这个示例来查看吧。
自动发现 从之前的自动发现文章中,我们发现,通常自动装配的类都写在 META-INF/spring.factories
这里面,现在我们可以直接查看 mybatis-spring-boot-autoconfigure
这里面的这个文件:
1 2 3 4 org.springframework.boot.autoconfigure.EnableAutoConfiguration =\ org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\ org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
通过名字,感觉 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
在装载的过程中比较重要。 这个类里面有个内部类,是 MyBatis
切入 SpringBoot
的重点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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)); } 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()) .filter(x -> x.getName().equals("lazyInitialization" )).findAny() .ifPresent(x -> builder.addPropertyValue("lazyInitialization" , "${mybatis.lazy-initialization:false}" )); registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition()); } @Override public void setBeanFactory (BeanFactory beanFactory) { this .beanFactory = beanFactory; } } @org .springframework.context.annotation.Configuration@Import(AutoConfiguredMapperScannerRegistrar.class) @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." ); } }
这个类实现了两个接口:
BeanFactoryAware
:这个接口,被 Spring
扫描到的话,是会被 Spring
调用的,传递了当前 BeanFactory
实例,有助于我们后期可以对 BeanFactory
的操作;
ImportBeanDefinitionRegistrar
:那这个接口还真的是第一次出现,他是一个在解析 Configuration
,可以注册业务需要的 BeanDefinition
,简单的说,在刷新容器的时候,BeanDefinitionRegistryPostProcessors
会被调用到,而 SpringBoot
中,总是有个 ConfigurationClassPostProcessor
存在,他在解析配置的时候,会手动的去触发 ImportBeanDefinitionRegistrar#registerBeanDefinitions
函数,这时候,上面的内部类就会被读取到。
那这个类他就是负责在解析配置类的时候,注册了一个 MapperScannerConfigurer
类,看名字不用猜就知道是扫描项目中被 @Mapper
注解装饰的接口类了。
配置 SqlSession 上面注册的 MapperScannerConfigurer
是一个 BeanDefinitionRegistryPostProcessor
处理器,他会在解析配置后面,被 BeanFactory
调用到。 调用就是扫描 Mapper
的存在了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor , InitializingBean, ApplicationContextAware, BeanNameAware { @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)); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 public int scan (String... basePackages) { int beanCountAtScanStart = this .registry.getBeanDefinitionCount(); doScan(basePackages); if (this .includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this .registry); } return (this .registry.getBeanDefinitionCount() - beanCountAtScanStart); }
由于 ClassPathMapperScanner
继承了 Spring
提供的 ClassPathBeanDefinitionScanner
,所以可以直接把扫描注解这件事情交给 Spring
来做,
1 2 3 4 5 6 7 8 9 10 11 12 13 @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; }
那由于上面已经扫描了大半的 BeanDefinition
,后面就新增了还需要扫描 @Mapper
的注解,这时候重新进行一遍扫描以后,新增加的 BeanDefinition
就是我们要的 Mapper
接口了。所以这时候拿到了所有的 Mapper
的 BeanDefinition
进行处理。 接下来对扫描到 Mapper
进行处理一波:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 private void processBeanDefinitions (Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); String beanClassName = definition.getBeanClassName(); LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface" ); definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 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); } }
获取Mapper 实例化在 ApplicationContext#finishBeanFactoryInitialization
中这一步实现 finishBeanFactoryInitialization
。这时候需要先看下 MyBatis
自动配置配置了哪些 Bean
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class MybatisAutoConfiguration implements InitializingBean { @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory (DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean (); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this .properties.getConfigLocation())) { factory.setConfigLocation(this .resourceLoader.getResource(this .properties.getConfigLocation())); } applyConfiguration(factory); if (this .properties.getConfigurationProperties() != null ) { factory.setConfigurationProperties(this .properties.getConfigurationProperties()); } if (!ObjectUtils.isEmpty(this .interceptors)) { factory.setPlugins(this .interceptors); } if (this .databaseIdProvider != null ) { factory.setDatabaseIdProvider(this .databaseIdProvider); } if (StringUtils.hasLength(this .properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this .properties.getTypeAliasesPackage()); } if (this .properties.getTypeAliasesSuperType() != null ) { factory.setTypeAliasesSuperType(this .properties.getTypeAliasesSuperType()); } if (StringUtils.hasLength(this .properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this .properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this .typeHandlers)) { factory.setTypeHandlers(this .typeHandlers); } if (!ObjectUtils.isEmpty(this .properties.resolveMapperLocations())) { factory.setMapperLocations(this .properties.resolveMapperLocations()); } Set<String> factoryPropertyNames = Stream .of(new BeanWrapperImpl (SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName) .collect(Collectors.toSet()); Class<? extends LanguageDriver > defaultLanguageDriver = this .properties.getDefaultScriptingLanguageDriver(); if (factoryPropertyNames.contains("scriptingLanguageDrivers" ) && !ObjectUtils.isEmpty(this .languageDrivers)) { factory.setScriptingLanguageDrivers(this .languageDrivers); if (defaultLanguageDriver == null && this .languageDrivers.length == 1 ) { defaultLanguageDriver = this .languageDrivers[0 ].getClass(); } } if (factoryPropertyNames.contains("defaultScriptingLanguageDriver" )) { factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); } return factory.getObject(); } @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); } } }
SqlSessionFactory
和 SqlSessionTemplate
,先来看看这两个类的作用: 这两个类主要围绕构建 SqlSession
来做一些配置的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 public interface SqlSession extends Closeable { <T> T selectOne(String statement); <T> T selectOne(String statement, Object parameter); <E> List<E> selectList(String statement); <E> List<E> selectList(String statement, Object parameter); <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds); <K, V> Map<K, V> selectMap(String statement, String mapKey); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey); <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds); <T> Cursor<T> selectCursor(String statement); <T> Cursor<T> selectCursor(String statement, Object parameter); <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds); void select(String statement, Object parameter, ResultHandler handler); void select(String statement, ResultHandler handler); void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler); int insert(String statement); int insert(String statement, Object parameter); int update(String statement); int update(String statement, Object parameter); int delete(String statement); int delete(String statement, Object parameter); void commit(); void commit(boolean force); void rollback(); void rollback(boolean force); List<BatchResult> flushStatements(); @Override void close(); void clearCache(); Configuration getConfiguration(); <T> T getMapper(Class<T> type); Connection getConnection(); }
这是 MyBatis
执行 SQL
的关键接口,定义了 jdbc
常用的方法。而 SqlSessionTemplate
就是一个 SqlSession
,专门用来整合 Spring
的一个 SqlSession
,而 SqlSessionFactory
则是封装了连接的方法以及一些其他的配置,比如拦截器等等。 那我们看看主启动类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @SpringBootApplication public class SampleXmlApplication implements CommandLineRunner { public static void main (String[] args) { SpringApplication.run(SampleXmlApplication.class, args); } private final CityDao cityDao; private final HotelMapper hotelMapper; public SampleXmlApplication (CityDao cityDao, HotelMapper hotelMapper) { this .cityDao = cityDao; this .hotelMapper = hotelMapper; } @Override @SuppressWarnings("squid:S106") public void run (String... args) { System.out.println(this .cityDao.selectCityById(1 )); System.out.println(this .hotelMapper.selectByCityId(1 )); } }
他注入了一个 CityDao
以及 一个 HotelMapper
,两种不同的查询方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Component public class CityDao { private final SqlSession sqlSession; public CityDao (SqlSession sqlSession) { this .sqlSession = sqlSession; } public City selectCityById (long id) { return this .sqlSession.selectOne("selectCityById" , id); } } @Mapper public interface HotelMapper { Hotel selectByCityId (int cityId) ; }
那么,Spring
容器在初始化 SampleXmlApplication
的时候就会去循环递归的去找到最后所需要的依赖,那其实 CityDao
的初始化并不难,现在看看 HotelMapper
的初始化。 HotelMapper
在注册给 Spring
的时候其实已经将实际对象的 FactoryBean
设置成 MapperFactoryBean
,而 MapperFactoryBean
实现了 FactoryBean
,Spring
将会通过调用 getObject
来获取工厂构造的对象,所以这时候我们看看 MapperFactoryBean
怎么实现这个方法的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MapperFactoryBean <T> extends SqlSessionDaoSupport implements FactoryBean <T> { private Class<T> mapperInterface; private boolean addToConfig = true ; public MapperFactoryBean () { } public MapperFactoryBean (Class<T> mapperInterface) { this .mapperInterface = mapperInterface; } @Override public T getObject () throws Exception { return getSqlSession().getMapper(this .mapperInterface); } }
OK,我们可以看到他从 SqlSession
中来获取 Mapper代理
,所以这个方法在这里充其量就是一个桥梁,架通了 MyBatis
构造的代理以及 Spring
的 Bean
的桥。 那 SqlSession
怎么来的,又是一个 Factory
构造来的,还记得上面的工厂配置类吗,就是那里构造出来的,现在我们需要回去去看看他怎么构造 MyBatis
上下文的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory (DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean (); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this .properties.getConfigLocation())) { factory.setConfigLocation(this .resourceLoader.getResource(this .properties.getConfigLocation())); } applyConfiguration(factory); if (this .properties.getConfigurationProperties() != null ) { factory.setConfigurationProperties(this .properties.getConfigurationProperties()); } if (!ObjectUtils.isEmpty(this .interceptors)) { factory.setPlugins(this .interceptors); } if (this .databaseIdProvider != null ) { factory.setDatabaseIdProvider(this .databaseIdProvider); } if (StringUtils.hasLength(this .properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this .properties.getTypeAliasesPackage()); } if (this .properties.getTypeAliasesSuperType() != null ) { factory.setTypeAliasesSuperType(this .properties.getTypeAliasesSuperType()); } if (StringUtils.hasLength(this .properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this .properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this .typeHandlers)) { factory.setTypeHandlers(this .typeHandlers); } if (!ObjectUtils.isEmpty(this .properties.resolveMapperLocations())) { factory.setMapperLocations(this .properties.resolveMapperLocations()); } Set<String> factoryPropertyNames = Stream .of(new BeanWrapperImpl (SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName) .collect(Collectors.toSet()); Class<? extends LanguageDriver > defaultLanguageDriver = this .properties.getDefaultScriptingLanguageDriver(); if (factoryPropertyNames.contains("scriptingLanguageDrivers" ) && !ObjectUtils.isEmpty(this .languageDrivers)) { factory.setScriptingLanguageDrivers(this .languageDrivers); if (defaultLanguageDriver == null && this .languageDrivers.length == 1 ) { defaultLanguageDriver = this .languageDrivers[0 ].getClass(); } } if (factoryPropertyNames.contains("defaultScriptingLanguageDriver" )) { factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); } return factory.getObject(); }
来到这里,终于进来了 MyBatis
的内容了,就是开始读取配置,解析,然后这个 Configuration
类将贯穿相应使用 MyBatis
的整个生命周期。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @Override public SqlSessionFactory getObject () throws Exception { if (this .sqlSessionFactory == null ) { afterPropertiesSet(); } return this .sqlSessionFactory; } @Override public void afterPropertiesSet () throws Exception { notNull(dataSource, "Property 'dataSource' is required" ); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required" ); state((configuration == null && configLocation == null ) !(configuration != null && configLocation != null ), "Property 'configuration' and 'configLocation' can not specified with together" ); this .sqlSessionFactory = buildSqlSessionFactory(); }
目前官方示例给的一个配置就是简单的扫描接收类型的包以及 Mapper
映射的路径:
1 2 3 4 5 6 7 8 9 10 11 12 <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <package name="sample.mybatis.domain"/> </typeAliases> <mappers> <mapper resource="sample/mybatis/mapper/CityMapper.xml"/> <mapper resource="sample/mybatis/mapper/HotelMapper.xml"/> </mappers> </configuration>
进入 buildSqlSessionFactory
我们就可以看到配置熟悉的东西都在这里被解析,这个代码没点 j8
基础都看不懂了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 protected SqlSessionFactory buildSqlSessionFactory () throws Exception { final Configuration targetConfiguration; XMLConfigBuilder xmlConfigBuilder = null ; if (this .configuration != null ) { targetConfiguration = this .configuration; if (targetConfiguration.getVariables() == null ) { targetConfiguration.setVariables(this .configurationProperties); } else if (this .configurationProperties != null ) { targetConfiguration.getVariables().putAll(this .configurationProperties); } } else if (this .configLocation != null ) { xmlConfigBuilder = new XMLConfigBuilder (this .configLocation.getInputStream(), null , this .configurationProperties); targetConfiguration = xmlConfigBuilder.getConfiguration(); } else { LOGGER.debug( () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration" ); targetConfiguration = new Configuration (); Optional.ofNullable(this .configurationProperties).ifPresent(targetConfiguration::setVariables); } Optional.ofNullable(this .objectFactory).ifPresent(targetConfiguration::setObjectFactory); Optional.ofNullable(this .objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory); Optional.ofNullable(this .vfs).ifPresent(targetConfiguration::setVfsImpl); if (hasLength(this .typeAliasesPackage)) { scanClasses(this .typeAliasesPackage, this .typeAliasesSuperType).stream() .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()) .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias); } if (!isEmpty(this .typeAliases)) { Stream.of(this .typeAliases).forEach(typeAlias -> { targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias); LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'" ); }); } if (!isEmpty(this .plugins)) { Stream.of(this .plugins).forEach(plugin -> { targetConfiguration.addInterceptor(plugin); LOGGER.debug(() -> "Registered plugin: '" + plugin + "'" ); }); } if (hasLength(this .typeHandlersPackage)) { scanClasses(this .typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass()) .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) .forEach(targetConfiguration.getTypeHandlerRegistry()::register); } if (!isEmpty(this .typeHandlers)) { Stream.of(this .typeHandlers).forEach(typeHandler -> { targetConfiguration.getTypeHandlerRegistry().register(typeHandler); LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'" ); }); } if (!isEmpty(this .scriptingLanguageDrivers)) { Stream.of(this .scriptingLanguageDrivers).forEach(languageDriver -> { targetConfiguration.getLanguageRegistry().register(languageDriver); LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'" ); }); } Optional.ofNullable(this .defaultScriptingLanguageDriver) .ifPresent(targetConfiguration::setDefaultScriptingLanguage); if (this .databaseIdProvider != null ) { try { targetConfiguration.setDatabaseId(this .databaseIdProvider.getDatabaseId(this .dataSource)); } catch (SQLException e) { throw new NestedIOException ("Failed getting a databaseId" , e); } } Optional.ofNullable(this .cache).ifPresent(targetConfiguration::addCache); if (xmlConfigBuilder != null ) { try { xmlConfigBuilder.parse(); LOGGER.debug(() -> "Parsed configuration file: '" + this .configLocation + "'" ); } catch (Exception ex) { throw new NestedIOException ("Failed to parse config resource: " + this .configLocation, ex); } finally { ErrorContext.instance().reset(); } } targetConfiguration.setEnvironment(new Environment (this .environment, this .transactionFactory == null ? new SpringManagedTransactionFactory () : this .transactionFactory, this .dataSource)); if (this .mapperLocations != null ) { if (this .mapperLocations.length == 0 ) { LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found." ); } else { for (Resource mapperLocation : this .mapperLocations) { if (mapperLocation == null ) { continue ; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder (mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException ("Failed to parse mapping resource: '" + mapperLocation + "'" , e); } finally { ErrorContext.instance().reset(); } LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'" ); } } } else { LOGGER.debug(() -> "Property 'mapperLocations' was not specified." ); } return this .sqlSessionFactoryBuilder.build(targetConfiguration); }
解析Configuration.xml 1 2 3 4 5 6 7 8 9 public Configuration parse () { if (parsed) { throw new BuilderException ("Each XMLConfigBuilder can only be used once." ); } parsed = true ; parseConfiguration(parser.evalNode("/configuration" )); return configuration; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 private void parseConfiguration (XNode root) { try { propertiesElement(root.evalNode("properties" )); Properties settings = settingsAsProperties(root.evalNode("settings" )); loadCustomVfs(settings); loadCustomLogImpl(settings); typeAliasesElement(root.evalNode("typeAliases" )); pluginElement(root.evalNode("plugins" )); objectFactoryElement(root.evalNode("objectFactory" )); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory" )); reflectorFactoryElement(root.evalNode("reflectorFactory" )); settingsElement(settings); environmentsElement(root.evalNode("environments" )); databaseIdProviderElement(root.evalNode("databaseIdProvider" )); typeHandlerElement(root.evalNode("typeHandlers" )); mapperElement(root.evalNode("mappers" )); } catch (Exception e) { throw new BuilderException ("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
这里大概就是 Configuration.xml
的所有内容的解析,那我们就直接看看解析 SqlMapper
的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private void mapperElement (XNode parent) throws Exception { if (parent != null ) { for (XNode child : parent.getChildren()) { if ("package" .equals(child.getName())) { String mapperPackage = child.getStringAttribute("name" ); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource" ); String url = child.getStringAttribute("url" ); String mapperClass = child.getStringAttribute("class" ); if (resource != null && url == null && mapperClass == null ) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder (inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null ) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder (inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null ) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException ("A mapper element may only specify a url, resource or class, but not more than one." ); } } } } }
解析Mapper.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public void parse () { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper" )); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
解析Statement语句 主要是解析标签比较重要,我们现在进入来看看 configurationElement(parser.evalNode("/mapper"))
方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private void configurationElement (XNode context) { try { String namespace = context.getStringAttribute("namespace" ); if (namespace == null namespace.equals("" )) { throw new BuilderException ("Mapper's namespace cannot be empty" ); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref" )); cacheElement(context.evalNode("cache" )); parameterMapElement(context.evalNodes("/mapper/parameterMap" )); resultMapElements(context.evalNodes("/mapper/resultMap" )); sqlElement(context.evalNodes("/mapper/sql" )); buildStatementFromContext(context.evalNodes("selectinsertupdatedelete" )); } catch (Exception e) { throw new BuilderException ("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
结合 Mapper.xml
来看看:
1 2 3 4 5 6 7 8 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="sample.mybatis.mapper.HotelMapper"> <select id="selectByCityId" resultType="Hotel"> select city, name, address, zip from hotel where city = #{id} </select> </mapper>
解析语句是发生在 buildStatementFromContext
里面的:
1 2 3 4 5 6 7 private void buildStatementFromContext (List<XNode> list) { if (configuration.getDatabaseId() != null ) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null ); }
不得不说,MyBatis
还真的是喜欢 Builder
1 2 3 4 5 6 7 8 9 10 11 12 private void buildStatementFromContext (List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder (configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public void parseStatementNode () { String id = context.getStringAttribute("id" ); String databaseId = context.getStringAttribute("databaseId" ); if (!databaseIdMatchesCurrent(id, databaseId, this .requiredDatabaseId)) { return ; } String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache" , !isSelect); boolean useCache = context.getBooleanAttribute("useCache" , isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered" , false ); XMLIncludeTransformer includeParser = new XMLIncludeTransformer (configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); String parameterType = context.getStringAttribute("parameterType" ); Class<?> parameterTypeClass = resolveClass(parameterType); String lang = context.getStringAttribute("lang" ); LanguageDriver langDriver = getLanguageDriver(lang); processSelectKeyNodes(id, parameterTypeClass, langDriver); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true ); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys" , configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType" , StatementType.PREPARED.toString())); Integer fetchSize = context.getIntAttribute("fetchSize" ); Integer timeout = context.getIntAttribute("timeout" ); String parameterMap = context.getStringAttribute("parameterMap" ); String resultType = context.getStringAttribute("resultType" ); Class<?> resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap" ); String resultSetType = context.getStringAttribute("resultSetType" ); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null ) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } String keyProperty = context.getStringAttribute("keyProperty" ); String keyColumn = context.getStringAttribute("keyColumn" ); String resultSets = context.getStringAttribute("resultSets" ); builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
通过构建好的配置信息,将查询的配置加入 Configuration
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 public MappedStatement addMappedStatement ( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) { throw new IncompleteElementException ("Cache-ref not yet resolved" ); } id = applyCurrentNamespace(id, false ); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; MappedStatement.Builder statementBuilder = new MappedStatement .Builder(configuration, id, sqlSource, sqlCommandType) .resource(resource) .fetchSize(fetchSize) .timeout(timeout) .statementType(statementType) .keyGenerator(keyGenerator) .keyProperty(keyProperty) .keyColumn(keyColumn) .databaseId(databaseId) .lang(lang) .resultOrdered(resultOrdered) .resultSets(resultSets) .resultMaps(getStatementResultMaps(resultMap, resultType, id)) .resultSetType(resultSetType) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null ) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); configuration.addMappedStatement(statement); return statement; }
注册Mapper到MyBatis的Registry Configuration
中,解析 Mapper
已经完成,接下来就是注册一个接口代理对象到 Configuration
中去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 private void bindMapperForNamespace () { String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null ) { Class<?> boundType = null ; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { } if (boundType != null ) { if (!configuration.hasMapper(boundType)) { configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); } } } } 重点就在于这个 `configuration.addMapper` 中: public class Configuration { public <T> void addMapper (Class<T> type) { mapperRegistry.addMapper(type); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class MapperRegistry { public <T> void addMapper (Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException ("Type " + type + " is already known to the MapperRegistry." ); } boolean loadCompleted = false ; try { knownMappers.put(type, new MapperProxyFactory <>(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder (config, type); parser.parse(); loadCompleted = true ; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } }
MapperFactoryBean对接Spring 还记得上面对接 Spring
中 getBean
的对接桥梁吗,就是 MapperFactoryBean
来适配接口的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class MapperFactoryBean <T> extends SqlSessionDaoSupport implements FactoryBean <T> { @Override public T getObject () throws Exception { return getSqlSession().getMapper(this .mapperInterface); } } public class SqlSessionTemplate implements SqlSession , DisposableBean { @Override public <T> T getMapper (Class<T> type) { return getConfiguration().getMapper(type, this ); } } public class Configuration { public <T> T getMapper (Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } } public class MapperRegistry { public <T> T getMapper (Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null ) { throw new BindingException ("Type " + type + " is not known to the MapperRegistry." ); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException ("Error getting mapper instance. Cause: " + e, e); } } } public class MapperProxyFactory <T> { @SuppressWarnings("unchecked") protected T newInstance (MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class [] { mapperInterface }, mapperProxy); } public T newInstance (SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy <>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
到达这里,初始化工作基本已经完成,现在我们要看看运行时怎么获取数据的
获取代理对象 那么现在就回到主程序中,来观察 MyBatis
是怎么封装 Mapper
接口来查询数据的,这里我重新贴一下主程序中的查询入口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @SpringBootApplication public class SampleXmlApplication implements CommandLineRunner { public static void main (String[] args) { SpringApplication.run(SampleXmlApplication.class, args); } private final CityDao cityDao; private final HotelMapper hotelMapper; public SampleXmlApplication (CityDao cityDao, HotelMapper hotelMapper) { this .cityDao = cityDao; this .hotelMapper = hotelMapper; } @Override @SuppressWarnings("squid:S106") public void run (String... args) { System.out.println(this .cityDao.selectCityById(1 )); System.out.println(this .hotelMapper.selectByCityId(1 )); } }
刚刚上面提到过,MyBatis
在整合 Spring
的时候其实就将自己 Registry
中的 MapperProxy
给放入 BeanDefinition
中,然后交给 Spring
去自动注入到需要依赖的对象。那么现在拿到的对象就是一个 MapperProxy
。 既然是 MapperProxy
,那要看怎么封装查询数据就是看这个了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else if (method.isDefault()) { if (privateLookupInMethod == null ) { return invokeDefaultMethodJava8(proxy, method, args); } else { return invokeDefaultMethodJava9(proxy, method, args); } } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }
MapperProxy
是何方神圣,哦,是一个 InvocationHandler
,这是一个我从来没见过的 jdk
接口。J8
专有的动态代理的接口,那他有什么用呢,就是将实现封装在这个实现类里边,实现类有个 invoke
方法需要实现,那么实现类的所有方法被调用时,都需要先走这个方法,这时候我们就可以在 invoke
里面做一些羞羞的事情。
InvocationHandler代理 那就来示例一下这个东西怎么用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public interface A { void add () ; } public class InvokeHandler <T> implements InvocationHandler { Class<T> type; public InvokeHandler (Class<T> tClass) { this .type = tClass; } @Override public Object invoke (Object o, Method method, Object[] objects) throws Throwable { System.out.println("调用到这里来了" ); return null ; } public T getProxy () { return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class []{type}, this ); } } public class TestInvoke { public static void main (String[] args) { A proxy = new InvokeHandler <>(A.class).getProxy(); proxy.add(); } }
那就是说,MapperProxy
拦截了我们所有接口的调用,然后,从这里读取了 mapper.xml
中配置的 SQL
语句,然后调用 jdbc
做相对应的查询。
获取MapperMethod OK,弄清楚了动态代理,现在回来看看怎么被调用的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this , args); } else if (method.isDefault()) { if (privateLookupInMethod == null ) { return invokeDefaultMethodJava8(proxy, method, args); } else { return invokeDefaultMethodJava9(proxy, method, args); } } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod (Method method) { return methodCache.computeIfAbsent(method, k -> new MapperMethod (mapperInterface, method, sqlSession.getConfiguration())); }
MapperMethod调用 先来看看 MapperMethod
有什么属性,方法肯定就是上面的 execute
是重中之重:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod (Class<?> mapperInterface, Method method, Configuration config) { this .command = new SqlCommand (config, mapperInterface, method); this .method = new MethodSignature (config, mapperInterface, method); } public Object execute (SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break ; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break ; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break ; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null ; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break ; case FLUSH: result = sqlSession.flushStatements(); break ; default : throw new BindingException ("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException ("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")." ); } return result; } }
SqlSession拦截器 上面的代码我们来到了 sqlSession.selectOne
这句话,所以进入代码看看是发生什么事情:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 public class SqlSessionTemplate implements SqlSession , DisposableBean { @Override public <T> T selectOne (String statement, Object parameter) { return this .sqlSessionProxy.selectOne(statement, parameter); } private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this .sqlSessionFactory, SqlSessionTemplate.this .executorType, SqlSessionTemplate.this .exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this .sqlSessionFactory)) { sqlSession.commit(true ); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this .exceptionTranslator != null && unwrapped instanceof PersistenceException) { closeSqlSession(sqlSession, SqlSessionTemplate.this .sqlSessionFactory); sqlSession = null ; Throwable translated = SqlSessionTemplate.this .exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null ) { unwrapped = translated; } } throw unwrapped; } finally { if (sqlSession != null ) { closeSqlSession(sqlSession, SqlSessionTemplate.this .sqlSessionFactory); } } } } public static SqlSession getSqlSession (SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null ) { return session; } LOGGER.debug(() -> "Creating a new SqlSession" ); session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; } }
查询方法 好,总体的开启 SqlSession
啊,对接事务的,有个了解,现在具体到怎么查询的问题,那就是 SqlSession
了。 算了,下集一起说……. To Be Continue...