Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Mybatis插件扩展以及与Spring整合原理

Mybatis插件扩展以及与Spring整合原理

作者头像
夜勿语
发布于 2020-09-07 03:24:07
发布于 2020-09-07 03:24:07
92700
代码可运行
举报
文章被收录于专栏:Java升级之路Java升级之路
运行总次数:0
代码可运行

文章目录

  • 前言
  • 正文
    • 插件扩展
      • 1. Interceptor核心实现原理
      • 2. Mybatis的拦截增强
    • Mybatis与Spring整合原理
      • 1. SqlSessionFactory的创建
      • 2. 扫描Mapper并创建代理对象
      • 3. 如何整合Spring事务
      • 4. FactoryBean的扩展知识
  • 总结

前言

前面几篇文章分析了Mybatis的核心原理,但模块较多,没有一一分析,更多的需要读者自己下来研究。不过Mybatis的插件扩展机制还是非常重要的,像PageHelper就是一个扩展插件,熟悉其扩展原理,才能更好的针对我们的业务作出更合适的扩展。另外,现在Mybatis都是和Spring/SpringBoot一起使用,那么Mybatis又是如何与它们进行整合的呢?一切答案尽在本文之中。

正文

插件扩展

1. Interceptor核心实现原理

熟悉Mybatis配置的都知道,在xml配置中我们可以配置如下节点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  <plugins>
    <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
      <property name="pluginProperty" value="100"/>
    </plugin>
  </plugins>

这个就是插件的配置,那么自然而然的这个节点就会在解析xml的时候进行解析,并将其添加到Configuration中。细心的读者应该还记得下面这段代码,在XMLConfigBuilderl类中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
     //解析<properties>节点
      propertiesElement(root.evalNode("properties"));
      //解析<settings>节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //解析<typeAliases>节点
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析<plugins>节点
      pluginElement(root.evalNode("plugins"));
      //解析<objectFactory>节点
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析<objectWrapperFactory>节点
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析<reflectorFactory>节点
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);//将settings填充到configuration
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析<environments>节点
      environmentsElement(root.evalNode("environments"));
      //解析<databaseIdProvider>节点
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析<typeHandlers>节点
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析<mappers>节点
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

其中pluginElement就是解析插件节点的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      //遍历所有的插件配置
      for (XNode child : parent.getChildren()) {
    	//获取插件的类名
        String interceptor = child.getStringAttribute("interceptor");
        //获取插件的配置
        Properties properties = child.getChildrenAsProperties();
        //实例化插件对象
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        //设置插件属性
        interceptorInstance.setProperties(properties);
        //将插件添加到configuration对象,底层使用list保存所有的插件并记录顺序
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

从上面可以看到,就是根据配置实例化为Interceptor对象,并添加到InterceptorChain中,该类的对象被Configuration持有。Interceptor包含三个方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  //执行拦截逻辑的方法
  Object intercept(Invocation invocation) throws Throwable;

  //target是被拦截的对象,它的作用就是给被拦截的对象生成一个代理对象
  Object plugin(Object target);

  //读取在plugin中设置的参数
  void setProperties(Properties properties);

InterceptorChain只是保存了所有的Interceptor,并提供方法给客户端调用,使得所有的Interceptor生成代理对象

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

可以看到pluginAll就是循环去调用了Interceptorplugin方法,而该方法的实现一般是通过Plugin.wrap去生成代理对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public static Object wrap(Object target, Interceptor interceptor) {
	//解析Interceptor上@Intercepts注解得到的signature信息
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();//获取目标对象的类型
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//获取目标对象实现的接口
    if (interfaces.length > 0) {
      //使用jdk的方式创建动态代理
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

其中getSignatureMap就是将@Intercepts注解中的value值解析并缓存起来,该注解的值是@Signature类型的数组,而这个注解可以定义class类型方法参数,即拦截器的定位。而getAllInterfaces就是获取要被代理的接口,然后通过JDK动态代理创建代理对象,可以看到InvocationHandler就是Plugin类,所以直接看invoke方法,最终就是调用interceptor.intercept方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      //获取当前接口可以被拦截的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {//如果当前方法需要被拦截,则调用interceptor.intercept方法进行拦截处理
        return interceptor.intercept(new Invocation(target, method, args));
      }
      //如果当前方法不需要被拦截,则调用对象自身的方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

这里的插件实现思路是通用的,即这个interceptor我们可以用来扩展任何对象的任何方法,比如对Mapget进行拦截,可像下面这样实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  @Intercepts({
      @Signature(type = Map.class, method = "get", args = {Object.class})})
  public static class AlwaysMapPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
      return "Always";
    }

    @Override
    public Object plugin(Object target) {
      return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
  }

然后在使用Map时先用插件对其包装,这样拿到的就是Map的代理对象。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    Map map = new HashMap();
    map = (Map) new AlwaysMapPlugin().plugin(map);

2. Mybatis的拦截增强

因为我们可以对Mybatis扩展任意多个的插件,所以它使用InterceptorChain对象来保存所有的插件,这是责任链模式的实现。那么Mybatis到底会拦截哪些对象和哪些方法呢?回忆上篇文章我们就可以发现Mybatis只会对以下4个对象进行拦截:

  • Executor
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
	......省略
	
    //通过interceptorChain遍历所有的插件为executor增强,添加插件的功能
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
  • StatementHandler
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	//创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
	StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
  • ParameterHandler
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }
  • ResultSetHandler
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

