springboot 中 mybatis configuration 配置失效问题

环境

  1. springboot 2.0.0
  2. tk.mybatis(mapper-spring-boot-starter) 2.0.0

场景

java 启动类

import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import tk.mybatis.spring.annotation.MapperScan;

@MapperScan(basePackages = "com.aya.service")
@SpringBootApplication
public class ProjectApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(DesignServiceApplication.class)
                .web(WebApplicationType.NONE)
                .run(args);
    }
}

application.yml 配置

mybatis:
    configuration:
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 
logging:
  level:
    root: WARN
    org.mybatis: DEBUG

# 省略数据源相关配置...

自定义 SqlSessionFactory

@Configuration
public class MySqlConfig {
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        return sqlSessionFactoryBean.getObject();
    }
}

问题: mybatis.configuration 的所有配置无效,不打印mybatis相关的日志

解答:

产生问题的原因很简单,是因为tk.mybatis 当SqlSessionFactory不存在定义的时候定义SqlSessionFactory

所以用tk.mybatis的时候,就不要手动注册 SqlSessionFactory 对象了

springboot角度分析
阅读springboot角度分析时,假设你已经了解 springboot 自动注入的相关知识

为什么 tk.mybatis 就可以注入,自定义就不可以呢?

接下来根据springboot的自动注入配置类,找到 MapperAutoConfiguration, 这里定义了 SqlSessionFactory

SqlSessionFactory 设置Configuration
tk.mybatis.mapper.autoconfigure.MapperAutoConfiguration
 

    private final MybatisProperties properties;

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    // 版面原因,省略了无关的属性设置
        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
        Configuration configuration = this.properties.getConfiguration();
        if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
            for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
                customizer.customize(configuration);
            }
        }
        factory.setConfiguration(configuration);
        return factory;
     }

通过 SqlSessionFactory 设置configuration的地方发现, 是通过 MybatisProperties.getConfiguration() 获得的

那么MybatisProperties是什么呢? 怎么注入的呢?

MybatisProperties从配置文件中设置Configuration

@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {

 // 版面原因,省略了无关的属性设置
  public static final String MYBATIS_PREFIX = "mybatis";

  @NestedConfigurationProperty
  private Configuration configuration;
}

这个类表示: 
当存在配置文件属性 mybatis.xx = 1 的时候,设置MybatisProperties属性为xx的值为1

这里就是一个嵌套的属性设置,mybatis.configuration.log-impl 就是设置MybatisProperties属性为
configuration的属性为logImpl的值为org.apache.ibatis.logging.stdout.StdOutImpl

当然tk.mybatis做了很多属性的映射,详细的设置请自己查看 MapperAutoConfiguration 的源码。

如果我们自定义 SqlSessionFactory 的时候,只要注入 MybatisProperties . 就可以解决本文的问题

例如

    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource, MybatisProperties mybatisProperties) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
        sqlSessionFactoryBean.setDataSource(dataSource);
        //注入MybatisProperties,设置Configuration
        sqlSessionFactoryBean.setConfiguration(mybatisProperties.getConfiguration());
        return sqlSessionFactoryBean.getObject();
    }

但是不推荐这样做. 因为 SqlSessionFactoryBean 还有一些其他的属性都要自己对应。

推荐做法: 用了tk.mybatis 就不要自己定义 SqlSessionFactory 了

spring角度分析

阅读spring角度分析时,假设你已经了解 spring bean加载的相关知识

既然是Configuration的加载属性问题. 先去 MapperProxy 的创建的地方

  1. 按照 MapperProxy->SqlSession(实现类SqlSessionTemplate)->sqlSessionFactory(实现类DefaultSqlSessionFactory)->configuration(实现类Configuration) 的顺序  查看 Configuration 是否被设置
  2. SqlSessionFactoryBean.getObject 的地方,查看 Configuration 的来源说明 MapperProxy 是所有的Mapper接口的动态代理执行方法类,实现了InvocationHandler,内部有sqlSession属性

MapperProxy之旅

接下来按照以下顺序寻找Configuration失效的根源 

  1. MapperProxy 

  2. SqlSessionFactoryBean::getObject

MapperProxy 是我们自定义 XXMapper 的代理类,实现了InvocationHandler接口

SqlSessionFactoryBean 是spring创建SqlSessionFactoryBean的过程

org.apache.ibatis.binding.MapperProxy
 

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

这里断点后,查看 this.sqlSession.configuration.logImpl 的值是null

说明确实没有被赋值成功

SqlSessionFactoryBean之旅

org.mybatis.spring.SqlSessionFactoryBean

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

    return this.sqlSessionFactory;
  }

SqlSessionFactoryBean 实现了 InitializingBean,FactoryBean。

所以创建的bean是通过getObject获得的,初始化方法是afterPropertiesSet

    // 初始化 
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.dataSource, "Property 'dataSource' is required");
        Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
        // 通过 buildSqlSessionFactory 获得 sqlSessionFactory
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }

     protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
            XMLConfigBuilder xmlConfigBuilder = null;
            Configuration configuration;
            // this.configuration = null ,走最下面的configuration = new Configuration();
            // 也就是说 configuration 的属性必须在调用 afterPropertiesSet 之前就已经设置了
            if (this.configuration != null) {
                configuration = this.configuration;
                if (configuration.getVariables() == null) {
                    configuration.setVariables(this.configurationProperties);
                } else if (this.configurationProperties != null) {
                    configuration.getVariables().putAll(this.configurationProperties);
                }
            } else if (this.configLocation != null) {
                xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
                configuration = xmlConfigBuilder.getConfiguration();
            } else {
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
                }

                configuration = new Configuration();
                configuration.setVariables(this.configurationProperties);
            }
            //版面原因,省略 configuration 设置属性的相关代码
            return this.sqlSessionFactoryBuilder.build(configuration);
     }

this.configuration = null ,走最下面的 configuration = new Configuration();

也就是说 configuration 的属性必须在调用 getObject 之前就已经设置了?

这不可能的,正常创建对象的过程中,都没填充属性,怎么可能在 getObject 设置 configuration 的属性呢

对比

打开 tk.mybatis 的官方测试项目或者新建一个测试项目,全部使用默认配置,

对spring的类 AbstractApplicationContext 的 refresh 下断点。

直到 invokeBeanFactoryPostProcessors 方法之后, 查看 ((DefaultListableBeanFactory) beanFactory).beanDefinitionMap.get("sqlSessionFactory")

  1. factoryBeanName: tk.mybatis.mapper.autoconfigure.MapperAutoConfiguration
  2. factoryMethodName: sqlSessionFactory

也就是说默认的定义在MapperAutoConfiguration.sqlSessionFactory()方法定义的sqlSessionFactory.

这样就从 spring 的角度跟进到了 springboot 的角度。 从而得知问题的根本原因


原文:https://blog.csdn.net/mz4138/article/details/81775137 
 

 

 

你可能感兴趣的:(Java)