Mybatis源码解析(一) —— mybatis与Spring是如何整合的?

  从大学开始接触mybatis到如今差很少快3年了吧,最近寻思着使用3年了,我却还不清楚其内部实现细节,好比:html

  • 它是如何加载各类mybatis相关的xml?面试

  • 它是如何仅仅经过一个Mapper接口 + Mappe.xml实现数据库操做的(尽管不少人可能都清楚是经过代理实现,但面试时一旦深刻询问:好比Mapper的代理类名是什么?是经过JDK仍是cglib实现?)?spring

  • 在同一个方法中,Mybatis屡次请求数据库,是否要建立多个SqlSession会话?sql

  • 它与Spring是如何适配(整合)的?数据库

  • 在Spring中是如何保障SqlSession的生命周期的?安全

  • 等等一系列的问题。。。bash

  若是以上问题你自认为没法回答,或者说了解一些,那么就从如今开始,咱们来一一揭开这层面纱。session

1、Mybatis:最简单测试Demo

  相信只要用过Mybatis的同窗看到下面的代码必定不会陌生,若是不清楚的能够看下官网文档mybatis

String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

    // 一、目前流行方式
    try (SqlSession session = sqlSessionFactory.openSession()) {
        UserMapper mapper = session.getMapper(UserMapper.class);
        User user = mapper.selectById(101);
    }
    // 二、之前流行方式
    try (SqlSession session = sqlSessionFactory.openSession()) {
        User user =  sqlSession.selectOne("xxx.UserMapper.selectById", "101");;
    }    
    
复制代码

  示列代码演示了Mybatis进行一次数据库操做的过程,大体分为(针对目前流行方式,其实之前的使用和目前流行的使用方式实现原理同样):app

  • 一、 经过 SqlSessionFactoryBuilder 将读取到的配置资源 build 生成 SqlSessionFactory

  • 二、 经过 SqlSessionFactory 的 openSession() 获取到 SqlSession

  • 三、 经过 SqlSession 获取到 Mapper的代理对象(MapperProxy)

  • 四、 经过 Mapper 进行 数据库请求操做

SqlSession、SqlSessionFactory、SqlSessionFactoryBuilder

  咱们能够轻易的发现每次去请求数据库操做都须要经过 SqlSessionFactory 去获取到 SqlSession,而 SqlSessionFactory 是经过 SqlSessionFactoryBuilder 构造出来的, 而且最后请求操做完成后都关闭了SqlSession。所以,不可贵出:

  • SqlSessionFactory 一个应用程序中最好只有1个,即单列。
  • SqlSessionFactoryBuilder 只有一个做用: 建立 SqlSessionFactory对象。
  • 一个SqlSession应该仅存活于一个业务请求中,也能够说一个SqlSession对应一次数据库会话,它不是永久存活的,每次访问数据库时都须要建立它,而且访问完成后都必须执行会话关闭

  针对这3个类以及mapper的做用域(Scope)和生命周期的描述,我的以为官方文档写得很清楚:

SqlSessionFactoryBuilder

这个类能够被实例化、使用和丢弃,一旦建立了 SqlSessionFactory,就再也不须要它了。 所以 SqlSessionFactoryBuilder 实例的最佳做用域是方法做用域(也就是局部方法变量)。 你能够重用 SqlSessionFactoryBuilder 来建立多个 SqlSessionFactory 实例,可是最好仍是不要让其一直存在,以保证全部的 XML 解析资源能够被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被建立就应该在应用的运行期间一直存在,没有任何理由丢弃它或从新建立另外一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复建立屡次,屡次重建 SqlSessionFactory 被视为一种代码“坏味道(bad smell)”。所以 SqlSessionFactory 的最佳做用域是应用做用域。 有不少方法能够作到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

每一个线程都应该有它本身的 SqlSession 实例。SqlSession 的实例不是线程安全的,所以是不能被共享的,因此它的最佳的做用域是请求或方法做用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也毫不能将 SqlSession 实例的引用放在任何类型的托管做用域中,好比 Servlet 框架中的 HttpSession。 若是你如今正在使用一种 Web 框架,要考虑 SqlSession 放在一个和 HTTP 请求对象类似的做用域中。 换句话说,每次收到的 HTTP 请求,就能够打开一个 SqlSession,返回一个响应,就关闭它。 这个关闭操做是很重要的,你应该把这个关闭操做放到 finally 块中以确保每次都能执行关闭。 依赖注入框架能够建立线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,所以能够直接忽略它们的生命周期。 若是对如何经过依赖注入框架来使用 MyBatis 感兴趣,能够研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。

