Mybatis对Spring扩展点的应用

1、@MapperScan注解

2、Spring如何将Mapper纳入Spring容器

3、Spring如何管理Mybatis的SqlSessionFactory和SqlSession

4、mybatis的一级缓存在spring中失效的原因

5、mybatis对xml和注解的处理

6、Mybatis在Spring环境下的事务

Spring集成mybatis简答的配置类

使用的版本:

  • spring 5.1.x

  • mybatis 3.5.3

  • mybatis-spring 2.0.3

@Configuration
@MapperScan("top.gmfcj.mapper")
@ComponentScan("top.gmfcj")
public class AppConfig {
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        return factoryBean.getObject();
    }
    @Bean
    public DataSource dataSource() {
        PooledDataSource dataSource = new PooledDataSource();
        dataSource.setDriver("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/library?useUnicode=true&useSSL=true&characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("password");
        return dataSource;
    }
}

配置很简单,我们首先来看SqlSessionFactoryBean这个类

// 具体的实例化过程:org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
public class SqlSessionFactoryBean implements FactoryBean,InitializingBean, ApplicationListener {
    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            afterPropertiesSet();
        }
        return this.sqlSessionFactory;
    }  
}
public boolean isSingleton() {
    return true;
}
public Class getObjectType() {
    return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
}

首先,这个SqlSessionFactoryBean实现了FactoryBean接口,都知道如果将实现了这个接口的类交给spring管理,那么在Spring容器中就会存在两种类,一种是实现了FactoryBean接口的类,它的beanName=&className ,另外还存在一个对象就是getObject方法返回的对象,它的beanName=className。以SqlSessionFactoryBean为例,如果我们给这个类添加了@Component注解,那么我们可以获取两个对象,一个是&sqlSessisonFactoryBean,类型是SqlSessisonFactoryBean。一个是sqlSessionFactoryBean,类型是SqlSessionFactory(getObjectType返回的类型)。

FactoryBean的好处就是可以人为去构建一个bean对象,有可能这个对象中的很多属性来自于其他不需要spring管理的对象,也有可能这个bean对象是一个很复杂的对象,如果这样的对象也要Spring来实例化是非常复杂的,所以Spring提供了这样一个机制,允许我们手动实例化一个对象,然后把这个对象通过FactoryBean接口暴露出来即可。这样做可以减少spring实例化带来的复杂逻辑,方便外部直接使用。

这个对象还实现了InitializingBean和ApplicationListener接口

public void afterPropertiesSet() throws Exception {
    // 创建sqlSessionFactory
    this.sqlSessionFactory = buildSqlSessionFactory();
}
public void onApplicationEvent(ApplicationEvent event) {
    if (failFast && event instanceof ContextRefreshedEvent) {
        // fail-fast -> check all statements are completed 检查所有语句是否完成
        this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
    }
}

创建sqlSessionFactory中关键的地方,主要涉及到事务

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
    // 设置事务工厂,在获取sqlSession的时候回去获取事务对象
    targetConfiguration.setEnvironment(
                        new Environment(this.environment,
                            this.transactionFactory == null ? 
                            new SpringManagedTransactionFactory() :
                            this.transactionFactory,this.dataSource));
}

注意:SpringManagedTransactionFactory

@MapperScan注解

@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {}
@Import(MapperScannerRegistrar.RepeatingRegistrar.class)
public @interface MapperScans {
  MapperScan[] value();
}

1、@Repeatable是JDK8的新注解,表示这个@MapperScan可以在一个类上多次使用

@MapperScan("mapper1")
@MapperScan("mapper2")
public class AppConfig{}
// 其实上面这种注解编译后会成为 @MapperScans({@MapperScan("mapper1"),@MapperScan("mapper2")})

2、MapperScans注解导入MapperScannerRegistrar.RepeatingRegistrar类,MapperScan注解导入MapperScannerRegistrar类,对比方法处理的异同。

