mybatis源码分析:mybatis与Spring整合

Scroll Down

概述

本文主要介绍在mybatis及Spring整合过程中所用到的技术及Spring中涉及到的流程。

Spring自定义标签和spring.handlers的加载过程

在mybatis整合到Spring过程中,mybatis需要Spring支持自定义标签scan的支持,该标签主要是用来扫描用户定义的接口。

1 schema配置

这里引用了网上的一些资料,如下图所示:
springtag1.png

2 Spring自定义标签:

Spring自定义标签的原理

XML通常通过DTD、XSD定义,但DTD的表达能力较弱,XSD定义则能力比较强,能够定义类型,出现次数等。自定义标签需要XSD支持,在实现时使用Namespace扩展来支持自定义标签。
原来的代码是这样:

<bean id="beanId" class="com.xxx.xxxx.Xxxxx">  
     <property name="property1">  
         <value>XXXX</value>  
     </property>  
     <property name="property2">  
         <value>XXXX</value>  
     </property>  
</bean>  

改造过后:

<xxx:xxxx id="beanId"/>  

Spring通过XML解析程序将其解析为DOM树,通过NamespaceHandler指定对应的Namespace的BeanDefinitionParser将其转换成BeanDefinition。再通过Spring自身的功能对BeanDefinition实例化对象。
在期间,Spring还会加载两项资料:

  • META-INF/spring.handlers 
    指定NamespaceHandler(实现org.springframework.beans.factory.xml.NamespaceHandler)接口,或使用org.springframework.beans.factory.xml.NamespaceHandlerSupport的子类。
  • META-INF/spring.schemas 
    在解析XML文件时将XSD重定向到本地文件,避免在解析XML文件时需要上网下载XSD文件。通过现实org.xml.sax.EntityResolver接口来实现该功能。

3 spring.handlers的加载过程

要实现自定义的xml配置,需要有两个默认spring配置文件来支持。一个是spring.schemas,一个是spring.handlers,前者是为了验证你自定义的xml配置文件是否符合你的格式要求,后者是告诉spring该如何来解析你自定义的配置文件。

1.在步骤4createReaderContext的时候,会做如下检查,如果没有resolver会创建一个默认的DefaultNamespaceHandlerResolver,

if (this.namespaceHandlerResolver == null) {
    this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}

Spring.handlers这个文件名和路径就定义在这个类中。定义如下:

public static final String DEFAULT_HANDLER_MAPPINGS_LOCATION = "META-INF/spring.handlers";

所以务必记住默认的文件路径是在META-INF文件夹下。
2.spring启动容器到handlers加载过程中间每一步怎么走的
在步骤10中根据会根据传入的namespaceUri找到对应的NamespaceHandler,这个映射是在spring.handlers中配置的。

在步骤13中会根据element的名字找到对应的BeanDefinitionParser,这个是在NamespaceHandler的init()方法里面来配置的。
springtag2.jpg

mybatis中的应用

(1)、首先在mybatis-spring包的META-INF下创建spring.handlers和spring.schemas两个文件。
spring.handlers

http\://mybatis.org/schema/mybatis-spring=org.mybatis.spring.config.NamespaceHandler

spring.schemas

http\://mybatis.org/schema/mybatis-spring-1.2.xsd=org/mybatis/spring/config/mybatis-spring-1.2.xsd
http\://mybatis.org/schema/mybatis-spring.xsd=org/mybatis/spring/config/mybatis-spring-1.2.xsd

(2)、定义NamespaceHandler,该类继承NamespaceHandlerSupport。
代码如下:

@Override
public void init() {
    registerBeanDefinitionParser("scan", new MapperScannerBeanDefinitionParser());
}

(3)、定义MapperScannerBeanDefinitionParser实现了BeanDefinitionParser
代码如下:

