debug数据源之sqlSessionFactory

本文详细介绍了在MyBatis中,如何通过SqlSessionFactoryBean配置数据源,以及在处理持久层请求时,SqlSession的创建与管理。通过debug,揭示了从SqlSessionFactory到SqlSession,再到MapperFactoryBean的创建过程。分析了SqlSessionTemplate和SqlSessionManager在事务管理和线程安全性中的角色,解释了MyBatis如何在Spring环境下利用TransactionSynchronizationManager进行资源管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用mybatis的时候会配置sqlSessionFactoryBean,将数据源传入。我对于mybatis的理解是当 处理持久层的请求时,ThreadLocal 内会持有一个数据源的jdbc 链接。这样 该线程的所有数据操作都是用一个链接来操作。完后关闭链接。debug的目的是看下到底是怎么做的。

SqlSessionFactoryBean内有很多对象,debug下来发现最关键是configuration对象。他持有用户配置的datasource。在afterPropertiesSet()方法中打上断点。

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

启动程序 进入断点

这个方法调用过程是spring创建bean的过程。

进入buildsqlSessionFactory(),很长,主要内容是通过配置来初始化configuration。然后通过configuration来创建一个sqlSessionFactory。

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;
//删除配置代码

    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }
//删除配置代码
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

 //删除配置代码

    return this.sqlSessionFactoryBuilder.build(configuration);
  }

最后sqlSessionFactoryBuilder.build new了一个DefaultSqlSessionFactory。顾名思义他的作用就是sqlSession的创建工厂

创建的sqlSession内持有Environment对象,内含我们配置的datasource 和TransactionFactory

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

afterPropertiesSet()这个方法结束 就是为sqlSessionFactoryBean 创建了一个sqlSessionFactory。现在看另一个重要的类

MapperFactoryBean 他是用来创建mapper接口的实现类的,它继承自SqlSessionDaoSupport

SqlSessionDaoSupport 的setSqlSessionFactory 方法会根据sqlSessionFactory来创建一个SqlSessionTemplate

该类是SqlSession的实现类,持有SqlSessionFactory对象。和一个动态代理SqlSession的对象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());
  }

上面是new一个SqlSessionTemplate 同时new一个SqlSession的动态代理对象SqlSessionProxy

  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;
//删除异常处理代码

  }

这是一个典型的jdk动态代理。SqlSessionTemplate 并不用来执行数据操作语句而是由通过getSqlsession来创建的sqlsession执行了数据操作后加入了commit。创建的过程就会打开一个链接放入sqlsession.getSqlsession获取的是defaultSqlSession对象他就是被放在threadLocal 的 。

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

 

然后调用sqlsession。getMapper来得到一个Dao对象。sqlsession的getMapper内部调用Configuration对象的getMapper,它的getMApper有调用mapperRegistry对象的getMapper.

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
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);
    }
  }

newInstance用来创建一个动态代理将 dao接口的方法代理为sqlSession来执行

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

mapperProxy的invoke为

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

上面是启动过程,再看一个数据操作的过程

一个dao的方法由上分析应该新进入mapperProxy的代理类在他的invoke出打断点

该代理类处理完参数封装为一个方法然后执行sqlSession的响应数据操作方法,然而他也被SqlSessionTemplate 的SqlSessionProxy代理同样进入

 这里执行完后 判断是否是事务,不是的话直接commit。执行的整体流程大致如此,并没有如自己所想有ThreadLocal 来管理session。查找sqlsession接口的实现类有一个SqlSessionManager 该类里面有

 private ThreadLocal<SqlSession> localSqlSession = new ThreadLocal<SqlSession>();

但是我的程序没有用这个类,而是用的SqlSessionTemplate 来动态代理DefaultSqlSession。

DefaultSqlSession 是实际执行数据操作的,而SqlSessionManager 和SqlSessionTemplate  都是代理。他们都可以保证线程安全。即可以保证同一个线程使用的的DefaultSqlSession 是一个。SqlSessionManager 是mybatis.session 包里面的 不依赖与spring 通过ThreadLocal 来保存DefaultSqlSession 。而SqlSessionTemplate  是mybatis-spring 包里面的。他依赖于spring。spring的TransactionSynchronizationManager类 里面有ThreadLocal 的map,可以存储当前线程的资源。SqlSessionTemplate  就是借助这个 从而不用在写一个ThreadLocal。

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

 