// RepeatingRegistrar
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScansAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScans.class.getName()));
    if (mapperScansAttrs != null) {
        // 获取value,需要扫描的包或类
        AnnotationAttributes[] annotations = mapperScansAttrs.getAnnotationArray("value");
        for (int i = 0; i < annotations.length; i++) {
            // 将这些类注入spring容器
            registerBeanDefinitions(annotations[i], registry, generateBaseBeanName(importingClassMetadata, i));
        }
    }
}
// MapperScannerRegistrar
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
        // 将这些类注入spring容器
        registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
}

上述两个方法都调用了registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName)方法。

void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    // 构建BeanDefinitionBuilder对象 读取注解中的属性,写到build中
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    Class mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
    String lazyInitialization = annoAttrs.getString("lazyInitialization");
    // 读取 value basePackages basePackageClasses
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    // 注册bd -> MapperScannerConfigurer 这个bd
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}

主要来看MapperScannerConfigurer这个类,这个类是比较关键的一个类。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware{
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) {
            processPropertyPlaceHolders();
        }
        // 扫描器 Spring中的一种扫描器
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        scanner.setAnnotationClass(this.annotationClass);
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
        if (StringUtils.hasText(lazyInitialization)) {
            scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
        }
        // 设置扫描器的过滤方法:匹配指定的类,根据AssignableTypeFilter类进行匹配,排除package-info.java
        scanner.registerFilters();
        // 开启扫描
        scanner.scan(
            StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
    }
}

这个类实现了BeanDefinitionRegistryPostProcessor接口,也就是说在容器刷新过程中的invokeBeanFactoryPostProcessors()回调BeanDefinitionRegistryPostProcessor接口,而在MapperScannerConfigurer类中这个方法会去扫描mapper接口。

// ClassPathMapperScanner#scan
public int scan(String... basePackages) {
    // 扫描前的数量
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
    // 扫描+注册  ClassPathMapperScanner#doScan
    doScan(basePackages);
    // Register annotation config processors, if necessary.
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }
    // 扫描完成后的数量 - 扫描前的数量
    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