@Override
// 解析mybatis-spring名称空间下的属性
public synchronized BeanDefinition parse(Element element, ParserContext parserContext) {
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(parserContext.getRegistry());
    ClassLoader classLoader = scanner.getResourceLoader().getClassLoader();
    XmlReaderContext readerContext = parserContext.getReaderContext();
    scanner.setResourceLoader(readerContext.getResourceLoader());
    try {
      String annotationClassName = element.getAttribute(ATTRIBUTE_ANNOTATION);
      if (StringUtils.hasText(annotationClassName)) {
        @SuppressWarnings("unchecked")
        Class<? extends Annotation> markerInterface = (Class<? extends Annotation>) classLoader.loadClass(annotationClassName);
        scanner.setAnnotationClass(markerInterface);
      }
      String markerInterfaceClassName = element.getAttribute(ATTRIBUTE_MARKER_INTERFACE);
      if (StringUtils.hasText(markerInterfaceClassName)) {
        Class<?> markerInterface = classLoader.loadClass(markerInterfaceClassName);
        scanner.setMarkerInterface(markerInterface);
      }
      String nameGeneratorClassName = element.getAttribute(ATTRIBUTE_NAME_GENERATOR);
      if (StringUtils.hasText(nameGeneratorClassName)) {
        Class<?> nameGeneratorClass = classLoader.loadClass(nameGeneratorClassName);
        BeanNameGenerator nameGenerator = BeanUtils.instantiateClass(nameGeneratorClass, BeanNameGenerator.class);
        scanner.setBeanNameGenerator(nameGenerator);
      }
    } catch (Exception ex) {
      readerContext.error(ex.getMessage(), readerContext.extractSource(element), ex.getCause());
    }
    String sqlSessionTemplateBeanName = element.getAttribute(ATTRIBUTE_TEMPLATE_REF);
    scanner.setSqlSessionTemplateBeanName(sqlSessionTemplateBeanName);
    String sqlSessionFactoryBeanName = element.getAttribute(ATTRIBUTE_FACTORY_REF);
    scanner.setSqlSessionFactoryBeanName(sqlSessionFactoryBeanName);
    scanner.registerFilters();
    String basePackage = element.getAttribute(ATTRIBUTE_BASE_PACKAGE);
    scanner.scan(StringUtils.tokenizeToStringArray(basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    return null;
}

上述方式是在传统ssm整合时会使用的逻辑,但是在Springboot整合mybatis时却不再使用上述的方式,因为Springboot集中使用了Configuration机制来替代xml文件配置的方式了。下面来分析下

Springboot整合mybatis

Springboot整合mybatis有两种方式,这两种方式与mybatis的mapper加载方式有关。
之前分析过mybatis加载mapper有两种途径,一、通过mapper文件。二、通过注解的方式
对应的Springboot也提供了这两种方式的支持:

  • (1)、mapper文件的方式:
    通过在Springboot的application.properties文件中mybatis.mapper-locations=classpath:mapper/*Mapper.xml属性来指定mapper文件的位置。
  • (2)、通过注解的方式:
    使用@Mapper及@MapperScan批量扫描接口的注入的方式,这种方式会使用mybatis的注解。

Springboot整合mybatis是通过mybatis-spring-boot-autoconfigure这个包来实现的。
spring.factories如下:

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

MybatisAutoConfiguration的代码如下:

// 标记这个类是一个属性类
@org.springframework.context.annotation.Configuration
// 设置该类必须在SqlSessionFactory和SqlSessionFactoryBean类型在classpath中存在才能注册
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
// 设置该类必须在DataSource存在容器中存在才能注册
@ConditionalOnBean(DataSource.class)
// 开启properties的映射
@EnableConfigurationProperties(MybatisProperties.class)
// 该类型在DataSourceAutoConfiguration注册之后才能注册
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
  // 对应properties中的相关属性的映射
  private final MybatisProperties properties;
  // plugin
  private final Interceptor[] interceptors;
  // 资源加载器
  private final ResourceLoader resourceLoader;
 
  private final DatabaseIdProvider databaseIdProvider;

  private final List<ConfigurationCustomizer> configurationCustomizers;

  public MybatisAutoConfiguration(MybatisProperties properties,
                                  ObjectProvider<Interceptor[]> interceptorsProvider,
                                  ResourceLoader resourceLoader,
                                  ObjectProvider<DatabaseIdProvider> databaseIdProvider,
                                  ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
  }
  // 校验configLocation是否被配置了
  @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()));
    }
    Configuration configuration = this.properties.getConfiguration();
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
      configuration = new Configuration();
    }
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
      for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
        customizer.customize(configuration);
      }
    }
    factory.setConfiguration(configuration);
    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 (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);
    }
  }

  /**
   * This will just scan the same base package as Spring Boot does. If you want
   * more power, you can explicitly use
   * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed
   * mappers working correctly, out-of-the-box, similar to using Spring Data JPA
   * repositories.
   */
  public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    private BeanFactory beanFactory;

    private ResourceLoader resourceLoader;
    // 扫描被@Mapper注解标记的类型,并注册BeanDefinition到容器中。
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

      logger.debug("Searching for mappers annotated with @Mapper");
      
      ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

      try {
        if (this.resourceLoader != null) {
          scanner.setResourceLoader(this.resourceLoader);
        }

        List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
        if (logger.isDebugEnabled()) {
          for (String pkg : packages) {
            logger.debug("Using auto-configuration base package '{}'", pkg);
          }
        }
        // 需要扫描的类型
        scanner.setAnnotationClass(Mapper.class);
        scanner.registerFilters();
        // 执行扫描
        scanner.doScan(StringUtils.toStringArray(packages));
      } catch (IllegalStateException ex) {
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
      }
    }
  }

  /**
   * {@link org.mybatis.spring.annotation.MapperScan} ultimately ends up
   * creating instances of {@link MapperFactoryBean}. If
   * {@link org.mybatis.spring.annotation.MapperScan} is used then this
   * auto-configuration is not needed. If it is _not_ used, however, then this
   * will bring in a bean registrar and automatically register components based
   * on the same component-scanning path as Spring Boot itself.
   */
  @org.springframework.context.annotation.Configuration
  // 需要执行的导入逻辑
  @Import({ AutoConfiguredMapperScannerRegistrar.class })
  // 如果容器中已经存在MapperFactoryBean则忽略该注册过程
  @ConditionalOnMissingBean(MapperFactoryBean.class)
  public static class MapperScannerRegistrarNotFoundConfiguration {

    @PostConstruct
    public void afterPropertiesSet() {
      logger.debug("No {} found.", MapperFactoryBean.class.getName());
    }
  }

}

