Spring源码|Spring源码之整合Mybatis底层实现

目录
1. Spring整合Mybatis底层源码
2. SqlSessionTemplate类的作用
3. Mybatis一级缓存失效问题
1. Spring整合Mybatis底层源码 Mybatis框架可以单独使用,需要用到Mybatis所提供的一些类构造出对应的Mapper对象,然后就能使用Mybatis框架提供的功能,我们先看一个Demo:

@Test public void testMybatis() throws IOException { // 读取配置文件 InputStream inputStream = Resources.getResourceAsStream("mybatis.xml"); // 解析配置文件,得到SqlSession工厂类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 从工厂中获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); // 然后从SqlSession中获取Mapper的代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); String result = mapper.selectById(); sqlSession.commit(); sqlSession.flushStatements(); sqlSession.close(); }

这是一段很普通的Mybatis的代码,我们点进去看一下getMapper()方法,看一下Mybatis如何生成UserMapper代理对象,这个方法是SqlSession接口的方法,这个接口里面有很多我们平常使用的CRUD方法:
public interface SqlSession extends Closeable { T selectOne(String var1); T selectOne(String var1, Object var2); List selectList(String var1); List selectList(String var1, Object var2); List selectList(String var1, Object var2, RowBounds var3); Map selectMap(String var1, String var2); Map selectMap(String var1, Object var2, String var3); Map selectMap(String var1, Object var2, String var3, RowBounds var4); Cursor selectCursor(String var1); Cursor selectCursor(String var1, Object var2); Cursor selectCursor(String var1, Object var2, RowBounds var3); void select(String var1, Object var2, ResultHandler var3); void select(String var1, ResultHandler var2); void select(String var1, Object var2, RowBounds var3, ResultHandler var4); int insert(String var1); int insert(String var1, Object var2); int update(String var1); int update(String var1, Object var2); int delete(String var1); int delete(String var1, Object var2); void commit(); void commit(boolean var1); void rollback(); void rollback(boolean var1); List flushStatements(); void close(); void clearCache(); Configuration getConfiguration(); T getMapper(Class var1); Connection getConnection(); }

主要看它默认的实现类DefaultSqlSession的方法:
所属类:org.apache.ibatis.session.defaults.DefaultSqlSession
public T getMapper(Class type) { return this.configuration.getMapper(type, this); }

所属类:org.apache.ibatis.session.Configuration
public T getMapper(Class type, SqlSession sqlSession) { return this.mapperRegistry.getMapper(type, sqlSession); }

所属类:org.apache.ibatis.binding.MapperRegistry
public T getMapper(Class type, SqlSession sqlSession) { MapperProxyFactory 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); } } }

mapperProxyFactory.newInstance(sqlSession)就会返回代理对象:
所属类:org.apache.ibatis.binding.MapperProxyFactory
public class MapperProxyFactory { private final Class mapperInterface; private final Map methodCache = new ConcurrentHashMap(); public MapperProxyFactory(Class mapperInterface) { this.mapperInterface = mapperInterface; }public Class getMapperInterface() { return this.mapperInterface; }public Map getMethodCache() { return this.methodCache; }protected T newInstance(MapperProxy mapperProxy) { return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy); }public T newInstance(SqlSession sqlSession) { MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache); return this.newInstance(mapperProxy); } }

把mapper传进去,通过JDK的动态代理生成代理对象。我们可以看一下MapperProxy类,它实现了InvocationHandler接口。
public class MapperProxy implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class mapperInterface; private final Map methodCache; public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) { this.sqlSession = sqlSession; this.mapperInterface = mapperInterface; this.methodCache = methodCache; }public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); }if (this.isDefaultMethod(method)) { return this.invokeDefaultMethod(proxy, method, args); } } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); }MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); }private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration()); this.methodCache.put(method, mapperMethod); }return mapperMethod; }

当我们得到代理对象,去执行Mapper接口里面的方法时,Mybatis就会进入MapperProxy类的invoke()方法,得到方法上面的注解(如:@Select("select 'user'")),然后去执行目标方法。
所有MapperProxy类很重要,上面这些生成代理逻辑,都是Mybatis提供的。
回到Spring,如果Mybatis要和Spring整合,除了Mybatis自带的jar包之外,还需整合的jar包:
org.mybatis mybatis-spring 1.3.1