// ClassPathMapperScanner#doScan
public Set doScan(String... basePackages) {
    // 扫描包的逻辑 => ClassPathBeanDefinitionScanner#doScan
    Set beanDefinitions = super.doScan(basePackages);
    // 扫描包的后置处理,很关键
    if (!beanDefinitions.isEmpty()) {
        processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
}

虽然mybatis和spring使用了同一个接口去扫描包,但是根据结果分析,Spring扫描的多是注解类,而mybatis会扫描所有的Mapper接口。这个区别就在下面的代码中体现。

org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
    => ClassPathBeanDefinitionScanner#doScan
    => ClassPathScanningCandidateComponentProvider#findCandidateComponents
    => ClassPathScanningCandidateComponentProvider#addCandidateComponentsFromIndex
    => ClassPathScanningCandidateComponentProvider#isCandidateComponent(AnnotatedGenericBeanDefinition sbd)
        # 是否需要添加到容器
// ClassPathScanningCandidateComponentProvider#isCandidateComponent
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    AnnotationMetadata metadata = beanDefinition.getMetadata();
    /**
     * metadata.isIndependent() && (metadata.isConcrete() 是一个顶部类或者静态内部类 并且 不能是接口或者抽象类
     * metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())
     *  可以是抽象类但是用于带有Lookup注解的方法
     */
    return (metadata.isIndependent() && (metadata.isConcrete() ||
                                         (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}

Mybatis的ClassPathMapperScanner继承了ClassPathBeanDefinitionScanner,而ClassPathBeanDefinitionScanner又继承了ClassPathScanningCandidateComponentProvider。

在ClassPathMapperScanner中重写了isCandidateComponent方法,导致筛选条件出现变化。

// ClassPathMapperScanner#isCandidateComponent
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}

筛选逻辑就是:是一个接口 + 是一个顶部类或者静态内部类

关于Spring中类的分类参考另一篇文章

mybatis和spring扫描的不同.png

到这里,mybatis中扫描的逻辑到此为止。

Mapper代理

我们都知道在mybatis中会为每一个Mapper接口生成一个代理对象。而在Spring中,每一个mapper接口都对应一个MapperFactoryBean,当MapperFactoryBean实例化之后,spring会为它内部注册一个sqlSessionTemplate对象,获取这个mapper的代理对象(MapperProxy)时,会调用sqlSessionTemplate的getMapper方法从knownMappers这个map集合中获取mapper的代理工厂,根据这个工厂创建代理对象(MapperProxyFactory#instance)。

  • 外部带调用mapper接口中的方法时,会执行到MapperProxy的invoke方法,进行一些判断是不是接口的默认方法等,最后会调用execute方法,这时的sqlSession还是sqlSessionTemplate。
  • 最后调用到sqlSessionTemplate对象中方法时,这些方法内部调用了一个代理sqlSession代理对象中对应方法,这个类是SqlSessionTemplate的内部类
  • 在调用这个内部类的invoke方法中,创建一个defaultSqlSession对象,调用这个defaultSqlSession对象去执行接下来的缓存查询工作,最后在查询结果出来之后会关闭这个defaultSqlSession对象。

Mapper扫描后的处理

在org.mybatis.spring.mapper.ClassPathMapperScanner#doScan的包扫描完成之后,会对扫描出来的bd进行一次处理

  • 为bd的构造函数注入参数(原来的mapper接口),将bd的class修改为MapperFactoryBean.class
  • 为bd添加添加两个重要属性:sqlSessionFactory + sqlSessionTemplate
  • 设置bd自动状态模型(byType)
  • 设置是否懒加载
private void processBeanDefinitions(Set beanDefinitions) {
    GenericBeanDefinition definition;
    // 遍历扫描出来的bd
    for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (GenericBeanDefinition) holder.getBeanDefinition();
        // mapper接口的class全名
        String beanClassName = definition.getBeanClassName();
        // 为bd的构造函数注入了一个参数
        definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); 
        // 修改了bd的class => mapperFactoryBean.class
        definition.setBeanClass(this.mapperFactoryBeanClass);
        // 向bd中添加了一个属性 => addToConfig
        definition.getPropertyValues().add("addToConfig", this.addToConfig);
        boolean explicitFactoryUsed = false;
        // 向bd的属性添加 sqlSessionFactory
        if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
            definition.getPropertyValues()
                      .add("sqlSessionFactory",new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
            explicitFactoryUsed = true;
        }
        // 向bd的属性添加 sqlSessionTemplate
        if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
            definition.getPropertyValues()
                      .add("sqlSessionTemplate",new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
            explicitFactoryUsed = true;
        }
        // 设置bd的装配模式为 byType => 除了class类型和一些特殊的类型,其余类型会根据属性的set方法去自动装配(方法名和属性名对应)
        // 默认是不自动装配,但是可以使用@AutoAwire和@Resource注解进行装配
        if (!explicitFactoryUsed) {
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
        // 设置懒加载 默认false
        definition.setLazyInit(lazyInitialization);
    }
}

MapperFactoryBean#init

分析MapperFactoryBean的类图


mapperFactoryBean类结构图.png

结合上面的类结构图,每一个包裹了mapper接口的MapperFactoryBean对象在实例化之后都会去调用DaoSupport#afterPropertiesSet方法中的checkDaoConfig方法(子类MapperFactoryBean重写了这个方法)。

// MapperFactoryBean#checkDaoConfig
protected void checkDaoConfig() {
    // 空方法
    super.checkDaoConfig();
    // 获取mybatis的配置对象
    Configuration configuration = getSqlSession().getConfiguration();
    // 检查这个mapper接口是否添加到了knownMappers这个map中
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
        // 没有就将这个mapper接口添加到knownMappers这个map中
        configuration.addMapper(this.mapperInterface);
    }
}
// org.apache.ibatis.session.Configuration#addMapper
public  void addMapper(Class type) {
    // 使用mapper注册器
    mapperRegistry.addMapper(type);
}
// org.apache.ibatis.binding.MapperRegistry#addMapper
public  void addMapper(Class type) {
    if (type.isInterface()) {
        boolean loadCompleted = false;
        try {
            // 构造一个mapper代理的工厂MapperProxyFactory,并将已创建工厂的mapper缓存起来
            knownMappers.put(type, new MapperProxyFactory<>(type));
            MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
             // parse方法 => 根据接口的namespace去加载对应的xml配置文件
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

MapperFactoryBean#getObject

public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
    // 构造方法,传入真实的mapper接口
    public MapperFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
    // 返回bean对象
    public T getObject() throws Exception {
        // 这里的getsqlSession就会得到之前向bd中注入的sqlSessionSTemplate对象
        return getSqlSession().getMapper(this.mapperInterface);
    }
    public Class getObjectType() {
        return this.mapperInterface;
    }
    public boolean isSingleton() {
        return true;
    }
}

进一步看获取mapper对象的方法

// org.mybatis.spring.SqlSessionTemplate#getMapper
public  T getMapper(Class type) {
    return getConfiguration().getMapper(type, this);
}
// org.apache.ibatis.session.Configuration#getMapper 通过注册器获取mapper
public  T getMapper(Class type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}
// org.apache.ibatis.binding.MapperRegistry#getMapper
public  T getMapper(Class type, SqlSession sqlSession) {
    // 获取mapper代理工厂
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
    try {
        // 构建mapper代理对象
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

构建Mapper代理对象

从knownMappers这个map中获取mapperProxyFactory,从这个代理工厂中创建出一个mapper代理对象

// MapperProxyFactory#newInstance
public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return (T) Proxy.newProxyInstance(
                    mapperInterface.getClassLoader(), 
                    new Class[] { mapperInterface }, 
                    // InvocationHandler => MapperProxy
                    mapperProxy);
  }

这里的MapperProxy类实现了InvocationHandler接口,所以到调用mapper接口中的方法时,会调用MapperProxy的invoke方法。这里和单独的mybatis一致。

区别就在于这个sqlSession,在单独使用mybatis时,这里的sqlSession时defaultSqlSession,而在spring中,这里时sqlSessionTemplate

// MapperProxy#invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 处理object中的方法(在target中没有重写object中的方法)
        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else if (method.isDefault()) {
            // 处理默认方法
            if (privateLookupInMethod == null) {
                return invokeDefaultMethodJava8(proxy, method, args);
            } else {
                return invokeDefaultMethodJava9(proxy, method, args);
            }
        }
    } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
    }
    // 注意这里的sqlSession -> sqlSessionTemplate
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
}
// MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
        case INSERT: {
            Object param = method.convertArgsToSqlCommandParam(args);
            // 调用sqlSession
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
        }
    }
}