而具体要拦截哪些对象和哪些方法则是由@Intercepts和@Signature指定的。

以上就是Mybatis扩展插件的实现机制,读者可据此自行分析下PageHelper的实现原理。另外需要注意,我们在进行自定义插件开发时,尤其要谨慎。因为直接关系到操作数据库,如果对插件的实现原理不透彻,很有可能引发难以估量的后果。

Mybatis与Spring整合原理

前面的示例都是单独使用Mybatis,可以看到需要创建SqlSessionFactorySqlSession对象,然后通过SqlSession去创建Mapper接口的代理对象,所以在与Spring整合时,显而易见的,我们就需要考虑以下几点:

  • 什么时候创建以及怎么创建SqlSessionFactorySqlSession
  • 什么时候创建以及怎么创建代理对象?
  • 如何将Mybatis的代理对象注入到IOC容器中?
  • Mybatis怎么保证和Spring在同一个事务中并且使用的是同一个连接?

那么如何实现以上几点呢?下文基于mybatis-spring-1.3.3版本分析。

1. SqlSessionFactory的创建

熟悉Spring源码的(如果不熟悉,可以阅读我之前的Spring系列源码)都知道Spring最重要的那些扩展点:

  • BeanDefinitionRegistryPostProcessor:Bean实例化前调用
  • BeanFactoryPostProcessor:Bean实例化前调用
  • InitializingBean:Bean实例化后调用
  • FactoryBean:实现该接口代替Spring管理一些特殊的Bean

其它还有很多,以上列举出来的就是Mybatis集成Spring所用到的扩展点。首先我们需要实例化SqlSessionFactory,而实例化该对象在Mybatis里实际上就是去解析一大堆配置并封装到该对象中,所以我们不能简单的使用<bean>标签来配置,为此Mybatis实现了一个类SqlSessionFactoryBean(这个类我们在以前使用整合包时都会配置),之前XML中的配置都以属性的方式放入到了该类中:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="typeAliasesPackage" value="com.enjoylearning.mybatis.entity" />
		<property name="mapperLocations" value="classpath:sqlmapper/*.xml" />
	</bean>

进入这个类,我们可以看到它实现了InitializingBeanFactoryBean接口,实现第一个接口的作用就是在该类实例化后立即去执行配置解析的阶段:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  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();
  }

具体的解析就在buildSqlSessionFactory方法中,这个方法比较长,但不复杂,这里就不贴代码了。而实现第二接口的作用就在于Spring获取该类实例时实际上会通过getObject方法返回SqlSessionFactory的实例,通过这两个接口就完成了SqlSessionFactory的实例化。

2. 扫描Mapper并创建代理对象

在整合之后我们除了要配置SqlSessionFactoryBean外,还要配置一个类:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 	 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.enjoylearning.mybatis.mapper" />
	</bean>

