SpringBoot整合MyBatis原理

    xiaoxiao2023-10-04  151

    SpringBoot整合MyBatis原理

    一. 自动配置

    SpringBoot提供了MyBatis的自动配置类MybatisAutoConfiguration,可以自动注册SqlSessionFactory、SqlSessionTemplate等组件,开发人员只需在配置文件中指定相关属性即可。

    @Configuration @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class }) @ConditionalOnBean(DataSource.class) @EnableConfigurationProperties(MybatisProperties.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class) public class MybatisAutoConfiguration { private static Log log = LogFactory.getLog(MybatisAutoConfiguration.class); @Autowired private MybatisProperties properties; @Autowired(required = false) private Interceptor[] interceptors; @Autowired private ResourceLoader resourceLoader = new DefaultResourceLoader(); @Autowired(required = false) private DatabaseIdProvider databaseIdProvider; //检查配置文件路径 @PostConstruct public void checkConfigFileExists() { if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) { Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation()); Assert.state(resource.exists(), "Cannot find config location: " + resource + " (please add config file or check your Mybatis " + "configuration)"); } } //注册SqlSessionFactory @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())); } factory.setConfiguration(properties.getConfiguration()); 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 (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); } if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } return factory.getObject(); } //注册SqlSessionTemplate @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); } } //注册ClassPathMapperScanner,进行Mapper的自动扫描,也可以使用@MapperScan代替 public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware { private BeanFactory beanFactory; private ResourceLoader resourceLoader; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { log.debug("Searching for mappers annotated with @Mapper'"); ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); try { if (this.resourceLoader != null) { scanner.setResourceLoader(this.resourceLoader); } List<String> pkgs = AutoConfigurationPackages.get(this.beanFactory); for (String pkg : pkgs) { log.debug("Using auto-configuration base package '" + pkg + "'"); } scanner.setAnnotationClass(Mapper.class); scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(pkgs)); } catch (IllegalStateException ex) { log.debug("Could not determine auto-configuration " + "package, automatic mapper scanning disabled."); } } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } } //如果程序中没有显式注入MapperFactoryBean,就打印日志 @Configuration @Import({ AutoConfiguredMapperScannerRegistrar.class }) @ConditionalOnMissingBean(MapperFactoryBean.class) public static class MapperScannerRegistrarNotFoundConfiguration { @PostConstruct public void afterPropertiesSet() { log.debug(String.format("No %s found.", MapperFactoryBean.class.getName())); } } }

    SpringBoot为我们自动注册了相应的组件:

    SqlSessionFactoryBean:用于构建MyBatis的SqlSessionFactorySqlSessionTemplate:MyBatis的代理类,将SqlSession与Spring的事务进行了整合ClassPathMapperScanner:提供MyBatis的Mapper的自动扫描

    二. SqlSessionFactoryBean

    SqlSessionFactoryBean是一个工厂Bean,其作用就是加载用户自定义的配置,然后使用MyBatis的API创建一个SqlSessionFactory,具体逻辑如下:

    protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); configuration.setVariables(this.configurationProperties); } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type alias: '" + typeAlias + "'"); } } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered plugin: '" + plugin + "'"); } } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type handler: '" + typeHandler + "'"); } } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); if (LOGGER.isDebugEnabled()) { 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(); } } if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } return this.sqlSessionFactoryBuilder.build(configuration); }

    三. SqlSessionTemplate

    SqlSessionTemplate是Spring提供的一个对MyBatis的SqlSession的一个增强类,它的作用就是将SqlSession与当前的事务所绑定,而且是线程安全的,一个SqlSessionTemplate可以被多个dao所共享。

    SqlSessionTemplate基于动态代理模式,内部委托了一个SqlSession对象,并且在其基础上进行了增强。代码如下:

    //传入的SqlSessionFactory private final SqlSessionFactory sqlSessionFactory; //Executor类型 private final ExecutorType executorType; //SqlSession的动态代理 private final SqlSession sqlSessionProxy; private final PersistenceExceptionTranslator exceptionTranslator; public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; //创建SqlSession的动态代理 this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }

    在创建SqlSessionTemplate传入SqlSessionFactory,而通过SqlSessionFactory创建的SqlSession其实是一个动态代理类,其增强的逻辑在SqlSessionInterceptor中定义,如下:

    private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //获取当前事务下绑定的SqlSession,由于Spring通过ThreadLocal将线程与事务绑定,所以也可以认为获取的是当前线程绑定的SqlSession SqlSession sqlSession = getSqlSession( SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { //执行SqlSession的方法 Object result = method.invoke(sqlSession, args); //如果当前在非事务环境下允许,则强制commit一下,因为有些数据库要求在close()方法前要先调用commit()或rollback() if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); throw unwrapped; } finally { //关闭sqlSession if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }

    这样一来,Spring就将事务与SqlSession整合到了一起。

    四. Mapper自动扫描

    通过@MapperScan注解,可以指定Mapper自动扫描的包路径。自动扫描的处理是通过ClassPathMapperScanner实现的,其doScan()方法如下:

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

    ClassPathMapperScanner会将所有的Mapper扫描进来,并且将每个Mapper包装成一个类型为MapperFactoryBean的BeanDefinition,注册到IoC容器中。 MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,是一个BeanFactoryPostProcessor,它的功能就是在容器启动阶段动态向容器中注册BeanDefinition。经过MapperScannerConfigurer处理后,所有Mapper接口的BeanDefinition就以MapperFactoryBean的形式注册到Spring IoC容器中了。 代码可见ClassPathMapperScanner#processBeanDefinitions():

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { GenericBeanDefinition definition; for (BeanDefinitionHolder holder : beanDefinitions) { definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) { logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' mapperInterface"); } // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean //这里实际注册的BeanDefinition是Mapper接口所对应的MapperFactoryBean definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59 definition.setBeanClass(this.mapperFactoryBean.getClass()); 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) { if (logger.isDebugEnabled()) { logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); } definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } } }

    五. MapperFactoryBean

    MapperFactoryBean也是一个工厂Bean,可以通过它创建Mapper实例:

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

    这样一来,所有程序中定义的Mapper就都以Bean的形式加载到Spring的IoC容器中了。

    六. Spring对MyBatis生命周期的修改

    SqlSession

    在MyBatis原生API中,SqlSession的生命周期是方法级别的,在每个方法中创建一个新的SqlSession实例,用完了之后就销毁。而Spring对MyBatis原生的SqlSession进行了事务的增强,通过一个单例的SqlSessionTemplate,保证每次获取的SqlSession其实都是与当前线程绑定的。

    Mapper

    MyBatis原生API中,通过SqlSession#getMapper()获取到的Mapper,是一个MapperProxy动态代理类,它的生命周期与SqlSession一致,都是方法级别的。但是Spring通过MapperFactoryBean创建的Mapper是单例的,也就是将其声明周期提升到了全局级别。

    七. MyBatis一级缓存失效问题

    我们知道MyBatis有两级缓存,其中一级缓存是默认开启的。但是在Spring整合了MyBatis后,却经常出现一级缓存失效的问题,其原因是在SqlSessionTemplate对SqlSession进行了代理后,在非事务环境下,每次执行完SqlSession的操作后都会进行一次commit(),且最后后关闭SqlSession,因此会清空一级缓存(强调下仅在非事务环境下,在事务中调用不会出现这个问题),源码如下:

    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)) { //在非事务环境下执行,会强制commit() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { // release the connection to avoid a deadlock if the translator is no loaded. See issue #22 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { //关闭SqlSession //在非事务环境下,就是直接调用SqlSession.close()方法 //在事务环境下,并不会关闭SqlSession,而仅是对当前事务的引用计数-1 if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }

    八. SpringBoot整合MyBatis简单演示

    引入MyBatis的Starter依赖

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

    在application.properties中增加MyBatis相关配置

    #数据源配置 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.username=xxx spring.datasource.password=xxx #MyBatis配置 mybatis.type-aliases-package=xxx mybatis.mapper-locations=classpath:mappers/*.xml

    编写Mapper和对应的映射文件(或注解)

    开启Mapper自动扫描,并指定包路径

    @MapperScan("xxx.mapper")
    最新回复(0)