SqlSessionTemplate

public class SqlSessionTemplate implements SqlSession, DisposableBean {
    // sqlSession工厂
    private final SqlSessionFactory sqlSessionFactory;
    // 执行器类型
    private final ExecutorType executorType;
    // SqlSession代理对象
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;
    // 构造函数
    public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession) newProxyInstance(
                                SqlSessionFactory.class.getClassLoader(),
                                new Class[] { SqlSession.class },
                                new SqlSessionInterceptor());// InvocationHandler
    }
    // 调用sqlSessionProxy
    public int insert(String statement, Object parameter) {
        return this.sqlSessionProxy.insert(statement, parameter);
    }
}

所以当调用sqlSessionTemplate中一些insert update select delete都会执行这个invoke方法

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 创建defaultSqlSession并openSession 
        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)) {
                // 提交事务
                sqlSession.commit(true);
            }
            return result;
        } catch (Throwable t) {
            Throwable unwrapped = unwrapThrowable(t);
            if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                // 如果未加载转换器,则释放连接以避免死锁 修复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 {
            // 在这里会关闭sqlsession
            if (sqlSession != null) {
                closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
            }
        }
    }
}

获取sqlSession

// org.mybatis.spring.SqlSessionUtils#getSqlSession 
// sessionFactory => DefaultSqlSessionFactory
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,PersistenceExceptionTranslator exceptionTranslator) {
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    if (session != null) {
        return session;
    }
    // 创建并打开默认的sqlSession
    LOGGER.debug(() -> "Creating a new SqlSession");
    session = sessionFactory.openSession(executorType);
    // 注册监听器(监听事务管理器触发的事件)
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
    return session;
}
// DefaultSqlSessionFactory#openSession
public SqlSession openSession(ExecutorType execType) {
    return openSessionFromDataSource(execType, null, false);
}
// org.mybatis.spring.SqlSessionUtils#registerSessionHolder
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
    SqlSessionHolder holder;
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        Environment environment = sessionFactory.getConfiguration().getEnvironment();
        if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
            holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
            // 绑定资源
            TransactionSynchronizationManager.bindResource(sessionFactory, holder);
            // 注册监听器
            TransactionSynchronizationManager.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
            holder.setSynchronizedWithTransaction(true);
            holder.requested();
        }
    }
}

