Spring之整合MyBatis底层原理源码解析

一、整合核心思路

由很多框架都需要和Spring进行整合,而整合的核心思想就是把其他框架所产生的对象放到Spring容器中,让其成为Bean。​

比如Mybatis,Mybatis框架可以单独使用,而单独使用Mybatis框架就需要用到Mybatis所提供的一些类构造出对应的对象,然后使用该对象,就能使用到Mybatis框架给我们提供的功能,和Mybatis整合Spring就是为了将这些对象放入Spring容器中成为Bean,只要成为了Bean,在我们的Spring项目中就能很方便的使用这些对象了,也就能很方便的使用Mybatis框架所提供的功能了。

1.1 Mybatis中Mapper的工作原理分析

我们平时使用mybatis的时候往往是和Spring一起使用的。其实Mybatis本身是可以单独使用的。

在这里插入图片描述
我们知道,我们平时只需要写一个Mapper接口就可以了,不需要写实现类。那mybatis是怎么构建Mapper的呢?

我们进入到sqlSession.getMapper方法中:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
   MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
   if (mapperProxyFactory == null) {
       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
   } else {
       try {
           return mapperProxyFactory.newInstance(sqlSession);
       } catch (Exception var5) {
           throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
       }
   }
}

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

// org.apache.ibatis.binding.MapperProxyFactory#newInstance(org.apache.ibatis.binding.MapperProxy<T>)
// JDK动态代理返回代理的Mapper对象
protected T newInstance(MapperProxy<T> mapperProxy) {
    return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}

在最终的newInstance方法中,我们可以看到是使用的JDK的动态代理来返回一个代理的Mapper对象的。

那么现在我们要关注的应该是当我们使用这个代理对象执行某个Mapper中的方法的时候,代理逻辑是怎么样的呢?所以我们现在要关注这个mapperProxy,它里面应该是定义了具体的InvocationHandler中invoke方法的逻辑。

我们进入到org.apache.ibatis.binding.MapperProxy类中:

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    // 定义Mapper对应的Class,这里是写的比较灵活,因为可能有非常多的Mapper
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

	// 代理逻辑
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
        	// 如果对象是Object,直接反射执行该方法即可
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            }

			// 如果方法是一个默认的方法:公共的、非抽象的、非static的方法,也是直接执行方法
            if (method.isDefault()) {
                return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }

		// 如果是其他的,比如接口中的抽象方法,就会执行一些额外的代理逻辑,比如缓存方法
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        // 执行Mapper中方法的时候的代理逻辑
        return mapperMethod.execute(this.sqlSession, args);
    }
 ........   

我们重点看一下mapperMethod.execute(this.sqlSession, args);方法:

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    Object param;
    switch(this.command.getType()) {
    case INSERT:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
        break;
    case UPDATE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
        break;
    case DELETE:
        param = this.method.convertArgsToSqlCommandParam(args);
        result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
        break;
    case SELECT:
        if (this.method.returnsVoid() && this.method.hasResultHandler()) {
            this.executeWithResultHandler(sqlSession, args);
            result = null;
        } else if (this.method.returnsMany()) {
            result = this.executeForMany(sqlSession, args);
        } else if (this.method.returnsMap()) {
            result = this.executeForMap(sqlSession, args);
        } else if (this.method.returnsCursor()) {
            result = this.executeForCursor(sqlSession, args);
        } else {
            param = this.method.convertArgsToSqlCommandParam(args);
            result = sqlSession.selectOne(this.command.getName(), param);
            if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                result = Optional.ofNullable(result);
            }
        }
        break;
    case FLUSH:
        result = sqlSession.flushStatements();
        break;
    default:
        throw new BindingException("Unknown execution method for: " + this.command.getName());
    }

    if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
        throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
    } else {
        return result;
    }
}

public SqlCommandType getType() {
   return this.type;
}

这一下就明白了,我们在Mapper接口中定义的方法上一般会添加@Select、@Update、@Delete等注解,相当于规定了方法的类型。然后执行代理逻辑的时候会在switch(this.command.getType())中判断方法类型,然后执行org.apache.ibatis.binding.MapperMethod.MethodSignature#convertArgsToSqlCommandParam方法,将注解或者配置文件中的参数解析为sql语句,然后调用org.apache.ibatis.session.SqlSession#insert(java.lang.String, java.lang.Object)(或者其他的更新、删除、查询)方法执行sql并获得返回结果!