这个类的作用就是用来扫描Mapper接口的,并且这个类实现了BeanDefinitionRegistryPostProcessorInitializingBean,这里实现第二个接口的作用主要是校验有没有配置待扫描包的路径

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public void afterPropertiesSet() throws Exception {
    notNull(this.basePackage, "Property 'basePackage' is required");
  }

主要看到postProcessBeanDefinitionRegistry方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  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.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

这里创建了一个扫描类,而这个扫描类是继承自Spring的ClassPathBeanDefinitionScanner,也就是会将扫描到的类封装为BeanDefinition注册到IOC容器中去:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

		// Register annotation config processors, if necessary.
		if (this.includeAnnotationConfig) {
			AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
		}

		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}

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

  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
      definition.setBeanClass(this.mapperFactoryBean.getClass());

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

	  // 当指定了sqlSessionFactoryBeanName或sqlSessionFactory或sqlSessionTemplateBeanName或sqlSessionTemplate ,将其注入到mapperFactoryBean中
      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;
      }
	
	  // 没有指定时,则通过类型自动注入sqlSession
      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

你可能会好奇,在哪里生成的代理对象?只是将Mapper接口注入到IOC有什么用呢?其实关键代码就在definition.setBeanClass(this.mapperFactoryBean.getClass()),这句代码的作用就是将每一个Mapper接口都转为MapperFactoryBean类型。 为什么要这么转呢?进入这个类你会发现它也是实现了FactoryBean接口的,所以自然而然的又是利用它来创建代理实现类对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

3. 如何整合Spring事务

Mybatis作为一个ORM框架,它是有自己的数据源和事务控制的,而Spring同样也会配置这两个,那么怎么将它们整合到一起呢?而不是在Service类调用Mapper接口时就切换了数据源和连接,那样肯定是不行的。 在使用Mybatis时,我们可以在xml中配置TransactionFactory事务工厂类,不过一般都会使用默认的JdbcTransactionFactory,而当与Spring整合后,默认的事务工厂类改为了SpringManagedTransactionFactory。回到SqlSessionFactoryBean读取配置的方法,在该方法中有下面这样一段代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

	 configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

上面默认创建了SpringManagedTransactionFactory,同时还将我们xml中ref属性引用的dataSource添加到了Configuration中,这个工厂会创建下面这个事务控制对象:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
    return new SpringManagedTransaction(dataSource);
  }

而这个方法是在DefaultSqlSessionFactory获取SqlSession时会调用:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

这就保证使用的是同一个数据源对象,但是怎么保证拿到的是同一个连接和事务呢?关键就在于SpringManagedTransaction获取连接是怎么实现的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  public Connection getConnection() throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }

  private void openConnection() throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug(
          "JDBC Connection ["
              + this.connection
              + "] will"
              + (this.isConnectionTransactional ? " " : " not ")
              + "be managed by Spring");
    }
  }

这里委托给了DataSourceUtils获取连接:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
		try {
			return doGetConnection(dataSource);
		}
		catch (SQLException ex) {
			throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
		}
	}

	public static Connection doGetConnection(DataSource dataSource) throws SQLException {
		Assert.notNull(dataSource, "No DataSource specified");

		ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
		if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
			conHolder.requested();
			if (!conHolder.hasConnection()) {
				logger.debug("Fetching resumed JDBC Connection from DataSource");
				conHolder.setConnection(dataSource.getConnection());
			}
			return conHolder.getConnection();
		}
		// Else we either got no holder or an empty thread-bound holder here.

		logger.debug("Fetching JDBC Connection from DataSource");
		Connection con = dataSource.getConnection();

		if (TransactionSynchronizationManager.isSynchronizationActive()) {
			logger.debug("Registering transaction synchronization for JDBC Connection");
			// Use same Connection for further JDBC actions within the transaction.
			// Thread-bound object will get removed by synchronization at transaction completion.
			ConnectionHolder holderToUse = conHolder;
			if (holderToUse == null) {
				holderToUse = new ConnectionHolder(con);
			}
			else {
				holderToUse.setConnection(con);
			}
			holderToUse.requested();
			TransactionSynchronizationManager.registerSynchronization(
					new ConnectionSynchronization(holderToUse, dataSource));
			holderToUse.setSynchronizedWithTransaction(true);
			if (holderToUse != conHolder) {
				TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
			}
		}

		return con;
	}