为什么呢?举个例子:
@Component public class UserService { @Autowired private UserMapper userMapper; public void test() { System.out.println(userMapper.selectById()); }}

上面的代码貌似没问题,但是如果没有整合jar包,就会报找不到userMapper这个Bean的错误。
我们知道,Spring在Bean的实例化中进行属性注入时,找不到userMapper,因为在过程中需要把Mybatis生成的UserMapper代理对象注入到这里,赋值该属性,才能进行后续的操作,Spring和其他框架整合亦如此。会把Mybatis创建的类(或代理对象)注册到Spring容器,Spring在用的时候才能找到。
这个章节要说的就是如何把Mybatis生成userMapper变成Spring所需的Bean,但是UserMapper是接口,所以不能用固有的思路通过Spring上下文去getBean(),接口是无法实例化的。
答案就是利用FactoryBean接口,例如:
/** * @author Kieasar */ @Component public class BaecFactoryBean implements FactoryBean { @Override public Object getObject() { // 参数中把类加载器传进去,然后new一个UserMapper,生成代理对象 Object newInstance = Proxy.newProxyInstance(BaecFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() { @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("执行代理对象的方法"); return null; } }); return newInstance; } // 把生成的代理对象的类型返回 @Override public Class getObjectType() { return UserMapper.class; } }

此时,这个Bean对象所对应的Bean就是getObject()方法返回的代理对象(newInstance),接下来,单元测试一下:
@Test public void main() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(AppConfig.class); context.refresh(); UserService userService = (UserService) context.getBean("userService"); userService.test(); }

执行结果没有报错,说明属性被赋值了,很明显,因为通过属性去找,能找到上面BaecFactoryBean生成的代理对象。
但是test()方法打印结果却为null,因为此时执行的是代理对象的invoke()方法。
上面的例子,貌似我们实现了Mybatis的功能,但是有个问题,此时只有一个UserMapper,如果再有OrderMapper或其他很多的类需要注入,难道我们要写很多个FactoryBean吗?所以,当当前的思路是不行滴~
于是,又想到,代码不要写死,这样不就可以了嘛:
/** * @author Kieasar */ @Component public class BaecFactoryBean implements FactoryBean { private Class mapperInterface; public BaecFactoryBean(Class mapperInterface) { this.mapperInterface = mapperInterface; } @Autowired public void setSqlSession(SqlSessionFactory sqlSessionFactory) { sqlSessionFactory.getConfiguration().addMapper(mapperInterface); this.sqlSession = sqlSessionFactory.openSession(); } @Override public Object getObject() { Object newInstance = Proxy.newProxyInstance(BaecFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() { @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { System.out.println("执行代理对象的方法"); return null; } }); return newInstance; } // 把生成的代理对象的类型返回 @Override public Class getObjectType() { return mapperInterface; } }

但是,@Component注解修饰的类,注入的时候是单例的,支持生成一个代理对象,不能生成我们需要的若干个Bean,但是,这样可以呀~
@Test public void main() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(AppConfig.class); context.refresh(); AbstractBeanDefinition userMapperBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition(); userMapperBeanDefinition.setBeanClass(BaecFactoryBean.class); userMapperBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class); // 得到一个userMapper代理对象 context.registerBeanDefinition("userMapper",userMapperBeanDefinition); AbstractBeanDefinition orederMapperBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition(); orederMapperBeanDefinition.setBeanClass(BaecFactoryBean.class); orederMapperBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class); // 得到一个orderMapper代理对象 context.registerBeanDefinition("orderMapper",orederMapperBeanDefinition); UserService userServcie = (UserService) context.getBean("userServcie"); userServcie.test(); }

确实可以,但是如果有很多的类需要注入呢……
用扫描的方式,则需要一个扫描器,继承ClassPathBeanDefinitionScanner类,就有了扫描器的功能。
public class BaecBeanDefinitionScanner extends ClassPathBeanDefinitionScanner { public BaecBeanDefinitionScanner(BeanDefinitionRegistry registry) { super(registry); } @Override protected Set doScan(String... basePackages) { Set beanDefinitionHolders = super.doScan(basePackages); for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) { BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition(); // 往构造方法的入参中传值的话,传的就是这个mapper解析之后的BeanDefinition的名称 beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName()); beanDefinition.setBeanClassName(BaecFactoryBean.class.getName()); } return beanDefinitionHolders; } @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface(); } }

但是Spring不关心接口,Mybatis只关心接口,所以,还需要加工一下,重写isCandidateComponent()方法,如果是接口,返回true,只拦截接口,不拦截类,如上。
扫描哪儿呢?还是注解方便,自定义一个注解:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Import(BaecBeanDefinitionRegistrar.class) public @interface BaecMapperScan { String value(); }

并且需要把BaecBeanDefinitionRegistrar类导入进来,这个类用来注册BeanDefinition:
public class BaecBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { // 参数AnnotationMetadata就是注解的元数据信息 @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { // 获取注解信息 Map annotationAttributes = importingClassMetadata.getAnnotationAttributes(BaecMapperScan.class.getName()); // 获取扫描路径 String path = (String) annotationAttributes.get("value"); // 引入扫描器 BaecBeanDefinitionScanner scanner = new BaecBeanDefinitionScanner(registry); // ClassPathBeanDefinitionScanner扫描器默认只扫描@Component注解的类 // 所以,重写IncludeFilter()方法,让它扫描所有的类 scanner.addIncludeFilter(new TypeFilter() { @Override public boolean match(@NotNull MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory){ return true; } }); // 扫描路径 scanner.scan(path); } }

我们定义的BaecBeanDefinitionScanner继承自ClassPathBeanDefinitionScanner扫描器,默认只扫描@Component注解的类,可以看一下ClassPathBeanDefinitionScanner的扫描过程,doScan()方法主要会调到这里:
所属类:springframework.context.annotation.ClassPathScanningCandidateComponentProvider
private Set scanCandidateComponents(String basePackage) { Set candidates = new LinkedHashSet<>(); try { // 获取basePackage下所以的文件资源 String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + resolveBasePackage(basePackage) + '/' + this.resourcePattern; // 根据路径获取资源,class文件的file对象 Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath); boolean traceEnabled = logger.isTraceEnabled(); boolean debugEnabled = logger.isDebugEnabled(); for (Resource resource : resources) { if (traceEnabled) { logger.trace("Scanning " + resource); } if (resource.isReadable()) { try { // 元数据读取器 MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource); // excludeFilters includeFilters过滤器判断 if (isCandidateComponent(metadataReader)) { // 构造BeanDefiniiton ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader); sbd.setSource(resource); // 又判断一次,符合条件才加入 if (isCandidateComponent(sbd)) { if (debugEnabled) { logger.debug("Identified candidate component class: " + resource); } candidates.add(sbd); } else { if (debugEnabled) { logger.debug("Ignored because not a concrete top-level class: " + resource); } } } else { if (traceEnabled) { logger.trace("Ignored because not matching any filter: " + resource); } } }----省略无关代码----

这个方法isCandidateComponent()就是用来判断有没有@Component注解:
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException { // 排除过滤器,返回false for (TypeFilter tf : this.excludeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return false; } } // 符合includeFilters的才会进行条件匹配,通过了才是Bean,也就是先看有没有@Component,再看是否符合@Conditianal for (TypeFilter tf : this.includeFilters) { if (tf.match(metadataReader, getMetadataReaderFactory())) { return isConditionMatch(metadataReader); } } return false; }

所以,UserMapper和OrderMapper要想被扫描到,就重写IncludeFilter()方法,让它扫描所有的类。
scanner.addIncludeFilter(new TypeFilter() { @Override public boolean match(@NotNull MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory){ return true; } });

但是自定义的BaecFactoryBean是自己实现代理对象,要整合Mybatis,就需要Mybatis生成得代理对象,顺着这个思路,换成MyBatis的。
@Component public class BaecFactoryBean implements FactoryBean {private Class mapperInterface; private SqlSession sqlSession; public BaecFactoryBean(Class mapperInterface) { this.mapperInterface = mapperInterface; }// 通过注入SqlSessionFactory拿到SqlSession @Autowired public void setSqlSession(SqlSessionFactory sqlSessionFactory) { sqlSessionFactory.getConfiguration().addMapper(mapperInterface); this.sqlSession = sqlSessionFactory.openSession(); }@Override public Object getObject() { return sqlSession.getMapper(mapperInterface); }// 把生成的代理对象的类型返回 @Override public Class getObjectType() { return mapperInterface; } }

那么,只要Spring能拿到SqlSessionFactory这个Bean,就能取到SqlSession对象,如何拿呢?这就需要程序员自己实现了:
@ComponentScan("com.baec") @BaecMapperScan("com.baec.mapper") public class AppConfig {@Bean public SqlSessionFactory sqlSessionFactory() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); return sqlSessionFactory; }}

当然,也需要提前把Mybatis的配置文件mybatis.xml准备好。
另外,如果不使用Spring的注解,有什么办法注入SqlSession呢?
public class BaecBeanDefinitionScanner extends ClassPathBeanDefinitionScanner { public BaecBeanDefinitionScanner(BeanDefinitionRegistry registry) { super(registry); } @Override protected Set doScan(String... basePackages) { Set beanDefinitionHolders = super.doScan(basePackages); for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) { GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition(); // 往构造方法的入参中传值的话,传的就是这个mapper解析之后的BeanDefinition的名称 beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName()); beanDefinition.setBeanClassName(BaecFactoryBean.class.getName()); // 自动找到BaecFactoryBean类里面的set方法,然后根据类型去找Bean beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } return beanDefinitionHolders; } @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface(); } }

把BeanDefinition强转成GenericBeanDefinition,位置AutowiredMode属性为按类型注入,Spring会自动找到BaecFactoryBean类里面的set方法,然后根据类型去找Bean。
然后,上面的BaecFactoryBean就可以修改为:
public class BaecFactoryBean implements FactoryBean {private Class mapperInterface; private SqlSession sqlSession; public BaecFactoryBean(Class mapperInterface) { this.mapperInterface = mapperInterface; }public void setSqlSession(SqlSessionFactory sqlSessionFactory) { sqlSessionFactory.getConfiguration().addMapper(mapperInterface); this.sqlSession = sqlSessionFactory.openSession(); }@Override public Object getObject() { return sqlSession.getMapper(mapperInterface); }@Override public Class getObjectType() { return mapperInterface; } }

没有使用Spring的注解,但是实现了注入。
接下来,我们看一下Mybatis的原理,从@MapperScan注解入手:
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Documented @Import({MapperScannerRegistrar.class}) public @interface MapperScan { String[] value() default {}; String[] basePackages() default {}; Class[] basePackageClasses() default {}; Class nameGenerator() default BeanNameGenerator.class; Class annotationClass() default Annotation.class; Class markerInterface() default Class.class; String sqlSessionTemplateRef() default ""; String sqlSessionFactoryRef() default ""; Class factoryBean() default MapperFactoryBean.class; }

它也是导入了MapperScannerRegistrar类,该类也是实现了ImportBeanDefinitionRegistrar接口:
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { private ResourceLoader resourceLoader; public MapperScannerRegistrar() { }public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName())); ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); if (this.resourceLoader != null) { scanner.setResourceLoader(this.resourceLoader); }Class annotationClass = annoAttrs.getClass("annotationClass"); if (!Annotation.class.equals(annotationClass)) { scanner.setAnnotationClass(annotationClass); }Class markerInterface = annoAttrs.getClass("markerInterface"); if (!Class.class.equals(markerInterface)) { scanner.setMarkerInterface(markerInterface); }Class generatorClass = annoAttrs.getClass("nameGenerator"); if (!BeanNameGenerator.class.equals(generatorClass)) { scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass)); }Class mapperFactoryBeanClass = annoAttrs.getClass("factoryBean"); if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass)); }scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef")); scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef")); List basePackages = new ArrayList(); String[] var10 = annoAttrs.getStringArray("value"); int var11 = var10.length; int var12; String pkg; for(var12 = 0; var12 < var11; ++var12) { pkg = var10[var12]; if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } }var10 = annoAttrs.getStringArray("basePackages"); var11 = var10.length; for(var12 = 0; var12 < var11; ++var12) { pkg = var10[var12]; if (StringUtils.hasText(pkg)) { basePackages.add(pkg); } }Class[] var14 = annoAttrs.getClassArray("basePackageClasses"); var11 = var14.length; for(var12 = 0; var12 < var11; ++var12) { Class clazz = var14[var12]; basePackages.add(ClassUtils.getPackageName(clazz)); }scanner.registerFilters(); scanner.doScan(StringUtils.toStringArray(basePackages)); }public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } }

在这里面,Mybatis同样创建了一个扫描器ClassPathMapperScanner,扫描@MapperScan注解,毋庸置疑,该类也是继承自Spring的扫描器——ClassPathBeanDefinitionScanner类,在它的doScan方法中的processBeanDefinitions()中,同样也设置了AutowireMode属性为:
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

接下来,再看一下ClassPathMapperScanner中创建的MapperFactoryBean,是把Mybatis生成代理对象转化为Bean最关键的一环,这个类也实现了FactoryBean接口:
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean { private Class mapperInterface; private boolean addToConfig = true; public MapperFactoryBean() { }public MapperFactoryBean(Class mapperInterface) { this.mapperInterface = mapperInterface; }protected void checkDaoConfig() { super.checkDaoConfig(); Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = this.getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { configuration.addMapper(this.mapperInterface); } catch (Exception var6) { this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6); throw new IllegalArgumentException(var6); } finally { ErrorContext.instance().reset(); } }}public T getObject() throws Exception { return this.getSqlSession().getMapper(this.mapperInterface); }public Class getObjectType() { return this.mapperInterface; }public boolean isSingleton() { return true; }public void setMapperInterface(Class mapperInterface) { this.mapperInterface = mapperInterface; }public Class getMapperInterface() { return this.mapperInterface; }public void setAddToConfig(boolean addToConfig) { this.addToConfig = addToConfig; }public boolean isAddToConfig() { return this.addToConfig; } }