映射器实例(Mapper实例) 映射器是一些由你建立的、绑定你映射的语句的接口。映射器接口的实例是从 SqlSession 中得到的。所以从技术层面讲,任何映射器实例的最大做用域是和请求它们的 SqlSession 相同的。尽管如此,映射器实例的最佳做用域是方法做用域。 也就是说,映射器实例应该在调用它们的方法中被请求,用过以后便可丢弃。 并不须要显式地关闭映射器实例,尽管在整个请求做用域保持映射器实例也不会有什么问题,可是你很快会发现,像 SqlSession 同样,在这个做用域上管理太多的资源的话会难于控制。 为了不这种复杂性,最好把映射器放在方法做用域内。就像示列代码同样。 若是SqlSession是注入的,那么映射器实例也可经过依赖注入,而且可忽略其生命周期。

2、Mybatis-Spring:将MyBatis代码无缝地整合到Spring

  前面是学习mybatis常看到的一种代码,但缺点也很明显: 每次请求都得建立SqlSession,而且Mapper的代理类是经过SqlSession获取(说明耦合度很高),也就意味着每次请求都得建立一个新的Mapper代理类。为了整合Spring,而且解决前面问题,因此Mybatis-Spring 子项目来袭。

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将容许 MyBatis 参与到 Spring 的事务管理之中,建立映射器 mapper 和 SqlSession 并注入到 bean中

  上面是 Mybatis-Spring的官方介绍,其中 容许 MyBatis 参与到 Spring 的事务管理之中,建立映射器 mapper 和 SqlSession 并注入到 bean中 是咱们本次解析的关键点。那么开始分析吧!

SqlSessionFactoryBean 、 MapperScannerConfigurer

  在Spring项目中应用了Mybatis都会有下面的2个bean配置,这2个配置就是实现xml加载、mapper和SqlSession注入的起始配置。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    </bean>

    <!-- DAO接口所在包名,Spring会自动查找其下的类 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="xxx.dao"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>

复制代码

1、 SqlSessionFactoryBean : 加载xml及build SqlSessionFactory对象

  从配置中咱们能够看的 SqlSessionFactoryBean 配置了数据源、mapper的xml路径、mybatis-config的xml路径。所以,不难想象,SqlSessionFactoryBean 内部实现了xml配置文件的加载及SqlSessionFactory对象的建立。咱们来看下 SqlSessionFactoryBean继承关系图形:

SqlSessionFactoryBean继承关系图形

在继承关系图中,咱们发现了 InitializingBean、FactoryBean 的身影,可能清楚这个的同窗,大概已经猜到了确定有 afterPropertiesSet() 来建立 SqlSessionFactory 对象 和 getObject() 来获取 SqlSessionFactory 对象 。 话很少说,先看下getObject()实现:

public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }
  
复制代码

  getObject()相对简单,咱们都知道FactoryBean子类都是经过getObject()来获取到实际的Bean对象,这里也就是SqlSessionFactory。从源码中咱们看到当 sqlSessionFactory为null会去调用 afterPropertiesSet(),因此 SqlSessionFactory 确定是由 afterPropertiesSet() 来实现建立的。继续看afterPropertiesSet()实现:

public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

复制代码

  afterPropertiesSet() 内部首先 验证了 dataSource 和 sqlSessionFactoryBuilder 部位null,最后调用 buildSqlSessionFactory()方法获取到 SqlSessionFactory 对象,并赋值到类字段属性 sqlSessionFactory 。 继续查看buildSqlSessionFactory()源码:

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    // 省略了 SqlSessionFactoryBean 的属性(好比:ObjectFactory )赋值到 Configuration 对象中的操做
    //  1 Configuration : Mybatis的核心类之一,主要存放读取到的xml数据,包括mapper.xml 
    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configLocation != null) {
      //  2 建立  xmlConfigBuilder 对象 : 用于解析 mybatis-config.xml 数据
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      configuration.setVariables(this.configurationProperties);
    }

    if (xmlConfigBuilder != null) {
      try {
        //  3  XmlConfigBuilder 解析方法执行 
        xmlConfigBuilder.parse();
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }
    
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }
        try {
          //  4 建立  XMLMapperBuilder 对象 : 用于解析 mapper.xml 数据
          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();
        }
      }
    } 
    // 5 经过 SqlSessionFactoryBuilder bulid  SqlSessionFactory 对象
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

