Skip to content

Latest commit

 

History

History
345 lines (250 loc) · 14.2 KB

3.MyBatis插件原理及Spring集成.md

File metadata and controls

345 lines (250 loc) · 14.2 KB

MyBatis插件原理及Spring集成

Mybatis插件原理

1) 有哪些对象允许被代理?有哪些方法可以被拦截?

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

image-20220226231015055

2) 怎么样创建代理

3) 什么时候创建代理对象?是在 MyBatis 启动的时候创建,还是调用的时 候创建?

4) 被代理后,调用的是什么方法?怎么调用到原被代理对象的方法(比如 Executor 的 query()方法)?

插件编写与注册

1、编写自己的插件类

  • 1)实现 Interceptor 接口 这个是所有的插件必须实现的接口。

  • 2)添加@Intercepts({@Signature()}),指定拦截的对象和方法、方法参数 方法名称+参数类型,构成了方法的签名,决定了能够拦截到哪个方法。 问题:拦截签名跟参数的顺序有关系吗?

  • 3)实现接口的 3 个方

// 用于覆盖被拦截对象的原有方法(在调用代理对象 Plugin 的 invoke()方法时被调用)
Object intercept(Invocation invocation) throws Throwable;
// target 是被拦截对象,这个方法的作用是给被拦截对象生成一个代理对象,并返回它
Object plugin(Object target);
// 设置参数
void setProperties(Properties properties);

2、插件注册,在 mybatis-config.xml 中注册插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
    	<property name="offsetAsPageNum" value="true"/> ……后面全部省略……
    </plugin>
</plugins>

3、插件登记

MyBatis 启 动 时 扫 描 标 签 , 注 册 到 Configuration 对 象 的 InterceptorChain 中。property 里面的参数,会调用 setProperties()方法处理

代理和拦截是怎么实现的?

问题 1:四大对象什么时候被代理,也就是:代理对象是什么时候创建的?

Executor 是 openSession() 的 时 候 创 建 的 ; StatementHandler 是 SimpleExecutor.doQuery()创建的;里面包含了处理参数的 ParameterHandler 和处理 结果集的 ResultSetHandler 的创建,创建之后即调用 InterceptorChain.pluginAll(), 返回层层代理后的对象。

问题 2:多个插件的情况下,代理能不能被代理?代理顺序和调用顺序的关系?

image-20220226231800622

问题 3:谁来创建代理对象?

Plugin 类 。 在 我 们 重 写 的 plugin() 方 法 里 面 可 以 直 接 调 用 return Plugin.wrap(target, this);返回代理对象。

问题 4:被代理后,调用的是什么方法?怎么调用到原被代理对象的方法?

因为代理类是 Plugin,所以最后调用的是 Plugin 的 invoke()方法。它先调用了定义 的拦截器的 intercept()方法。可以通过 invocation.proceed()调用到被代理对象被拦截 的方法

插件调用流程

image-20220226231914375

image-20220226235503585

Mybatis与 Spring 整合分析

1.管理对象

2.通过一个Template封装方法

1、 SqlSessionFactory 是什么时候创建的?

<!-- 在Spring启动时创建 sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
    <property name="dataSource" ref="dataSource"/>
</bean>
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.gupaoedu.crud.dao"/>
</bean>

2、 SqlSession 去哪里了?为什么不用它来 getMapper?

3、 为什么@Autowired 注入一个接口,在使用的时候却变成了代理对象?在 IOC 的容器里面我们注入的是什么? 注入的时候发生了什么事

关键配置

1.整合jar包

2.

3.

4.注入使用

1.创建会话工厂

Spring 对 MyBatis 的对象进行了管理,但是并不会替换 MyBatis 的核心对象。也就 意味着:MyBatis jar 包中的 SqlSessionFactory、SqlSession、MapperProxy 这些都 会用到。而 mybatis-spring.jar 里面的类只是做了一些包装或者桥梁的工作。

image-20220227001922630

image-20220227002000857

2.创建 SqlSession

Q1:可以直接使用 DefaultSqlSession 吗?

它是线程不安;