这样一来,我们对mybatis的整体工作流程应该是有了一个比较清晰的认知。

注意:JDK动态代理是代理的接口,即使这个接口没有任何的实现类,我们也能自己在invoke方法中实现代理逻辑并返回一个代理结果,甚至都不需要反射调用真正的方法(也无法调用,因为没有实现类意味着没有真正的对象)。

JDK动态代理代理无实现类的接口
public class ProxyTest2 {
    public static void main(String[] args) {
        UserService proxyInstance = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("代理方法被执行了(执行的是代理逻辑,没有执行真正的方法)...");
                        return "sayHelloTo " + args[0];
                    }
                });

        String result = proxyInstance.sayHelloTo("jihu");
        System.out.println("代理逻辑返回的方法结果" + result);

    }
}

在这里插入图片描述

1.2 mybatis整合spring思路梳理

我们平时在项目中使用mybatis的时候往往是这样的:

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
}

如果说现在我们来启动Spring容器的话:
在这里插入图片描述
思考:此时能启动成功吗?

有spring使用经验的小伙伴应该可以看出来,此时一定会报错:NoSuchBeanDefinition的错误。因为我们自己定义的UserMapper此时只是一个接口,根本就没有实现类,所以是无法将其注册成一个Bean的。实例化的时候就报错了。

所以说,此时我们必须要给UserMapper一个实例化对象,但是又不能创建实现类,那该怎么办到呢?艾,我们之前分析mybatis中Mapper的工作原理的时候,不是看到mybatis为所有的mapper创建了代理对象吗?那我们能不能将那个代理对象赋值给这个UserMapper呢?这时候我们使用userMapper执行具体方法的时候,不就相当于在使用mybaits了吗?

这样看来,我们需要在Spring的生命周期中找到合适的地方,自己来将这些mapper注入到容器中。

1.2.1 如果优雅的将mybatis中mapper的代理对象注入到容器中

思考,我们能通过普通的BeanDefinition来完成注册码?

显然是不行的,因为我们通过BeanDefinition来注册Bean的时候必须要指定类型,但是此时类型只能指定成接口,这显然是不行的,因为启动容器会直接报错,依然无法实例化对象。

那该如何解决呢?有没有可以自动生成一个对象然后赋值给Bean的呢?而不是让Spring自己去实例化对象?

熟悉Spring的小伙伴应该能想到FactoryBean。我们知道FactoryBean实例化的对象正是其getObject方法中返回的对象。那我们能够在getObject方法中获取到mybatis的代理对象然后将其返回,这样不就可以将代理对象注入到容器中了吗!!!

@Component  // 需要将factoryBean注入到Spring容器中
public class MapperFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        // 这里获取并返回Mybatis中创建的代理对象(暂时使用JDK动态代理代替...)
        // ====> 使用JDK代理生成代理对象
        UserMapper proxyInstance = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            	// 指定代理逻辑,解析sql并执行,然后返回执行结果
                return null;
            }
        });
        // 这样就返回了代理对象了
        return proxyInstance;
    }

    @Override
    public Class<?> getObjectType() {
        return UserMapper.class;
    }
}

这样可以处理UserMapper了,但是思考一下,我们项目中一般会有非常多的Mpper,而且整合的时候也不知道项目中是哪些mapper呀???所以我们要把这个Mapper的类型写活:

@Component
public class MapperFactoryBean implements FactoryBean {

    // 将Mapper的类型写活
    private Class mapperInterface;

    public MapperFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }
 ......   

这样我们对不同的Mapper传入不同的 mapperInterface即可。但是此时还有另一个问题,我们这里的MapperFactoryBean 上加了@Component注解,意味着只能生成一个MapperFactoryBean,这显然是不对的。应该是每个mapper都生成一个对应的代理对象。所以我们不能将MapperFactoryBean注入到容器中。

思路: 我们回到使用BeanDefinition注册mapper的思路,我们可以将每个Mapper的beanDefiniton的类型设置为MapperFactoryBean,然后他们的名字不同。这样就可以成功的将所有Mapper注入到容器中去了。

修改后的MapperFactoryBean:

// 有多少个Mapper,就应该要生成多少个代理对象,如果加上@Component,就只能生成一个代理对象了。所以不能将其注入到容器!
public class MapperFactoryBean implements FactoryBean {