看到ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource)这段代码相信熟悉Spring源码的已经知道了,这个我在分析Spring事务源码时也讲过,通过DataSource对象拿到当前线程绑定的ConnectionHolder,这个对象是在Spring开启事务的时候存进去的。至此,关于Spring和Mybatis的整合原理我们就个搞清楚了,至于和SpringBoot的整合,读者可自行分析。最后,我再分享一个小扩展知识。

4. FactoryBean的扩展知识

很多读者可能不知道这个接口有什么作用,其实很简单,当我们有某个类由Spring实例化比较复杂,想要自己控制它的实例化时,就可以实现该接口。而实现该接口的类首先会被实例化并放入一级缓存,而当我们依赖注入我们真正想要的类时(如Mapper接口的代理类),就会从一级缓存中拿到FactoryBean实现类的实例,并判断是否实现了FactoryBean接口,如果是就会调用getObject方法返回我们真正想要的实例。 那如果我们确实想要拿到的就是FactoryBean实现类的实例该怎么办呢?只需要在传入的beanName前面加上“&”符号即可。

总结

本篇分析了Mybatis如何扩展插件以及插件的实现原理,但如非必要,切忌扩展插件,如果一定要,那么一定要非常谨慎。另外还结合Spirng的扩展点分析了Mybatis和Spring的整合原理,解决了困在我心中已久的一些疑惑,相信那也是大多数读者的疑惑,好好领悟这部分内容非常有利于我们自己对Spring进行扩展。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/07/09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Mybatis之Spring与Mybatis的事物transaction
    mybatis-3.4.6.release, mybatis-spring-1.3.2.release.
克虏伯
2019/10/28
1.2K0
MyBatis 源码
MyBatis 工作流程:应用程序首先加载mybatis-config.xml 配置文件,并根据配置文件的内容创建 SqlSessionFactory 对象;然后,通过 SqlSessionFactory 对象创建 SqlSession 对象,SqlSession 接口中定义了执行 SQL 语句所需要的各种方法。之后,通过 SqlSession 对象执行映射配置文件中定义的 SQL 语句,完成相应的数据操作。最后通过 SqlSession 对象提交事务,关闭 SqlSession 对象,整个过程具体实现如下:就按照下面的流程进行源码分析
Java架构师必看
2021/05/14
3360
MyBatis 源码
MyBatis源码分析
接下来我们就开始MyBatis的源码之旅,首先大家要从宏观上了解Mybatis的整体框架分为三层,分别是基础支持层、核心处理层、和接口层。如下图
Java鱼头
2022/11/27
5201
Mybatis拦截器执行过程解析
上一篇文章 Mybatis拦截器之数据加密解密 介绍了 Mybatis 拦截器的简单使用,这篇文章将透彻的分析 Mybatis 是怎样发现拦截器以及调用拦截器 intercept 方法的
用户4172423
2019/09/04
1.3K0
Mybatis拦截器执行过程解析
mybatis插件机制源码解析
首先需要说明的是,本篇文章不是mybatis插件开发的教程,而是从源码层面分析mybatis是如何支持用户自定义插件开发的。
用户7634691
2021/10/19
4460
mybatis插件机制源码解析
MyBatis 架构与原理深入解析,面试随便问!
本文主要讲解JDBC怎么演变到Mybatis的渐变过程,重点讲解了为什么要将JDBC封装成Mybaits这样一个持久层框架。再而论述Mybatis作为一个数据持久层框架本身有待改进之处。
搜云库技术团队
2023/09/18
6780
MyBatis 架构与原理深入解析,面试随便问!
Mybatis源码解析-执行流程(旧)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Java微观世界
2025/01/20
1310
Mybatis源码解析-执行流程(旧)
每天用Mybatis,但是Mybatis的工作原理你真的知道吗?
近来想写一个mybatis的分页插件,但是在写插件之前肯定要了解一下mybatis具体的工作原理吧,于是边参考别人的博客,边看源码就开干了。
程序员追风
2019/11/08
3010
每天用Mybatis,但是Mybatis的工作原理你真的知道吗?
聊聊mybatis的Interceptor机制
org/apache/ibatis/plugin/Interceptor.java
code4it
2023/08/31
1730
聊聊mybatis的Interceptor机制
MyBatis从入门到精通(七)—源码剖析之Configuration、SqlSession、Executor、StatementHandler细节
MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使用org.apache.ibatis.session.Configuration 实例来维护。 下⾯进⼊对配置⽂件解析部分:
共饮一杯无
2022/11/28
1.1K0
怒肝一夜 | Mybatis源码深度解析
前面已经发过Mybatis源码解析的文章了,本文是对前面文章进行精简以及部分调整优化,总结出来的一篇万字Mybatis源码分析。
田维常
2021/01/13
6.7K0
怒肝一夜 | Mybatis源码深度解析
mybatis 开发自定义插件,你学废了吗
MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。比如执行前、执行后或者对SQL结果集处理、sql入参处理等,这样就可以在不修改mybatis源码的情况下对sql执行的过程或结果进行修改,实现了解耦。
索码理
2022/09/20
5820
mybatis 开发自定义插件,你学废了吗
长文警告!springboot mybatis 源码
springboot-mybatis 整合了 mybatis 对数据库的访问操,其为我做了很好的封装,开箱即用。只要在项目中引入 maven的依赖,打上 MapperScan 的注解就可以进入我们的业务开发。
用户5546570
2020/11/12
7310
MyBatis源码阅读(九) --- 插件原理
插件功能也是Mybatis框架中的一个核心功能,Mybatis提供了自定义插件功能来帮我们扩展个性化业务需求。本篇文章我们将总结Mybatis的插件机制以及如何自定义一个插件。
终有救赎
2024/01/30
1800
MyBatis源码阅读(九) --- 插件原理
spring-boot-2.0.3不一样系列之源码篇 - pageHelper分页,绝对有值得你看的地方
  用过pageHelper的都知道(没用过的感觉去google下),实现分页非常简单,service实现层调用dao(mapper)层之前进行page设置,mapper.xml中不处理分页,这样就够了,就能实现分页了,具体如下