我们现在已经有一个 DefaultSqlSessionFactory,按照编程式的开发过程,我们接 下来就会创建一个 SqlSession 的实现类,但是在 Spring 里面,我们不是直接使用 DefaultSqlSession 的,而是对它进行了一个封装,这个 SqlSession 的实现类就是 SqlSessionTemplate。这个跟 Spring 封装其他的组件是一样的,比如 JdbcTemplate, RedisTemplate 等等,也是 Spring 跟 MyBatis 整合的最关键的一个

所有的方法都会先走到内部代理类 SqlSessionInterceptor 的 invoke()方法。

SqlSessionTemplate 里面有 DefaultSqlSession 的所有的方法:selectOne()、 selectList()、insert()、update()、delete(),不过它都是通过一个代理对象实现的。这 个代理对象在构造方法里面通过一个代理类创建

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;
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
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)) {
          // 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);
        }
      }
    }
  }

Q2:怎么拿到一个 SqlSessionTemplate?

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSessionTemplate sqlSessionTemplate;

  /**
   * Set MyBatis SqlSessionFactory to be used by this DAO.
   * Will automatically create SqlSessionTemplate for the given SqlSessionFactory.
   *
   * @param sqlSessionFactory a factory of SqlSession
   */
  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
      this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
    }
  }
}

也就是说我们让 DAO 层的实现类继承 SqlSessionDaoSupport,就可以获得 SqlSessionTemplate,然后在里面封装 SqlSessionTemplate 的方法。

当 然 , 为 了 减 少 重 复 的 代 码 , 我 们 通 常 不 会 让 我 们 的 实 现 类 直 接 去 继 承 SqlSessionDaoSupport,而是先创建一个 BaseDao 继承 SqlSessionDaoSupport。在 BaseDao 里面封装对数据库的操作,包括 selectOne()、selectList()、insert()、delete() 这些方法,子类就可以直接调用。

public  class BaseDao extends SqlSessionDaoSupport {
    //使用sqlSessionFactory
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Autowired
    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        super.setSqlSessionFactory(sqlSessionFactory);
    }
}

Q3:有没有更好的拿到 SqlSessionTemplate 的方法

通过接口扫描

3.接口扫描注册

1.什么时候扫描

我们只需要重写 postProcessBeanDefinitionRegistry()方法,在这里面操作 Bean 就可以了。

processBeanDefinitions 方法里面,在注册 beanDefinitions 的时候,BeanClass 被改为 MapperFactoryBean(注意灰色的注释)。

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");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // 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) {
        LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

2. 注册的时候,注册的是什么?这个决定了我们拿到的是什么实际对象。

mapperFactoryBean

 definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());

4、接口注入使用

Spring 会根据 Mapper 的名字从 BeanFactory 中获取它的 BeanDefination,再从 BeanDefination 中 获 取 BeanClass , EmployeeMapper 对 应 的 BeanClass 是 MapperFactoryBean(上一步已经分析过)。

第一步:接下来就是创建 MapperFactoryBean,因为实现了 FactoryBean 接口,同样是调 用 getObject()方法。

因 为 MapperFactoryBean 继 承 了 SqlSessionDaoSupport , 所 以 这 个 getSqlSession()就是调用父类的方法,返回 SqlSessionTemplate;

第二步,SqlSessionTemplate 的 getMapper()方法,里面又有两个方法

  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
// SqlSessionDaoSupport.java
public SqlSession getSqlSession() {
	return this.sqlSessionTemplate;
}
// SqlSessionTemplate.java
public <T> T getMapper(Class<T> type) {
	return getConfiguration().getMapper(type, this);
}
// SqlSessionTemplate.java
public <T> T getMapper(Class<T> type) {
	return getConfiguration().getMapper(type, this);
}
// Configuration.java
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
	return mapperRegistry.getMapper(type, sqlSession);
}

我们注入到 Service 层的接口,实际上还是一个 MapperProxy 代理对象。 所以最后调用 Mapper 接口的方法,也是执行 MapperProxy 的 invoke()方法,后面的 流程就跟编程式的工程里面一模一样了。

image-20220227010242024

image-20220227005529101