复制代码

   整个 buildSqlSessionFactory() 源码主要有如下几个重要的点:

  • 一、 XMLConfigBuilder ,经过调用其 parse() 方法来 解析 mybatis-config.xml 配置(若是 配置有 mapper.xml ,其会经过 XMLMapperBuilder 进行解析加载),并将解析的数据赋值到 Configuration(Mybatis的核心类之一,主要存放读取到的xml数据,包括mapper.xml,该类贯穿整个mybatis,足以见得其重要性)

  • 二、 XMLMapperBuilder : 经过调用其 parse() 方法来 解析 mapper.xml 配置, 并将解析的数据赋值到 Configuration

  • 三、 将存放有解析数据的 Configuration 做为 sqlSessionFactoryBuilder.build() 参数,建立 sqlSessionFactory 对象。

至此

2、 MapperScannerConfigurer :扫描Mapper接口路径,将 Mapper 偷梁换柱成 MapperFactoryBean

  MapperScannerConfigurer 是 mybatis-spring 项目中为了实现方便加载Mapper接口,以及将 Mapper 偷梁换柱成 MapperFactoryBean。查看 MapperScannerConfigurer 源码,先看下其继承关系图:

MapperScannerConfigurer继承关系图形

   从中咱们其继承了 BeanDefinitionRegistryPostProcessor 接口,熟悉Spring 的同窗应该 已经大体想到了 其如何将 Mapper 偷梁换柱成 MapperFactoryBean 了。话很少说,咱们来看看 MapperScannerConfigurer 是如何实现 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 方法:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    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.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

复制代码

   咱们能够发现整个方法内部其实就是经过 ClassPathMapperScanner 的 scan() 方法,查看 scan() 实现,发现其内部调用了关键方法 doScan(),那么咱们来看下 doScan() 方法实现:

@Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    // 一、调用父类 ClassPathBeanDefinitionScanner的 doScan方法 加载路径下全部的mapper接口生成对应的 BeanDefinition 
    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 {
      for (BeanDefinitionHolder holder : beanDefinitions) {
        GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

        // 二、 设置 被代理的 Bean(也就是Mapper) 的class信息
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
        // 三、 偷梁换柱成 MapperFactoryBean 
        definition.setBeanClass(MapperFactoryBean.class);

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

        boolean explicitFactoryUsed = false;
        // 四、 设置 sqlSessionFactory 
        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;
        }
        
        // 五、 设置 sqlSessionTemplate
        if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
          definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionTemplate != null) {
          definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
          explicitFactoryUsed = true;
        }
        
        if (!explicitFactoryUsed) {
          definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
      }
    }

    return beanDefinitions;
  }


复制代码

   整个方法分为3个部分:

  • 一、 调用父类 ClassPathBeanDefinitionScanner的 doScan()方法 加载路径下全部的mapper接口生成对应的 BeanDefinition

  • 二、 经过definition.setBeanClass(MapperFactoryBean.class) 偷梁换柱成 MapperFactoryBean

  • 三、 经过 definition.getPropertyValues().add() 添加 MapperFactoryBean 所需的 字段或者方法参数信息 : sqlSessionFactory 、 mapperInterface等

   至此 MapperScannerConfigurer 的使命已经完成, 至于 MapperFactoryBean 的建立就彻底交给Spring来完成了。

3、 MapperFactoryBean 、SqlSessionTemplate:Mapper与SqlSession解耦的利器

   咱们知道在mybatis中,Mapper是经过 SqlSession建立的,而SqlSession的生命周期仅仅在一次会话中,那么按照这种设计,每一次会话都要去建立SqlSession,而后再经过SqlSession去建立Mapper。咱们知道Mapper其实没有必要每次都去建立,它更加适合做为一个单列对象。那么怎么将SqlSession和Mapper解耦呢? 在mybatis-spring项目中经过 MapperFactoryBean 、SqlSessionTemplate 来实现的。接下来咱们就来解析它们。