青石路
2019/03/11
8750
spring-boot-2.0.3不一样系列之源码篇 - pageHelper分页,绝对有值得你看的地方
从零开始实现一个MyBatis加解密插件
公司出于安全合规的考虑,需要对明文存储在数据库中的部分字段进行加密,防止未经授权的访问以及个人信息泄漏。
2020labs小助手
2022/08/23
8630
mybatis源码解析
文章目录 1. Configuration 2. MapperRegistry 3. MappedStatement 4. MapperProxyFactory 5. MapperProxy 6. MapperMethod 7. BoundSql 8. ResultHandler 9. TypeHandler 10. Mybatis四大关键类 10.1. Executor 10.2. ParameterHandler 10.3. ResultSetHandler 10.4. StatementHandl
爱撒谎的男孩
2019/12/31
4600
Mybatis源码阅读(一) 配置文件的加载及查询过程
首先在 MyBatis 启动的时候我们要去解析配置文件,包括全局配置文件和映射器配置文件,这里面包含了我们怎么控制 MyBatis 的行为,和我们要对数据库下达的指令,也就是我们的 SQL 信息。我们会把它们解析成一个 Configuration 对象。
源码之路
2021/12/16
9680
Mybatis源码阅读(一) 配置文件的加载及查询过程
Mybatis源码学习(四)拦截器与插件原理
回顾前几文加载mybatis时,会通过sqlSessionFactoryBuilder的build方法对xml文件进行解析,解析成document树后,再依次对树中的XNode结点进行解析,如xml配置中的plugins、environments、mappers、typeHandlers等基础配置信息,初始化后赋值给configuration,解析结束。
虞大大
2020/09/01
7800
美团面试官:你说你们公司的Mybatis分页插件是你写的,给我说说它的设计原理?
大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外。
乔戈里
2020/02/12
4120
推荐阅读
相关推荐
Mybatis之Spring与Mybatis的事物transaction
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验