getObject()方法中也是通过getSqlSession().getMapper(this.mapperInterface)拿到的,属性mapperInterface也是通过MapperFactoryBean类的构造方法注入的。
而getSqlSession()方法在它的父类SqlSessionDaoSupport中定义了,和上面的例子一样。
我们回到文章一开头的代码,这句代码就相当于开了一个事务。
// 从工厂中获取SqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession();

它与Mybatis的一级缓存和二级缓存有关,但是,如果一级缓存打开了,第二次去执行同样的sql时就不会去请求数据库了,因为SqlSession会缓存第一次执行sql的结果,会执行MapperProxy的invoke()方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); }if (this.isDefaultMethod(method)) { return this.invokeDefaultMethod(proxy, method, args); } } catch (Throwable var5) { throw ExceptionUtil.unwrapThrowable(var5); }MapperMethod mapperMethod = this.cachedMapperMethod(method); // 主要执行这里 return mapperMethod.execute(this.sqlSession, args); }

当执行某个方法时,会判断方法是@Select、@Insert等注解,以@Select为例,最终会调用
public Object execute(SqlSession sqlSession, Object[] args) { Object param; Object result; 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); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + this.command.getName()); }

执行到result = sqlSession.selectOne(this.command.getName(), param); 拿到了sql和方法的参数。
2. SqlSessionTemplate类的作用 在MapperFactoryBean的父类SqlSessionDaoSupport中的SqlSession其实是SqlSessionTemplate类:
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);