SqlSessionSynchronization内部就封装了对各种事件的触发。

获取的session是DefaultSqlSession

// 在mybatis包下:DefaultSqlSessionFactory#openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        // mybatis的环境配置信息
        final Environment environment = configuration.getEnvironment();
        // 在sqlSessionFactoryBean的构建过程中已经为环境注入的事务工厂SpringManagedTransactionFactory
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        // 事务工厂返回的事务对象 SpringManagedTransaction
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 执行器、插件、命名规则等都是在sqlSessionFactoryBean的构建过程中设置的
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        // may have fetched a connection so lets call close()
        closeTransaction(tx); 
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

SpringManagedTransaction

public class SpringManagedTransaction implements Transaction {
    // 根据连接池创建事务对象
    public SpringManagedTransaction(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    // 从连接池中获取连接
    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            openConnection();
        }
        return this.connection;
    }
    // 获取连接
    private void openConnection() throws SQLException {
        this.connection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.connection.getAutoCommit();
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
    }
    // 使用connection.commit
    public void commit() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
            this.connection.commit();
        }
    }
    // 使用connection.rollback
    public void rollback() throws SQLException {
        if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
            this.connection.rollback();
        }
    }
}

开启事务的是在Spring获取到连接之后,在事务管理器中con.setAutoCommit(false);。调用真正的connection对象设置是否自动提交。

// org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin
protected void doBegin(Object transaction, TransactionDefinition definition) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
    Connection con = txObject.getConnectionHolder().getConnection();
    txObject.setPreviousIsolationLevel(previousIsolationLevel);
    // 开启事务
    if (con.getAutoCommit()) {
        con.setAutoCommit(false); // com.mysql.jdbc.ConnectionImpl#setAutoCommit
    }
}

当在Spring的事务管理中调用真正的mapper方法时,这时才进入到mybatis的MapperProxy处理逻辑。


SpringManagedTransaction.png

Mybatis一级缓存失效

mybatis的一级缓存是基于sqlSession的,在上面SqlSessionInterceptor#invoke方法中,每一次调用都会去创建一个session,并且在finally中关闭的session。我们在使用spring+mybatis时是无法干预到sqlSession的创建和关闭的,并且查询完毕就关闭了sqlSession,所以使得一级缓存失效。

1、一级缓存的生命周期

mybatis在开启一个数据库会话时,会创建一个新的sqlSession对象,这个对象中持有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象。当关闭sqlSession时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

  • 如果调用的close方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。
  • 如果调用了clearCache方法,会清空PerpetualCache,但是缓存仍然可以。
  • 如果执行了任意的update操作,都会清空PerpetualCache,但是缓存仍然可以。

2、怎么判断某两次查询是完全相同的查询

满足以下条件一样的两次查询被认为是同一次查询

  • 传入的statementId一致
  • 传入Statement设置的参数一致
  • 本次查询生成的传递给Preparedstatement的sql语句一致
  • 查询返回类型一致

Mybatis二级缓存