这里先说明下该autoconfiguration的逻辑。

  • (1)、首先判断各种conditional都符合后会先根据@Bean注解标记的方法向容器中注册相应的BeanDefinition。比如sqlSessionFactory和sqlSessionTemple.
  • (2)、在注册MapperScannerRegistrarNotFoundConfiguration由于其使用了@Import注解,那么会执行AutoConfiguredMapperScannerRegistrar的registerBeanDefinitions方法。该方法会扫描类路径下全部被@Mapper标记的类型

注意:这里需要特殊注意下,一般Controller执行对象初始化会比Configuration中的类靠前,如果需要在属性中依赖到Configuration中的类,那么会提前初始化类型,如果不存在就看@Autowired注解是否必须依赖这个对象。如果必须依赖,就直接报错了。

解析被@Mapper标记的类型

使用了ClassPathBeanDefinitionScanner来实现扫描并注册BeanDefinition到容器中的操作。具体关于ClassPathBeanDefinitionScanner的原理后面的文章会专门分析。
首先来看下AutoConfiguredMapperScannerRegistrar扫描并注册被@Mapper类型标记的对象。

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 筛选出结果BeanDefinitionHoler
    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 {
      // 处理筛选出的BeanDefinition
      processBeanDefinitions(beanDefinitions);
    }
    
    return beanDefinitions;
}

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
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      // 将BeanDefinition设置为MapperFactoryBean
      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() + "'.");
        }
        // 将该BeanDefinition自动加载的方式设置为通过类型方式自动注入
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
}

