从配置文件研究MyBatis的运行过程

从配置文件研究MyBatis的运行过程,第1张

从配置文件研究MyBatis的运行过程 一、Spring整合MyBatis配置文件
    
        
        
        
        
         

可见,sqlSession对象的初始化是new 出了SqlSessionTemplate对象,我们看SqlSessionTemplate源码:
首先,看其属性:

private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

可以看出,SqlSessionTemplate类本身是SqlSession对象,而其成员属性也有一个SqlSession对象,说明这是装饰者模式,干活的肯定是sqlSessionProxy对象,下面看sqlSessionProxy的初始化:

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

可以看出,sqlSessionProxy 是通过jdk动态代理生成的代理对象,重点分析InvocationHandler的逻辑,这里是对方法的加强,所以看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);
        }
      }
    }
  }

先查看getSqlSession方法源码,该方法调用的是SqlSessionUtils类里的方法:

  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
    notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);

    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
      return session;
    }

    if (LOGGER.isDebugEnabled()) {
      LOGGER.debug("Creating a new SqlSession");
    }

    session = sessionFactory.openSession(executorType);

    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

分析其源码,先分析这段:

SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

看getResource方法源码:

public static Object getResource(Object key) {
        Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
        Object value = doGetResource(actualKey);
        if (value != null && logger.isTraceEnabled()) {
            logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
        }

        return value;
    }

再看doGetResource方法源码:

 private static Object doGetResource(Object actualKey) {
        Map map = (Map)resources.get();
        if (map == null) {
            return null;
        } else {
            Object value = map.get(actualKey);
            if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
                map.remove(actualKey);
                if (map.isEmpty()) {
                    resources.remove();
                }

                value = null;
            }

            return value;
        }
    }

可见,最终返回的是resources属性中的一个值,我们看resources的类型:

    private static final ThreadLocal> resources = new NamedThreadLocal("Transactional resources");

可见,这是一个ThreadLocal类,也就是说,SqlSessionUtils类中的getSqlSession方法,先从ThreadLocal中获取当前线程的SqlSession对象,如果有,则直接返回,如果没有,则运行如下代码,创建sqlSession对象:

session = sessionFactory.openSession(executorType);

因此,对于一个mapper接口而言,一个线程中只存在一个sqlsession对象。
我们继续分析代理对象sqlSessionProxy的代理参数InvokeHandler之SqlSessionInterceptor 源码:

finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }

可见,最终,要对sqlsession进行关闭 *** 作。所以,对于sqlsession而言,就是一个线程的生命周期,在一个线程中创建,并在一个线程中关闭。

  • getMapper方法研究:

上面我们分析了MapperFactoryBean类中getObject方法中getSqlSession方法的来龙去脉,可知一个线程获取到了一个SqlSession对象,下面我们继续研究getObject方法中getMapperr方法的源码:

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

从代码来看getMapper是讲Mapper接口作为参数传入,然后返回其代理对象到Spring容器中,看其源码:
因为MyBatis和Spring融合了,所以看Spring提供的SqlSessionTemplate中的getMapper方法,一顿跳入,最终点进了这个方法:

public  T getMapper(Class type, SqlSession sqlSession) {
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

可以看到,getMapper方法首先获得了MapperProxyFactory对象,然后调用MapperProxyFactory的newInstance方法,获得Mapper的代理对象,看MapperProxyFactory源码:

public class MapperProxyFactory {

  private final Class mapperInterface;
  private final Map methodCache = new ConcurrentHashMap();

  public MapperProxyFactory(Class mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class getMapperInterface() {
    return mapperInterface;
  }

  public Map getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

可以看出,newInstance方法也是用了jdk的动态代理,生成的代理类,看主要参数InvocationHandler的实现类MapperProxy的invoke方法:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

最终,调用mapper的方法,进行执行。至于代理类是如何从xml中获取到sql,然后形成语句,发送数据库执行的,这里不做研究。

总结

从上面可以看到,MyBatis的设计思路大致如下:
1.程序员手动或自动(SpringBoot项目)配置SqlSessionFactoryBean类到Spring容器,SqlSessionFactoryBean是一个FactoryBean,通过getObject方法将SqlSessionFactory加入到Spring容器中,同时也将MyBatis的大杂烩Configuration对象加入到Spring容器。所以,SqlSessionFactory是伴随项目运行始终的,所以,二级缓存就放在了SqlSessionFactory中,只要Spring容器不销毁,SqlSessionFactory就一直存在。在分布式场景下,多个项目,就是多个Spring容器,也就是多个SqlSessionFactory,所以,并不能公用二级缓存,在分布式业务场景下,MyBatis的二级分布式缓存就显得很鸡肋。

2.程序员配置MapperScannerConfigurer类和扫描包,MapperScannerConfigurer是一个BeanDefinitionRegistryPostProcessor,可以注册BeanDefinition对象,其扫描到一个mapper后,就会注册一个BeanDefinition对象,注册的BeanDefinition是MapperFactoryBean类对象,mapper接口以mapperinterfaces属性存入MapperFactoryBean中。

3.MapperFactoryBean类也是个FactoryBean对象,其getObject方法将mapper的代理类放入到Spring容器中。

4.MapperFactoryBean类是通过SqlSession对象的代理对象获取到的mapper的代理对象,在一个线程中,一个mapper只会有一个sqlsession对象,也只生成一个mapper的代理对象,当线程方法运行完后,会把sqlsession对象关闭,所以,每个线程都是新的sqlsession对象和新的mapper代理对象。

5.sqlsession对象也用代理对象的目的是为了加强sqlsession对象,对其进行关闭等 *** 作,所以用了代理。二级缓存在sqlsession中,所以,每个线程有自己的二级缓存,不能公用,而在一个线程中,同一个方法查询两次的 *** 作也不多,所以说,MyBatis的二级缓存也很鸡肋。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5671513.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存