二级缓存是基于namespace的缓存,需要在xml中配置 。配置二级缓存意味着:

  • xml文件中的所有select语句将会被缓存
  • xml文件中的所有insert、update和delete语句会刷新缓存
  • 缓存使用默认的Least Recently Used(LRU,最近最少使用的)算法来回收
  • 缓存不会以任何时间顺序来刷新

二级缓存的缺点在于命名空间的缺陷问题,如果对表的操作存在多个namespace,比如在明细表的namespace下查询出主表的明细表的所有数据,同时有用户去修改了主表namespace下的主从数据,这里明细表的namespace缓存是无法更新的,那么查询到的数据依旧是过时的数据(出现脏数据)。

如果要使用的话最好将关联的所有表的操作都放在同一个namespace下。

xml文件的解析

Mybatis在spring中是如何处理mapper的xml文件的?

在spring整合myabtis时我们需要指定mapper xml文件的位置,因此这种资源很有可能在SqlSessionFactory创建的时候解析

// 设置静态xml资源路径
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResource("classpath:mapper/BookInfoMapper.xml"));

在org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory构建SqlSessionFactory是会去找MapperLocations

// 核心代码如下
if (this.mapperLocations != null && this.mapperLocations.length != 0) {
    for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
            continue;
        }
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(
            mapperLocation.getInputStream(),
            targetConfiguration, 
            mapperLocation.toString(), 
            targetConfiguration.getSqlFragments());
        // 解析xml
        xmlMapperBuilder.parse();
    }
}
// org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        // 进行解析xml
        configurationElement(parser.evalNode("/mapper"));
        configuration.addLoadedResource(resource);
        // 将解析出来的xml添加到configuration中
        // 使用mapper注册器注册mapper(内部会显示这个xml文件已经加载过了 -> namespace:xxxx)
        bindMapperForNamespace();
    }
    // 解析待定的  节点
    parsePendingResultMaps();
    // 解析待定的  节点
    parsePendingCacheRefs();
    // 解析待定的 SQL 语句的节点
    parsePendingStatements();
}

XMLMapperBuilder#bindMapperForNamespace

configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
// org.apache.ibatis.binding.MapperRegistry#addMapper
// 解析xml,维护mapper中的方法和xml的映射关系
parser.parse();
// MapperAnnotationBuilder#parse
public void parse() {
    String resource = type.toString();
    // 检查是否已经加载  resource: interface top.gmfcj.mapper.BookInfoMapper
    if (!configuration.isResourceLoaded(resource)) {
        // 加载xml 会再次检查另一个资源名称  namespace:top.gmfcj.mapper.BookInfoMapper
        // 一般的xml走到这里会直接出来,在SqlSessionFactory创建的时候就已经标记为已经解析了 注意资源名称
        loadXmlResource();
        // 把当前的interface top.gmfcj.mapper.BookInfoMapper标记为已加载
        configuration.addLoadedResource(resource);
        assistant.setCurrentNamespace(type.getName());
        parseCache();
        parseCacheRef();
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            // issue #237
            if (!method.isBridge()) {
                // 解析注解的方法
                parseStatement(method);
            }
        }
    }
    // 因parseStatement存在依赖资源不存在而解析失败,在解析完之后重新尝试再次解析。
    parsePendingMethods();
} 
// 解析方法 MapperAnnotationBuilder#parseStatement 
void parseStatement(Method method) {
    // 参数类型
    Class parameterTypeClass = getParameterType(method);
    // 方法驱动类型  XmlLangugeDriver RawLanguageDriver(注解)
    LanguageDriver languageDriver = getLanguageDriver(method);
    // xml在这里或拿不到sqlSource而返回
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
        // 处理注解 处理完成或会缓存起来
    }
}

Mybatis在Spring环境下的事务

在前面的文章中我们介绍了Spring的声明式事务的基本原理。以DataSourceTransactionManager为例,它负责修改事务状态、开启事务并处理多事务的传播方式、触发提交回滚前后的事件、进行事务的提交和回滚等。


mybatis在spring下的事务处理.png

你可能感兴趣的:(Mybatis对Spring扩展点的应用)