上面比较重要的是将被@Mapper注解标记的类型的BeanDefinition的class设置为MapperFactoryBean。
对于注册部分到这里已经完成了。下面分析下MapperFactoryBean类型。

MapperFactoryBean

对于Springboot中操作的用户定义的interface都是对应的MapperFactoryBean的getObject返回的类型。结构图如下:
image.png

因为MapperFactoryBean涉及到关于Spring自动注入的内容,这里补充下:
spring里面可以设置BeanDefinition自动注入类型,默认为AUTOWIRE_NO(不进行自动注入)。mybatis里面的扫描接口生成MapperFactoryBean的时候设置了

 definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

他这里是为了按类型自动注入SqlSessionFactory或者SqlSessionTemplate。

spring构造bean的时候会进行填充属性,调用了如下方法:

protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw);

内部有一段逻辑:

if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
				mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
			MutablePropertyValues newPvs = new MutablePropertyValues(pvs);

			// Add property values based on autowire by name if applicable.
			if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
				autowireByName(beanName, mbd, bw, newPvs);
			}

			// Add property values based on autowire by type if applicable.
			if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
				autowireByType(beanName, mbd, bw, newPvs);
			}

			pvs = newPvs;
		}

前面MapperFactoryBean的BeanDefinition已经设置成AUTOWIRE_BY_TYPE,所以会调用autowireByType方法,该方法内部逻辑为获取当前bean的所有PropertyDescriptor,并且过滤出包含有WriteMethod的PropertyNames。

获取一个bean的PropertyDescriptor示例代码如下:

public class IntrospectorTest {

    /**
     * PropertyDescriptor依赖字段的set和get方法
     * 没有对应的set和get方法则没有对应的read和write方法
     *
     * 依赖于set和get方法,跟具体的字段名没关系
     *
     * @throws IntrospectionException
     */
    @Test
    public void testPropertyDescriptors() throws IntrospectionException {
        BeanInfo beanInfo = Introspector.getBeanInfo(IntrospectorTest.class);
        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor pd : pds) {
            if (pd.getName().equals("class")) {
                continue;
            }
            System.out.println(pd.getName());
            System.out.println(pd.getReadMethod());
            System.out.println(pd.getWriteMethod());
            System.out.println("********");
        }
    }

    public void setName(String name){}

}

然后从获取的PropertyNames迭代,获取相应WriteMethod的入参类型,并从spring容器获取相应类型的Bean,如果获取到设置到MutablePropertyValues里。

最后调用方法:

applyPropertyValues(beanName, mbd, bw, pvs);

迭代MutablePropertyValues的PropertyValue,内部最终调用构造Bean的setXxx方法进行注入。

总结:spring的PropertyValues注入都是通过setXxx方法设置,比如xml配置的property或者BeanDefinition的getPropertyValues().add(key,value)方法。

继续按照上面的逻辑说明逻辑:
因为MapperFactoryBean实现了FactoryBean,所以在创建MapperFactoryBean对象时实际上会调用getObject()方法:

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

这里的getSqlSession()方法实际上是之前通过setter方法注入的SqlSessionFactory或者SqlSessionTemplate。
代码如下:

public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
}

public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
}

由此可以获取到SqlSession对象。
之后的getMapper就属于mybatis的获取接口动态代理对象的部分内容。
对于SqlSessionFactoryBean配置部分内容不再详细说明,这里详细说明下SqlSessionTemple,该类的结构如下:
image.png

SqlSessionTemple使用了代理模式和动态代理技术。代码如下:

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;
    // 使用动态代理,通过SqlSessionInterceptor。
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
}

SqlSessionInterceptor代码如下:

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // 获取真正处理数据库操作的sqlSession
  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)) {
      // force commit even on non-dirty sessions because some databases require
      // a commit/rollback before calling close()
      // 提交数据库操作
      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 {
    if (sqlSession != null) {
      // 关闭sqlSession
      closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
    }
  }
}