    // 将Mapper的类型写活
    private Class mapperInterface;

    public MapperFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object getObject() throws Exception {
        // 这里获取并返回Mybatis中创建的代理对象(暂时使用JDK动态代理代替...)
        // ====> 使用JDK代理生成代理对象
        UserMapper proxyInstance = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return null;
            }
        });
        // 这样就返回了代理对象了
        return proxyInstance;
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }
}

注入beanDefinition:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

// ===> UserMapper
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
// 指定bean的类型为MapperFactoryBean
beanDefinition.setBeanClass(MapperFactoryBean.class);
// 使用构造方法注入,让其对mapperInterface属性赋值
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
context.registerBeanDefinition("userMapper", beanDefinition);  // userMapper --> UserMapper代理对象

// ===> OrderMapper
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
// 指定bean的类型为MapperFactoryBean
beanDefinition.setBeanClass(MapperFactoryBean.class);
// 使用构造方法注入,让其对mapperInterface属性赋值
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
context.registerBeanDefinition("orderMapper", beanDefinition);  // orderMapper --> OrderMapper代理对象

我们知道,当FactoryBean作为一个bean的类型,在注入到spring容器中的时候,并不会实例化这个FactoryBean对象然后将其设置给当前bean,而是去调用其getObject方法获取到返回的对象,然后将这个结果作为当前bean的实例化对象。

这样一来,每一个Mapper注入的实例化对象其实就是getObject方法返回的对象。而我们在getObject方法中获取到mybatis的代理对象并返回,这样就成功将mybatis的代理对象设置给mapper。 这样当我们执行mapper对应方法的时候,就会执行mybatis中定义的代理逻辑了。

思考:此时问题是我们这样显示的创建beanDefinition不是很好,有没有其他的方式创建呢?

使用BeanDefinitionRegistryPostProcessor完成注册:

@Component
public class MapperBeanDefinitionRegistryProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // ===> UserMapper
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        // 指定bean的类型为MapperFactoryBean
        beanDefinition.setBeanClass(MapperFactoryBean.class);
        // 使用构造方法注入,让其对mapperInterface属性赋值
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
        registry.registerBeanDefinition("userMapper", beanDefinition);  // userMapper --> UserMapper代理对象

        // ===> OrderMapper
        AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        // 指定bean的类型为MapperFactoryBean
        beanDefinition.setBeanClass(MapperFactoryBean.class);
        // 使用构造方法注入,让其对mapperInterface属性赋值
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
        registry.registerBeanDefinition("orderMapper", beanDefinition);  // orderMapper --> OrderMapper代理对象
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        // ...
    }
}

这样也可以完成beanDefinintion的注册,而且不用写在spring启动的时候注册了。

但是此时依然存在的问题是,我们无法知道有哪些Mapper。这时候有没有想起来我们平常使用的@MapperScan注解?我们可以扫描得所有的mapper,然后一个一个注入。所有还需要有扫描的支持。

我们先定义一个扫描注解然后将其添加到配置类上:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyMapperScan {
    String value();
}

@ComponentScan("com.jihu")
@MyMapperScan("com.jihu.mapper")
@Import(MapperImportBeanDefinitionRegistry.class)
public class AppConfig {
}

但是我们之前使用的BeanDefinitionRegistryPostProcessor 只有注册beanDefinition的功能,没有扫描的功能,所以我们要使用带扫描功能的ImportBeanDefinitionRegistrar来完成整个Mapper的扫描和注入:

// 不能通过注解添加到Spring容器中,只能通过@Import
public class MapperImportBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
        // importingClassMetadata 注解的元数据信息
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName());
        // 获取到扫描路径
        String scanPath = (String) annotationAttributes.get("value");
        // 扫描mapper(利用spring的扫描器)
        // 1、此时mapper都是接口,spring是无法扫描到的。所以使用自定义的scanner,只扫描接口
        MapperBeanDefinitionScanner scanner = new MapperBeanDefinitionScanner(registry);

        // 2、spring将类扫描成bean的时候会判断有没有加@Component,这个逻辑是在一个includeFilter中完成的
        // 我们使用mybatis不会在mapper中添加注解,所以需要自己定义一个includeFilter
        // 增加一个包含过滤器,将所有的类(不管有没有添加注解)都扫描成beanDefinition
        scanner.addExcludeFilter(new TypeFilter() {
            @Override
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                // 扫描到任何类或者接口都进行注入,不再判断是否加了@Component注解
                return true;
            }
        });
        // 完成扫描和beanDefiniton的注册
        scanner.scan(scanPath);
    }
}
// 自己实现扫描器,目的是为了能扫描到接口。Spring默认的不会扫描接口
public class MapperBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
    public MapperBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }


    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        // spring扫描出来的beanDefinition的类型默认是不对的,我们需要自己修改一下
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();

            // 给MapperFactoryBean.mapperInterface传值(string会在类加载后自动转化成class)
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
            beanDefinition.setBeanClassName(MapperFactoryBean.class.getName());
        }

        return beanDefinitionHolders;
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        // 支持接口扫描
        return beanDefinition.getMetadata().isInterface();
    }
}

现在我们可以扫描注册所有的Mapper,唯一还存在问题的就是如何在FactoryBean中返回mybatis的代理对象,因为我们之前使用的JDK代理对象进行替代的。

进行改造:

首先需要自己注入一个SqlSessionFactory用于实例化sqlSession:

@ComponentScan("com.jihu")
@MyMapperScan("com.jihu.mapper")
@Import(MapperImportBeanDefinitionRegistry.class)
public class AppConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws IOException {
        // 构建一个SqlSessionFactory
        InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build((InputStream) null);
        return sqlSessionFactory;
    }
}

然后修改BeanFactory:

public class MapperFactoryBean implements FactoryBean {

    // 将Mapper的类型写活
    private Class mapperInterface;

    private SqlSession sqlSession;

    public MapperFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    @Autowired // 调用setter方法之前,spring会先找到sqlSessionFactory对应的bean。所以我们需要自己注入一个SqlSessionFactory
    public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
    	sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
        // 实例化sqlSession
        this.sqlSession = sqlSessionFactory.openSession();
    }

    @Override
    public Object getObject() throws Exception {
        // 返回mybatis代理对象
        return sqlSession.getMapper(mapperInterface);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }
}

这里其实可以省略掉setter方法上的@Autowired注解,这样可以完全脱离spring。但是需要修改自定义扫描器中的doScan方法:

@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
    // spring扫描出来的beanDefinition的类型默认是不对的,我们需要自己修改一下
    for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
        GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();

        // 给MapperFactoryBean.mapperInterface传值(string会在类加载后自动转化成class)
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
        beanDefinition.setBeanClassName(MapperFactoryBean.class.getName());
        // 让MapperFactoryBean中的setter方法可以被自动执行(省去添加的@Autowired注解)
        beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }

    return beanDefinitionHolders;
}

这样一来,我们就逐渐完善了spring整合mybatis的所有流程和思想。加下来我们就在源码中来验证我们的思路是否正确。

二、Mybatis-Spring 1.3.2版本底层源码执行流程

Spring整合Mybatis之后SQL执行流程:
在这里插入图片描述
1、通过@MapperScan导入了MapperScannerRegistrar类

2、MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以Spring在启动时会调用MapperScannerRegistrar类中的registerBeanDefinitions方法

3、在registerBeanDefinitions方法中定义了一个ClassPathMapperScanner对象,用来扫描mapper

4、设置ClassPathMapperScanner对象可以扫描到接口,因为在Spring中是不会扫描接口的

5、同时因为ClassPathMapperScanner中重写了isCandidateComponent方法,导致isCandidateComponent只会认为接口是备选者Component

6、通过利用Spring的扫描后,会把接口扫描出来并且得到对应的BeanDefinition

7、接下来把扫描得到的BeanDefinition进行修改,把BeanClass修改为MapperFactoryBean,把AutowireMode修改为byType

8、扫描完成后,Spring就会基于BeanDefinition去创建Bean了,相当于每个Mapper对应一个FactoryBean

9、MapperFactoryBean中的getObject方法中,调用了getSqlSession()去得到一个sqlSession对象,然后根据对应的Mapper接口生成一个Mapper接口代理对象,这个代理对象就成为Spring容器中的Bean

10、sqlSession对象是Mybatis中的,一个sqlSession对象需要SqlSessionFactory来产生

