日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

mybatis源碼之集成spring原理詳解

作者:xuguofeng2016 更新時間: 2022-07-22 編程語言

本文將結合示例并閱讀源碼,分析mybatis與spring的集成原理,將重點分析@MapperScan注解掃描、插件注冊等內容。

示例代碼

Configuration配置類

@MapperScan(basePackages = {"org.net5ijy.mybatis.test.mapper"})
@Configuration
public class MybatisConfig {
  // bean config
}

數據源配置

使用的是Druid數據源。

@Bean
public DataSource druidDataSource()
    throws IOException, InvocationTargetException,
    IllegalAccessException, IntrospectionException,
    InstantiationException, ClassNotFoundException {

  DruidDataSource dataSource = new DruidDataSource();

  // 讀取數據源配置文件
  Properties properties = new Properties();
  properties.load(
      MybatisConfig.class.getClassLoader().getResourceAsStream("jdbc.properties"));

  // 設置數據源核心參數
  dataSource.setUsername(properties.getProperty("username"));
  dataSource.setPassword(properties.getProperty("password"));
  dataSource.setDriverClassName(properties.getProperty("driver"));
  dataSource.setUrl(properties.getProperty("url"));

  Set<Object> keys = properties.keySet();
  for (Object key : keys) {
    String k = (String) key;
    if (k.startsWith("druid.")) {
      String propertyName = k.substring(6);
      String propertyValue = properties.getProperty(k);
      if (propertyName.equals("proxyFilters")) {
        dataSource.setProxyFilters(createFilters(propertyValue));
      } else {
        PropertyDescriptor propertyDescriptor =
            new PropertyDescriptor(propertyName, DruidDataSource.class);
        Method writeMethod = propertyDescriptor.getWriteMethod();
        Class<?> type = writeMethod.getParameterTypes()[0];
        Object v = transferValue(type, propertyValue);
        writeMethod.invoke(dataSource, v);
      }
    }
  }

  return dataSource;
}

SqlSessionFactory配置

SqlSessionFactoryBean實現了FactoryBean接口。

@Bean
public SqlSessionFactoryBean sessionFactory(@Autowired DataSource dataSource) {
  SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
  // 設置數據源
  sessionFactoryBean.setDataSource(dataSource);
  // 設置mapper xml配置文件路徑
  sessionFactoryBean.setMapperLocations(new ClassPathResource("mapper/BlogMapper.xml"));
  return sessionFactoryBean;
}

SqlSessionTemplate配置(可選)

@Bean
public SqlSession session(@Autowired SqlSessionFactory sessionFactory) {
  return new SqlSessionTemplate(sessionFactory);
}

Mapper接口

正常編寫。

Mapper xml配置文件

正常編寫。

jdbc參數配置

jdbc.properties配置文件:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis_source_analysis
username=root
password=123456

druid.proxyFilters=stat
druid.maxActive=20
druid.initialSize=1
druid.maxWait=60000
druid.minIdle=1
druid.timeBetweenEvictionRunsMillis=60000
druid.minEvictableIdleTimeMillis=300000
druid.validationQuery=SELECT 1 FROM DUAL
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=true
druid.maxOpenPreparedStatements=20
druid.defaultAutoCommit=false

測試代碼

public class BlogMapper3Test {

  private BlogMapper blogMapper;

  @Before
  public void before() {
    // 創建ApplicationContext
    AnnotationConfigApplicationContext applicationContext =
        new AnnotationConfigApplicationContext(MybatisConfig.class);
    // 獲取mapper對象
    this.blogMapper = applicationContext.getBean(BlogMapper.class);
  }

  @Test
  public void testInsertBlog() {
    Blog blog = new Blog();
    blog.setTitle("spring學習");
    blog.setContent("spring深入 - 源碼分析");

    int rows = this.blogMapper.insertBlog(blog);

    System.out.println(rows);
    System.out.println(blog.getId());
  }

  @Test
  public void testSelectBlogById() {
    Blog blog = this.blogMapper.selectBlogById(1);
    System.out.println(blog);
  }

  @Test
  public void testSelectBlogByBlogSearchParameter() throws ParseException {

    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    List<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(2);

    Blog blog = new Blog();
    blog.setContent("mybatis源碼分析");
    blog.setTitle("mybatis");

    BlogSearchParameter parameter = new BlogSearchParameter();
    parameter.setId(1);
    parameter.setIds(ids);
    parameter.setCreateTime(format.parse("2020-01-01 00:00:00"));

    parameter.setBlog(blog);

    List<Blog> list = this.blogMapper.selectBlogByParameter(parameter);

    for (Blog b : list) {
      System.out.println(b);
    }
  }
}

重點分析的內容

  • @MapperScan注解掃描Mapper接口的原理
  • 插件注冊的原理
  • 與獨立運行時的不同點

SqlSessionFactoryBean類