MapperFactoryBean

   正如前面咱们所看到的同样,MapperFactoryBean 其实能够理解为 Mapper的代理工厂Bean,咱们能够经过 MapperFactoryBean 的方法获取到 Mapper的代理对象。先来看下 MapperFactoryBean继承关系 :

MapperFactoryBean继承关系图

   咱们能够看到 MapperFactoryBean 实现了 FactoryBean, 那么 确定经过 实现 getObject() 获取到 Mapper的代理对象,查看源码以下:

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

复制代码

   其内部就是咱们熟悉的 getSqlSession().getMapper() 建立Mapper代理对象的方法。熟悉Spring 的同窗都知道 在Bean加载的过程当中若是发现当前Bean对象是 FactoryBean 会去 调用getObject() 获取真正的Bean对象。不熟悉的同窗能够去看下 AbstractBeanFactory 的 getBean() 方法。

   可是彷佛仍是没有吧SqlSession和Mapper解耦的迹象呢?不着急,咱们继续看下 getSqlSession(), 发现其是 父类 SqlSessionDaoSupport 实现,咱们看下SqlSessionDaoSupport源码:

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

  //  建立 SqlSession子类 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;
  }

  
  public SqlSession getSqlSession() {
    return this.sqlSession;
  }

  ....

}

复制代码

   咱们发现咱们获取到的SqlSession实际上是其子类SqlSessionTemplate, 咱们查看其构造方法源码:

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());
  }
  
复制代码

   咱们能够清楚的发现,其内部维护了一个 SqlSession的字段 sqlSessionProxy ,其赋值的是代理对象 SqlSessionInterceptor。 咱们再来看下 SqlSessionInterceptor 的源码:

private class SqlSessionInterceptor implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 经过getSqlSession() 获取一个 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) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
  
复制代码

  咱们发现其代理实现时,经过getSqlSession() 获取一个 全新的SqlSession。也就是说建立Mapper的SqlSession和会话请求的SqlSession不是同一个。这里就完美的解耦了Mapper和SqlSession,而且保障了每次会话SqlSession的生命周期范围。

  这里超前提下: getSqlSession().getMapper() 其实 是经过 configuration.getMapper() 来获取的,那么就意味着 configuration内部必须添加了Mapper信息,那么configuration是什么时候添加的呢? 能够看下 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 (Throwable t) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
        throw new IllegalArgumentException(t);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }
  
复制代码

  因为父类实现了 InitializingBean 接口,而且其afterPropertiesSet() 调用了 checkDaoConfig() 方法 ,因此,至少在初始化建立MapperFactoryBean 时,就已经向 configuration内部必须添加了Mapper信息。

3、我的总结

  本文解析了Mybatis与Spring是如何整合的,其中的关键对象包括:

  • SqlSessionFactoryBuilder: 用于建立 SqlSessionFactory

  • SqlSessionFactory: 用于建立 SqlSession

  • SqlSession: Mybatis工做的最顶层API会话接口,全部访问数据库的操做都是经过SqlSession来的

  • Configuration: 存放有全部的mybatis配置信息,包括mapper.xml、 mybatis-config.xml等

  • XMLConfigBuilder: 解析 mybatis-config.xml 配置并存放到Configuration中

  • XMLMapperBuilder: 解析 mapper.xml 配置并存放到Configuration中

  • SqlSessionFactoryBean: mybatis整合Spring时的 生成 SqlSessionFactory 的FactoryBean

  • MapperScannerConfigurer: mybatis整合Spring时的 实现方便加载Mapper接口,以及将 Mapper 偷梁换柱成 MapperFactoryBean

  • MapperFactoryBean: 生成 Mapper 代理对象的FactoryBean

  • SqlSessionTemplate: 内部维护有 SqlSession 的代理对象,解耦Mapper和SqlSession的关键对象。

         若是您对这些感兴趣,欢迎star、follow、收藏、转发给予支持!

本文由博客一文多发平台 OpenWrite 发布!

相关文章
相关标签/搜索