11、MapperFactoryBean的AutowireMode为byType,所以Spring会自动调用set方法,有两个set方法,一个setSqlSessionFactory,一个setSqlSessionTemplate,而这两个方法执行的前提是根据方法参数类型能找到对应的bean,所以Spring容器中要存在SqlSessionFactory类型的bean或者SqlSessionTemplate类型的bean。

12、如果你定义的是一个SqlSessionFactory类型的bean,那么最终也会被包装为一个SqlSessionTemplate对象,并且赋值给sqlSession属性

13、而在SqlSessionTemplate类中就存在一个getMapper方法,这个方法中就产生一个Mapper接口代理对象

14、到时候,当执行该代理对象的某个方法时,就会进入到Mybatis框架的底层执行流程。

三、Mybatis-Spring 2.0.6版本(最新版)底层源码执行流程

1、通过@MapperScan导入了MapperScannerRegistrar类

2、MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以Spring在启动时会调用MapperScannerRegistrar类中的registerBeanDefinitions方法

3、在registerBeanDefinitions方法中注册一个MapperScannerConfigurer类型的BeanDefinition

4、而MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,所以Spring在启动过程中时会调用它的postProcessBeanDefinitionRegistry()方法

5、在postProcessBeanDefinitionRegistry方法中会生成一个ClassPathMapperScanner对象,然后进行扫描

6、后续的逻辑和1.3.2版本一样。

带来的好处是,可以不使用@MapperScan注解,而可以直接定义一个Bean,比如:

@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
	MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
	mapperScannerConfigurer.setBasePackage("com.jihu");
	return mapperScannerConfigurer;
}

四、Spring整合Mybatis后一级缓存失效问题

Mybatis中的一级缓存是基于SqlSession来实现的,所以在执行同一个sql时,如果使用的是同一个SqlSession对象,那么就能利用到一级缓存,提高sql的执行效率。​

但是在Spring整合Mybatis后,如果没有执行某个方法时,该方法上没有加@Transactional注解,也就是没有开启Spring事务,那么后面在执行具体sql时,每执行一个sql时都会新生成一个SqlSession对象来执行该sql,这就是我们说的一级缓存失效(也就是没有使用同一个SqlSession对象),而如果开启了Spring事务,那么该Spring事务中的多个sql,在执行时会使用同一个SqlSession对象,从而一级缓存生效,具体的底层执行流程在上图。​

个人理解:实际上Spring整合Mybatis后一级缓存失效并不是问题,是正常的实现、因为,一个方法如果没有开启Spring事务,那么在执行sql时候,那就是每个sql单独一个事务来执行,也就是单独一个SqlSession对象来执行该sql,如果开启了Spring事务,那就是多个sql属于同一个事务,那自然就应该用一个SqlSession来执行这多个sql。

所以,在没有开启Spring事务的时候,SqlSession的一级缓存并不是失效了,而是存在的生命周期太短了(执行完一个sql后就被销毁了,下一个sql执行时又是一个新的SqlSession了)。​

spring整合mybatis源码位置 :org.mybatis.spring.SqlSessionUtils#registerSessionHolder:

private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
	// 如果开启了Spring事务,这里返回true,代表才会将SqlSessionHolder对象缓存到ThreadLocal中
	// 如果没有开启,则不进行缓存。这也就是为什么mybatis整合spring之后缓存失效的原因。
    if (TransactionSynchronizationManager.isSynchronizationActive()) {
        Environment environment = sessionFactory.getConfiguration().getEnvironment();
        if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
            LOGGER.debug(() -> {
                return "Registering transaction synchronization for SqlSession [" + session + "]";
            });
            SqlSessionHolder holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
            TransactionSynchronizationManager.bindResource(sessionFactory, holder);
            TransactionSynchronizationManager.registerSynchronization(new SqlSessionUtils.SqlSessionSynchronization(holder, sessionFactory));
            holder.setSynchronizedWithTransaction(true);
            holder.requested();
        } else {
            if (TransactionSynchronizationManager.getResource(environment.getDataSource()) != null) {
                throw new TransientDataAccessResourceException("SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
            }

            LOGGER.debug(() -> {
                return "SqlSession [" + session + "] was not registered for synchronization because DataSource is not transactional";
            });
        }
    } else {
        LOGGER.debug(() -> {
            return "SqlSession [" + session + "] was not registered for synchronization because synchronization is not active";
        });
    }

}