這個類實現了FactoryBean接口,是spring工廠模式獲取bean的方式,使用getObject()方法獲取bean并放入bean factory中。

FactoryBean接口:

public interface FactoryBean<T> {

	T getObject() throws Exception;

	Class<?> getObjectType();

	default boolean isSingleton() {
		return true;
	}
}

SqlSessionFactoryBean類提供了絕大多數mybatis配置的入口。

可以配置數據源:

sessionFactoryBean.setDataSource(dataSource);

可以配置mapper xml配置路徑:

sessionFactoryBean.setMapperLocations(new ClassPathResource("mapper/BlogMapper.xml"));

可以配置主配置文件路徑:

void setConfigLocation(Resource configLocation);

可以配置枚舉類型處理器:

void setDefaultEnumTypeHandler(Class<? extends TypeHandler> defaultEnumTypeHandler);

可以配置插件:

void setPlugins(Interceptor... plugins);

可以配置事務管理器:

void setTransactionFactory(TransactionFactory transactionFactory);

可以配置別名:

void setTypeAliases(Class<?>... typeAliases);
void setTypeAliasesPackage(String typeAliasesPackage);

可以配置類型處理器:

void setTypeHandlers(TypeHandler<?>... typeHandlers);

SqlSessionTemplate類

實現SqlSession接口,內部維護著一個SqlSessionFactory對象和一個SqlSession的代理對象,所有的SQL執行都是使用這個SqlSession代理對象來做的。

構造方法

看一下構造方法:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
  this(sqlSessionFactory, executorType,
      new MyBatisExceptionTranslator(
          sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
    PersistenceExceptionTranslator exceptionTranslator) {

  this.sqlSessionFactory = sqlSessionFactory;
  this.executorType = executorType;
  this.exceptionTranslator = exceptionTranslator;

  // 這里創建了一個SqlSession的代理
  // 代理邏輯在SqlSessionInterceptor中
  // SqlSessionInterceptor是SqlSessionTemplate的內部類,可以訪問SqlSessionTemplate的所有成員
  this.sqlSessionProxy = (SqlSession) newProxyInstance(
      SqlSessionFactory.class.getClassLoader(),
      new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}

SqlSessionInterceptor類

前面已經說明,SqlSessionInterceptor實現了SqlSessionTemplate的sqlSessionProxy的代理邏輯,看一下invoke代碼:

private class SqlSessionInterceptor implements InvocationHandler {
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 為了解決DefaultSqlSession的非線程安全問題
    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);
      }
    }
  }
}

// Checks if SqlSession passed as an argument is managed by Spring TransactionSynchronizationManager
// If it is not, it closes it, otherwise it just updates the reference counter and lets Spring call
// the close callback when the managed transaction ends
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
  if ((holder != null) && (holder.getSqlSession() == session)) {
    holder.released();
  } else {
    session.close();
  }
}

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

  // TransactionSynchronizationManager類: 
  // Central delegate that manages resources and transaction synchronizations per thread.
  // To be used by resource management code but not by typical application code.
  // 這是spring-tx提供的一個工具類,用于管理資源和事務的同步
  // 底層使用ThreadLocal實現
  SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);

  // 獲取當前線程上的SqlSession
  // 第一次執行是沒有的
  SqlSession session = sessionHolder(executorType, holder);
  if (session != null) {
    return session;
  }

  // 如果沒有就open一個,內部代碼已經分析過
  // 返回的是一個DefaultSqlSession對象
  session = sessionFactory.openSession(executorType);

  // 注冊到當前線程上
  registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

  return session;
}

// 這里記錄一下openSession方法,因為此時使用的事務管理器和之前不同,其余內容都一樣
private SqlSession openSessionFromDataSource(
    ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    final Environment environment = configuration.getEnvironment();
    final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
    // 這里使用的是SpringManagedTransactionFactory類型
    // 此處涉及到spring的事務管理,后續深入分析補充
    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();
  }
}

@MapperScan注解掃描原理

之前的 Autowired注入Service變成了biaomidou的Mapper代理 一文的 MapperScan注解 章節對此問題做過詳細分析,此處不再做分析。

https://blog.csdn.net/xuguofeng2016/article/details/120515536

執行SQL查詢的流程

MapperFactoryBean類和FactoryBean接口

FactoryBean接口是spring工廠模式創建bean的方式,spring在創建完FactoryBean的實現類對象后,會調用getObject()方法來獲取真正的bean對象,然后將這個對象放入factory中:

public interface FactoryBean<T> {

    // 使用這個方法獲取bean對象
	T getObject() throws Exception;

	Class<?> getObjectType();

	default boolean isSingleton() {
		return true;
	}
}

看一下MapperFactoryBean的getObject()方法:

public T getObject() throws Exception {
  // 獲取到SqlSession成員變量,用他來getMapper
  // this.mapperInterface也是成員變量,就是我們的mapper接口
  return getSqlSession().getMapper(this.mapperInterface);
}