我们用到的UserMapper是通过SqlSessionTemplate.getMapper()返回的代理对象,那这个SqlSessionTemplate是什么东东呢?
先看它的构造函数:
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) { 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()); }

传入一个SqlSessionFactory ,最终通过JDK的动态代理生成一个SqlSession的代理对象,并赋值给sqlSessionProxy属性。
再看它的getMapper()方法:
public T getMapper(Class type) { return this.getConfiguration().getMapper(type, this); }

参数传的是类型和this,去生成代理对象。mapper.selectById()调用的就是SqlSessionTemplate.selectOne()方法:
public T selectOne(String statement, Object parameter) { return this.sqlSessionProxy.selectOne(statement, parameter); }

就是调用的sqlSessionProxy就是构造函数生成的代理对象的selectOne()方法。
此时还没有真正去执行sql,执行sql是DefaultSqlSession对象,执行sql最终会进入SqlSessionInterceptor类,它是构造方法中生成动态代理导进去的,调用到它的invoke():
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 { // 执行sql就是在这 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; }

SqlSessionUtils.getSqlSession()方法就是从Session工厂中得到DefaultSqlSession:
所属类:org.mybatis.spring.SqlSessionUtils
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { Assert.notNull(sessionFactory, "No SqlSessionFactory specified"); Assert.notNull(executorType, "No ExecutorType specified"); // 通过Spring的事务管理器,从ThreadLocal中是否有SqlSession SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); // 有则返回 if (session != null) { return session; } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Creating a new SqlSession"); }// 没有,则从DefaultSqlSessionFactory工厂类中创建一个新的DefaultSqlSession session = sessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; } }

总之,调用流程就是SqlSessionTemplate.selectOne()---->SqlSessionProxy.selectOne()--->DefaultSqlSession.selectOne()。这儿抛出两个问题:
SqlSessionTemplate的作用是什么?
因为DefaultSqlSession是线程不安全的,没有锁和同步方法,当不同线程同时调用DefaultSqlSession时,DefaultSqlSession确是同一个,存在并发安全问题。
SqlSessionTemplate就是用来解决线程安全这个问题,但该类里面的方法却没有加锁,所以只要保证每个线程有单独的DefaultSqlSession,一个线程执行sql时是同一个DefaultSqlSession就可以了,如何做的?
通过ThreadLocal,每个线程执行之前,先看ThreadLoca中是否有DefaultSqlSession,有直接拿来用,没有则创建。
上面SqlSessionUtils.getSqlSession()方法中的TransactionSynchronizationManager.getResource(),就是Spring的事务,底层就是ThreadLocal:
private static Object doGetResource(Object actualKey) { // resources是ThreadLocal包装的Map,用来缓存资源的,比如缓存当前线程中由某个DataSource所创建的数据库连接 Map map = resources.get(); if (map == null) { return null; }// 获取DataSource对象所对应的数据库连接对象 Object value = https://www.it610.com/article/map.get(actualKey); // Transparently remove ResourceHolder that was marked as void... if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) { map.remove(actualKey); // Remove entire ThreadLocal if empty... if (map.isEmpty()) { resources.remove(); } value = null; } return value; }

检查ThreadLocal中是否有SqlSession,返回的是SqlSession的包装类SqlSessionHolder,有则从SqlSessionHolder中取出,没有,则从DefultSqlSession工厂类中创建一个新的:
SqlSessionFactory.openSession(executorType); registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

然后执行registerSessionHolder()方法,把DefultSqlSession包装成SqlSessionHolder,存到ThreadLocal。
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) { if (TransactionSynchronizationManager.isSynchronizationActive()) { Environment environment = sessionFactory.getConfiguration().getEnvironment(); if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]"); }SqlSessionHolder holder = new SqlSessionHolder(session, executorType, exceptionTranslator); // 通过事务管理器,存到ThreadLocal TransactionSynchronizationManager.bindResource(sessionFactory, holder); TransactionSynchronizationManager.registerSynchronization(new SqlSessionUtils.SqlSessionSynchronization(holder, sessionFactory)); holder.setSynchronizedWithTransaction(true); holder.requested();

3. Mybatis一级缓存失效问题 为什么Spring和Mybatis整合之后一级缓存会失效?
在上面的源码中,注意有个条件TransactionSynchronizationManager.isSynchronizationActive(),是否开启了Spring事务,如果为true才去执行后面的逻辑。
不符合带来的结果就是,一个线程去执行不同的sql时,发现ThreadLocal中没有SqlSession,于是每次都去创建,导致一级缓存失效,因为一级缓存的运行机制就是同一个SqlSession执行同一个sql时,返回之前缓存的结果。
如何解决?就是方法上加@Transactional注解,就会使用Spring的事务管理器建立的数据库连接,Mybatis如何拿到事务管理器的连接呢?就是prepareStatement()方法:
所属类:org.apache.ibatis.executor.SimpleExecutor
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { // 获取数据库连接 Connection connection = this.getConnection(statementLog); Statement stmt = handler.prepare(connection, this.transaction.getTimeout()); handler.parameterize(stmt); return stmt; }

进而调用到org.apache.ibatis.executor.BaseExecutor.getConnection()方法:
protected Connection getConnection(Log statementLog) throws SQLException { Connection connection = this.transaction.getConnection(); return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection; }

这个Transaction是Mybatis的事务管理接口,包括事务的提交、回滚,及数据库连接的获取。
会先在DefaultSqlSessionFactory.openSessionFromDataSource()方法中通过TransactionFactory创建的,进而创建了Executor执行器。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { Environment environment = this.configuration.getEnvironment(); TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); Executor executor = this.configuration.newExecutor(tx, execType); return new DefaultSqlSession(this.configuration, executor, autoCommit); ----省略无关代码---- }

Spring整合Mybatis后得到的是SpringManagedTransaction类,它继承自Transaction接口:
public class SpringManagedTransaction implements Transaction { private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class); private final DataSource dataSource; private Connection connection; private boolean isConnectionTransactional; private boolean autoCommit; public SpringManagedTransaction(DataSource dataSource) { Assert.notNull(dataSource, "No DataSource specified"); this.dataSource = dataSource; }public Connection getConnection() throws SQLException { if (this.connection == null) { this.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); }public void commit() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { this.connection.commit(); } }public void rollback() throws SQLException { if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) { this.connection.rollback(); } }public void close() throws SQLException { DataSourceUtils.releaseConnection(this.connection, this.dataSource); }public Integer getTimeout() throws SQLException { ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource); return holder != null && holder.hasTimeout() ? holder.getTimeToLiveInSeconds() : null; } }