这样来讲,当mybatis整合spring之后,如果我们在方法上加了@Transaction注解,则方法中mybatis的一级缓存生效,否则不生效,因为每一个sql执行的时候都会创建新的sqlSession。

为什么工作中不建议使用mybatis的一级缓存

因为我们认为数据库的隔离级别要更加重要。比如我们配置了数据库隔离级别为读未提交,同时开启了mybaits的一级缓存。那么当我们在代码中第二次执行某个sql的时候,要以缓存为主(查询到的是第一次查询的结果),还是以隔离级别为主(期间可能产生新数据,所以会查询到更多的结果)为准呢?所以一般会关闭mybatis的一级缓存。

五、mybatis中解决sqlSession线程安全问题的方法

先来看单独使用mybatis的代码:

public static void main(String[] args) throws IOException {
    InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");

    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build((InputStream) null);
    SqlSession sqlSession = sqlSessionFactory.openSession();

    OrderDao mapper = sqlSession.getMapper(OrderDao.class);

    String result = mapper.selectById();
}

再来看sqlSession对象:

public class DefaultSqlSession implements SqlSession {
    private final Configuration configuration;
    private final Executor executor;
    private final boolean autoCommit;
    private boolean dirty;
    private List<Cursor<?>> cursorList;

    public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
        this.configuration = configuration;
        this.executor = executor;
        this.dirty = false;
        this.autoCommit = autoCommit;
    }

    public DefaultSqlSession(Configuration configuration, Executor executor) {
        this(configuration, executor, false);
    }

    public <T> T selectOne(String statement) {
        return this.selectOne(statement, (Object)null);
    }
.......

可以看到里面有很多的共享属性,并且方法上都没有加锁。那么多个线程执行的时候,应该是有线程安全问题的???

其实在源码中我们可以发现,mybatis的代理对象是通过SqlSessionTemplate中的getMapper方法获取的:

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

当我们执行某个Mapper.selectOne方法的时候,其实就是在调用sqlSession.selectOne方法。

但是SqlSessionTemplate中的getMapper方法中参数传是this,相当于调用selectOne方法的时候是在调用sqlSessionTemplate.selectOne方法:

public <T> T selectOne(String statement, Object parameter) {
  return this.sqlSessionProxy.selectOne(statement, parameter);
}

可以看到,这里又会调用sqlSessionProxy.selectOne方法,那么SqlSessionProxy又是什么呢?

当我们构造SqlSessionTemplate的时候就会创建这个SqlSessionProxy:

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
   Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    Assert.notNull(executorType, "Property 'executorType' is required");
    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}

引入SqlSessionTemplate就是为了解决线程安全问题,方式是通过ThreadLocal。

这样当我们执行方法的时候都会进入到SqlSessionTemplate.SqlSessionInterceptor()代理逻辑中:

private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

   Object unwrapped;
   try {
       Object result = method.invoke(sqlSession, args);
       if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
           sqlSession.commit(true);
       }

       unwrapped = result;
   } catch (Throwable var11) {
       unwrapped = ExceptionUtil.unwrapThrowable(var11);
       if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
           SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
           sqlSession = null;
           Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
           if (translated != null) {
               unwrapped = translated;
           }
       }

       throw (Throwable)unwrapped;
   } finally {
       if (sqlSession != null) {
           SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
       }

   }

   return unwrapped;
}

我们重点关注SqlSession的获取,来看SqlSessionUtils.getSqlSession方法:

public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
    Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
    Assert.notNull(executorType, "No ExecutorType specified");
    // 从threadLocal中获取
    SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
    SqlSession session = sessionHolder(executorType, holder);
    // 找到了返回
    if (session != null) {
        return session;
    } else {
    	// 找不到则创建,并添加到ThreadLocal中
        LOGGER.debug(() -> {
            return "Creating a new SqlSession";
        });
        session = sessionFactory.openSession(executorType);
        registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
        return session;
    }
}

我们看看SqlSessionHolder 是存在哪里的:

public abstract class TransactionSynchronizationManager {
    private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
    private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");
    private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");
    private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");
    private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");

......

这就是我们平常说的,mybatis数据库连接为何是线程安全的,我们都知道是存储在ThreadLocal中的,这回终于得到了验证。

ThreadLocal中存储的SqlSessionHolder 对象。这样每个sqlSession都在自己的线程栈中拥有一个独立的DefaultSqlSession对象,自然就不会存在线程安全问题了。

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值