getSqlSession()獲取到的是MapperFactoryBean持有的sqlSessionTemplate對象,這個是spring幫忙注入進來的。

getMapper(Class)流程

入口在SqlSessionTemplate.getMapper(Class)中:

public <T> T getMapper(Class<T> type) {
  return getConfiguration().getMapper(type, this);
}

進入到Configuration.getMapper()方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

進入到MapperRegistry.getMapper()方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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);
  }
}

這段代碼在 mybatis源碼分析_mapper接口源碼分析 一文中看到過,內部的邏輯不再分析,不過這里有一個問題,knownMappers是什么時候put元素的?

knownMappers的初始化

在 mybatis源碼分析_mapper接口源碼分析 一文中,我們了解到XMLMapperBuilder.parse方法調用bindMapperForNamespace()方法,最后會調用MapperRegistry的addMapper(Class)方法將mapper接口的方法轉為Statement保存到Configuration中。

在spring環境中是如何處理的?

還是要回到SqlSessionFactoryBean的getObject()方法,我們之前分析過這個類,但是沒有展開分析getObject()方法,此處看一下這個方法:

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

  return this.sqlSessionFactory;
}

public void afterPropertiesSet() throws Exception {
  this.sqlSessionFactory = buildSqlSessionFactory();
}

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

  final Configuration targetConfiguration;

  XMLConfigBuilder xmlConfigBuilder = null;
  // 讀取configuration配置,是null分支進不來
  if (this.configuration != null) {
    targetConfiguration = this.configuration;
    if (targetConfiguration.getVariables() == null) {
      targetConfiguration.setVariables(this.configurationProperties);
    } else if (this.configurationProperties != null) {
      targetConfiguration.getVariables().putAll(this.configurationProperties);
    }
  } else if (this.configLocation != null) {
    // 從配置的主配置文件路徑解析配置,是null分支進不來
    xmlConfigBuilder = 
        new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
    targetConfiguration = xmlConfigBuilder.getConfiguration();
  } else {
    targetConfiguration = new Configuration();
    Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
  }

  Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
  Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
  Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

  // 別名包配置解析,略
  if (hasLength(this.typeAliasesPackage)) {
    scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
        .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
        .filter(clazz -> !clazz.isMemberClass())
        .forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
  }

  // 類型別名配置解析,略
  if (!isEmpty(this.typeAliases)) {
    Stream.of(this.typeAliases).forEach(typeAlias -> {
      targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
    });
  }

  // 插件在這里,代碼比較熟悉
  if (!isEmpty(this.plugins)) {
    Stream.of(this.plugins).forEach(plugin -> {
      targetConfiguration.addInterceptor(plugin);
    });
  }

  // 解析類型處理器
  if (hasLength(this.typeHandlersPackage)) {
    scanClasses(this.typeHandlersPackage, TypeHandler.class)
        .stream().filter(clazz -> !clazz.isAnonymousClass())
        .filter(clazz -> !clazz.isInterface())
        .filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
        .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
  }

  // 解析類型處理器
  if (!isEmpty(this.typeHandlers)) {
    Stream.of(this.typeHandlers).forEach(typeHandler -> {
      targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
    });
  }
  // 默認的枚舉類型處理器
  targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

  // 腳本
  if (!isEmpty(this.scriptingLanguageDrivers)) {
    Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
      targetConfiguration.getLanguageRegistry().register(languageDriver);
    });
  }
  Optional.ofNullable(this.defaultScriptingLanguageDriver)
      .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

  if (this.databaseIdProvider != null) {
    try {
      targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
    } catch (SQLException e) {
      throw new NestedIOException("Failed getting a databaseId", e);
    }
  }

  Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

  // 分支進不來
  if (xmlConfigBuilder != null) {
    try {
      xmlConfigBuilder.parse();
    } catch (Exception ex) {
      throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  targetConfiguration.setEnvironment(new Environment(this.environment,
      this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
      this.dataSource));

  // 這里是重點
  // 這里在解析mapper xml配置文件
  // 就是之前使用setMapperLocations(...)方法配置的
  if (this.mapperLocations != null) {
    if (this.mapperLocations.length == 0) {
      // ...
    } else {
      for (Resource mapperLocation : this.mapperLocations) {
        try {
          // 這里就比較熟悉了
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
          // 這里調用parse()方法
          // 這樣就和之前的分析連上了
          // OK
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }
      }
    }
  } else {
    // ...
  }

  return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}

查詢流程

和 mybatis源碼分析_mapper接口源碼分析 一文中的分析基本一致,只是最后使用的是SqlSessionTemplate的方法,而不是DefaultSqlSession的方法,而SqlSessionTemplate內部又是使用一個SqlSession代理做的,代理的實現邏輯在SqlSessionInterceptor類中,前文做過分析,此處不再記錄。

原文鏈接:https://blog.csdn.net/xuguofeng2016/article/details/124832716

相關推薦

欄目分類
最近更新