mybatis源码之集成spring原理

本文将结合示例并阅读源码,分析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类中,前文做过分析,此处不再记录。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值