openConnection()中的DataSourceUtils.getConnection(this.dataSource)就可以拿到由Spring事务管理器创建的数据库连接。

如果方法没有开启事务,那么在执行sql时候,每个sql有自己的SqlSession对象来执行。
如果开启了Spring事务,就是多个sql属于同一个事务,那应该用一个SqlSession来执行多个sql。所以,在没有开启Spring事务的时候,SqlSession的一级缓存并不是失效了,而是存在的生命周期太短了(执行完一个sql后就被销毁了,下一个sql执行时又是一个新的SqlSession)。
Spring为什么要这样设计?解决线程安全问题,一般情况下不建议使用Mybatis的一级缓存,如果使用,就会涉及到事务的隔离级别,假如设置的隔离级别是读未提交,那么同一条sql查到的结果可能是一样的(从缓存中拿的),把隔离界别忽略了。那数据库的隔离级别重要还是Mybatis的一级缓存重要?肯定是数据库的隔离级别重要。
如果非要使用Mybatis的一级缓存,提供另外一种方法,用@Bean创建一个SqlSession的Bean:
@ComponentScan("com.baec") @BaecMapperScan("com.baec.mapper") public class AppConfig {@Autowired public SqlSessionFactory sqlSessionFactory() throws IOException { InputStream inputStream = Resources.getResourceAsStream("mybatis.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); return sqlSessionFactory; }@Bean public SqlSession sqlSession() throws IOException { return sqlSessionFactory().openSession(); }

这样,在Service中就可以直接使用了:
@Component public class UserService { @Autowired private UserMapper userMapper; @Autowired private SqlSession sqlSession; public void test() { sqlSession.selectOne("cn.kieasar.mybatis.mapper.UserMapper.selectById"); sqlSession.selectOne("cn.kieasar.mybatis.mapper.UserMapper.selectById"); sqlSession.selectOne("cn.kieasar.mybatis.mapper.UserMapper.selectById"); } }

这样一来,这三个Sql执行使用的SqlSession 就是同一个了,而且是同一个线程。
如果加了@MapperScan注解,会扫描、注入BeanDefinition,Mapper的代理对象Bean,即MapperFactoryBean,MapperFactoryBean继承了SqlSessionDaoSupport,SqlSessionDaoSupport又继承了DaoSupport,而DaoSupport类实现了InitializingBean接口,所以各种Mapper创建时会执行afterPropertiesSet()方法:
public abstract class DaoSupport implements InitializingBean { /** Logger available to subclasses. */ protected final Log logger = LogFactory.getLog(getClass()); @Override public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { // Let abstract subclasses check their configuration. checkDaoConfig(); // Let concrete implementations initialize themselves. try { initDao(); } catch (Exception ex) { throw new BeanInitializationException("Initialization of DAO failed", ex); } }

初始化的时候首先会调用checkDaoConfig(),检查SqlSession是否为空,会调到这里:
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSession sqlSession; private boolean externalSqlSession; public SqlSessionDaoSupport() { }public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (!this.externalSqlSession) { this.sqlSession = new SqlSessionTemplate(sqlSessionFactory); } }public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) { this.sqlSession = sqlSessionTemplate; this.externalSqlSession = true; }public SqlSession getSqlSession() { return this.sqlSession; }protected void checkDaoConfig() { Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required"); } }

接下来,会进入到子类MapperFactoryBean.checkDaoConfig():
protected void checkDaoConfig() { super.checkDaoConfig(); Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = this.getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { // 把泛型Class的mapperInterface添加到Mapper中 configuration.addMapper(this.mapperInterface); } catch (Exception var6) { this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6); throw new IllegalArgumentException(var6); } finally { ErrorContext.instance().reset(); } }}

把泛型Class的mapperInterface添加到Mapper中。
另外,如果不想写@MapperScan注解,还有一种方式可以扫描注入Mybatis生成的代理对象Bean,整合Mybatis:
@ComponentScan("com.baec") public class AppConfig {@Bean public MapperScannerConfigurer mapperScannerConfigurer(){ MapperScannerConfigurer configurer = new MapperScannerConfigurer(); // 指定扫描路径 configurer.setBasePackage("com.baec.mapper"); return configurer; } @Bean public JdbcTemplate jdbcTemplate() { return new JdbcTemplate(dataSource()); } @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/tuling?characterEncoding=utf-8& useSSL=false"); dataSource.setUsername("root"); dataSource.setPassword("Zhouyu123456***"); return dataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource()); return sessionFactoryBean.getObject(); }}

SqlSessionFactory类实现了InitializingBean和FactoryBean接口,它的getObject()方法调用了afterPropertiesSet()方法:
public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { this.afterPropertiesSet(); } return this.sqlSessionFactory; }

public void afterPropertiesSet() throws Exception { this.sqlSessionFactory = this.buildSqlSessionFactory(); }

buildSqlSessionFactory()方法中会构建出Mybatis的核心配置类Configuration,在这里面创建了Spring的事务管理器SpringManagedTransactionFactory工厂,通过该类可以拿到SpringManagedTransaction。
MapperScannerConfigurer实现了BenDefinitionRegistryPostProcessor接口,在postProcessBeanDefinitionRegistry()方法中创建了扫描器,实现了扫描BeanDefiniiton的功能:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { this.processPropertyPlaceHolders(); }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.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n")); }

最后,我们总结一下Spring整合Mybatis底层源码执行流程:
  1. 通过@MapperScan导入了MapperScannerRegistrar类;
  2. MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,Spring在启动时会调用MapperScannerRegistrar类中的registerBeanDefinitions()方法;
  3. registerBeanDefinitions()方法中定义了ClassPathMapperScanner对象,用来扫描Mapper;
  4. 设置ClassPathMapperScanner对象可以扫描到接口,因为在Spring中不会扫描接口(因为ClassPathMapperScanner中重写了isCandidateComponent方法,导致isCandidateComponent只会扫描@Component注解的类);
  5. 扫描接口并且得到对应的BeanDefinition;
  6. 把扫描得到的BeanDefinition进行修改,把BeanClass修改为MapperFactoryBean,把AutowireMode修改为byType;
  7. 扫描完成后,Spring就会基于BeanDefinition去创建Bean,相当于每个Mapper对应一个FactoryBean;
  8. 在MapperFactoryBean中的getObject()方法中,调用了getSqlSession()去得到一个sqlSession对象,然后根据对应的Mapper接口生成一个Mapper接口代理对象,这个代理对象就成为Spring容器中的Bean;
  9. SqlSession对象属于Mybatis,SqlSession对象需要SqlSessionFactory来产生;
  10. MapperFactoryBean的AutowireMode为byType,所以Spring会自动调用set方法,有两个set方法,一个setSqlSessionFactory(),一个setSqlSessionTemplate(),而这两个方法执行的前提是根据方法参数类型能找到对应的bean,所以Spring容器中要存在SqlSessionFactory类型的Bean或者SqlSessionTemplate类型的Bean;
  11. 如果你定义的是一个SqlSessionFactory类型的Bean,那么最终也会被包装为一个SqlSessionTemplate对象,并且赋值给sqlSession属性;
  12. 而在SqlSessionTemplate类中就存在一个getMapper()方法,这个方法会产生一个Mapper接口代理对象;
  13. 当执行该代理对象的某个方法时,就会进入到Mybatis框架的底层执行流程,执行流程看下图:
Spring源码|Spring源码之整合Mybatis底层实现
文章图片

【Spring源码|Spring源码之整合Mybatis底层实现】

    推荐阅读