<think>好的,用户现在想了解如何在Spring Boot中使用@ConfigurationProperties来配置动态数据源,并且需要示例。我之前已经回答过关于Maven配置阿里云仓库的问题,现在需要结合用户提供的站内引用内容来回答这个问题。 首先,我需要回顾用户提供的引用内容。引用[1]、[2]、[3]都提到了动态数据源的配置,其中引用[2]提到需要排除DataSourceAutoConfiguration,引用[1]和[3]提到了使用AbstractRoutingDataSource。引用[4]虽然讲的是Redis多数据源,但配置方式可能有类似之处,比如使用配置文件定义多个数据源属性。 用户的问题明确提到了使用@ConfigurationProperties,所以需要将动态数据源的配置与这个注解结合起来。通常,@ConfigurationProperties用于将配置文件中的属性绑定到Java对象上,这样可以在代码中方便地使用这些配置。 接下来,我需要考虑动态数据源的一般步骤:定义多个数据源的配置,创建数据源实例,设置动态数据源路由,以及配置Spring Boot应用。结合@ConfigurationProperties,可以为每个数据源创建一个配置类,绑定对应的前缀,然后在动态数据源配置中加载这些配置。 用户可能的需求是希望通过配置文件(如application.yml)来定义多个数据源,然后在代码中动态切换。需要确保排除自动配置的数据源,避免冲突,这点在引用[2]中提到过。因此,在启动类上需要排除DataSourceAutoConfiguration。 然后,需要定义数据源配置类,使用@ConfigurationProperties绑定前缀,比如spring.datasource.master和spring.datasource.slave。接着,创建AbstractRoutingDataSource的子类,实现determineCurrentLookupKey方法,根据当前线程选择数据源标识。 在配置类中,需要将多个数据源注入,并设置到AbstractRoutingDataSource中。同时,需要配置事务管理和SqlSessionFactory,确保动态切换生效。 用户可能还需要示例代码,所以需要提供YAML配置示例、数据源属性类、动态路由类、配置类以及使用方式,比如通过注解或手动切换数据源。 需要验证用户是否理解如何排除自动配置,如何绑定多个数据源的属性,以及如何在实际业务中使用动态切换。可能的问题包括配置是否正确、事务管理是否影响数据源切换等,这些需要在注意事项中提及。 最后,生成的相关问题需要围绕动态数据源的配置和使用,比如如何结合MyBatis、事务处理、性能优化等,帮助用户深入理解。</think>### 使用 `@ConfigurationProperties` 配置动态数据源的完整示例 #### 一、核心配置步骤 1. **排除自动配置** 在 Spring Boot 启动类中禁用默认数据源自动配置,避免与动态数据源冲突: ```java @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class Application { ... } ``` 参考自多数据源配置的通用做法[^2]。 2. **定义数据源配置类** 使用 `@ConfigurationProperties` 绑定多个数据源配置(YAML 格式): ```yaml # application.yml datasource: master: url: jdbc:mysql://localhost:3306/master_db username: root password: master_pwd driver-class-name: com.mysql.cj.jdbc.Driver slave: url: jdbc:mysql://localhost:3306/slave_db username: root password: slave_pwd driver-class-name: com.mysql.cj.jdbc.Driver ``` 3. **创建数据源属性绑定类** ```java @Getter @Setter @ConfigurationProperties(prefix = "datasource") public class DataSourceProperties { private DataSourceConfig master; private DataSourceConfig slave; @Data public static class DataSourceConfig { private String url; private String username; private String password; private String driverClassName; } } ``` 4. **动态数据源路由实现** 继承 `AbstractRoutingDataSource` 实现动态切换逻辑: ```java public class DynamicDataSource extends AbstractRoutingDataSource { private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); @Override protected Object determineCurrentLookupKey() { return CONTEXT_HOLDER.get(); } public static void setDataSourceKey(String key) { CONTEXT_HOLDER.set(key); } public static void clearDataSourceKey() { CONTEXT_HOLDER.remove(); } } ``` 5. **数据源配置类** 装配动态数据源并注入多数据源配置: ```java @Configuration @EnableConfigurationProperties(DataSourceProperties.class) public class DataSourceConfig { @Bean @Primary public DataSource dynamicDataSource(DataSourceProperties properties) { Map<Object, Object> targetDataSources = new HashMap<>(); // 创建主数据源 DataSource master = DataSourceBuilder.create() .url(properties.getMaster().getUrl()) .username(properties.getMaster().getUsername()) .password(properties.getMaster().getPassword()) .driverClassName(properties.getMaster().getDriverClassName()) .build(); // 创建从数据源 DataSource slave = DataSourceBuilder.create() .url(properties.getSlave().getUrl()) .username(properties.getSlave().getUsername()) .password(properties.getSlave().getPassword()) .driverClassName(properties.getSlave().getDriverClassName()) .build(); targetDataSources.put("master", master); targetDataSources.put("slave", slave); DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(targetDataSources); dynamicDataSource.setDefaultTargetDataSource(master); // 默认主库 return dynamicDataSource; } @Bean public PlatformTransactionManager transactionManager(DataSource dynamicDataSource) { return new DataSourceTransactionManager(dynamicDataSource); } } ``` --- #### 二、使用动态数据源 1. **通过注解切换数据源** 自定义 `@DataSource` 注解实现声明式切换: ```java @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value() default "master"; } @Aspect @Component public class DataSourceAspect { @Before("@annotation(dataSource)") public void beforeSwitch(JoinPoint joinPoint, DataSource dataSource) { DynamicDataSource.setDataSourceKey(dataSource.value()); } @After("@annotation(dataSource)") public void afterSwitch(DataSource dataSource) { DynamicDataSource.clearDataSourceKey(); } } ``` 2. **业务层调用示例** ```java @Service public class UserService { @Autowired private UserMapper userMapper; @DataSource("master") public User getMasterUser(Long id) { return userMapper.selectById(id); } @DataSource("slave") public User getSlaveUser(Long id) { return userMapper.selectById(id); } } ``` --- #### 三、关键注意事项 1. **事务管理** 在事务方法中切换数据源可能失效,建议在事务边界外切换数据源或使用 `ChainedTransactionManager`[^3]。 2. **连接池配置** 可在 `DataSourceConfig` 中添加连接池参数(如 HikariCP): ```yaml datasource: master: hikari: maximum-pool-size: 10 connection-timeout: 30000 ``` 3. **多数据源 MyBatis 集成** 需为每个数据源单独配置 `SqlSessionFactory`(参考多数据源 MyBatis 配置[^2])。 --- #### 四、验证配置 1. 启动应用时观察日志中是否加载了多个数据源配置 2. 通过 `DEBUG` 模式跟踪 `DynamicDataSource.determineCurrentLookupKey()` 方法 3. 执行单元测试验证不同数据源操作结果 --- #### 相关问题 1. 动态数据源切换如何与 Spring 事务管理器协同工作? 2. 如何为不同的 MyBatis Mapper 接口指定不同数据源? 3. 动态数据源配置下如何实现读写分离自动化?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值