当前位置:   article > 正文

Spring源码之整合Mybatis底层实现_sqlsession.flushstatements()

sqlsession.flushstatements()

目录

1. Spring整合Mybatis底层源码

2. SqlSessionTemplate类的作用

3. Mybatis一级缓存失效问题


1. Spring整合Mybatis底层源码

        Mybatis框架可以单独使用,需要用到Mybatis所提供的一些类构造出对应的Mapper对象,然后就能使用Mybatis框架提供的功能,我们先看一个Demo:

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

        这是一段很普通的Mybatis的代码,我们点进去看一下getMapper()方法,看一下Mybatis如何生成UserMapper代理对象,这个方法是SqlSession接口的方法,这个接口里面有很多我们平常使用的CRUD方法:

  1. public interface SqlSession extends Closeable {
  2. <T> T selectOne(String var1);
  3. <T> T selectOne(String var1, Object var2);
  4. <E> List<E> selectList(String var1);
  5. <E> List<E> selectList(String var1, Object var2);
  6. <E> List<E> selectList(String var1, Object var2, RowBounds var3);
  7. <K, V> Map<K, V> selectMap(String var1, String var2);
  8. <K, V> Map<K, V> selectMap(String var1, Object var2, String var3);
  9. <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);
  10. <T> Cursor<T> selectCursor(String var1);
  11. <T> Cursor<T> selectCursor(String var1, Object var2);
  12. <T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3);
  13. void select(String var1, Object var2, ResultHandler var3);
  14. void select(String var1, ResultHandler var2);
  15. void select(String var1, Object var2, RowBounds var3, ResultHandler var4);
  16. int insert(String var1);
  17. int insert(String var1, Object var2);
  18. int update(String var1);
  19. int update(String var1, Object var2);
  20. int delete(String var1);
  21. int delete(String var1, Object var2);
  22. void commit();
  23. void commit(boolean var1);
  24. void rollback();
  25. void rollback(boolean var1);
  26. List<BatchResult> flushStatements();
  27. void close();
  28. void clearCache();
  29. Configuration getConfiguration();
  30. <T> T getMapper(Class<T> var1);
  31. Connection getConnection();
  32. }

        主要看它默认的实现类DefaultSqlSession的方法:

        所属类:org.apache.ibatis.session.defaults.DefaultSqlSession

  1. public <T> T getMapper(Class<T> type) {
  2. return this.configuration.getMapper(type, this);
  3. }

         所属类:org.apache.ibatis.session.Configuration

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. return this.mapperRegistry.getMapper(type, sqlSession);
  3. }

        所属类:org.apache.ibatis.binding.MapperRegistry

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

        mapperProxyFactory.newInstance(sqlSession)就会返回代理对象:

        所属类:org.apache.ibatis.binding.MapperProxyFactory

  1. public class MapperProxyFactory<T> {
  2. private final Class<T> mapperInterface;
  3. private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
  4. public MapperProxyFactory(Class<T> mapperInterface) {
  5. this.mapperInterface = mapperInterface;
  6. }
  7. public Class<T> getMapperInterface() {
  8. return this.mapperInterface;
  9. }
  10. public Map<Method, MapperMethod> getMethodCache() {
  11. return this.methodCache;
  12. }
  13. protected T newInstance(MapperProxy<T> mapperProxy) {
  14. return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
  15. }
  16. public T newInstance(SqlSession sqlSession) {
  17. MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
  18. return this.newInstance(mapperProxy);
  19. }
  20. }

        把mapper传进去,通过JDK的动态代理生成代理对象。我们可以看一下MapperProxy类,它实现了InvocationHandler接口。

  1. public class MapperProxy<T> implements InvocationHandler, Serializable {
  2. private static final long serialVersionUID = -6424540398559729838L;
  3. private final SqlSession sqlSession;
  4. private final Class<T> mapperInterface;
  5. private final Map<Method, MapperMethod> methodCache;
  6. public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
  7. this.sqlSession = sqlSession;
  8. this.mapperInterface = mapperInterface;
  9. this.methodCache = methodCache;
  10. }
  11. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  12. try {
  13. if (Object.class.equals(method.getDeclaringClass())) {
  14. return method.invoke(this, args);
  15. }
  16. if (this.isDefaultMethod(method)) {
  17. return this.invokeDefaultMethod(proxy, method, args);
  18. }
  19. } catch (Throwable var5) {
  20. throw ExceptionUtil.unwrapThrowable(var5);
  21. }
  22. MapperMethod mapperMethod = this.cachedMapperMethod(method);
  23. return mapperMethod.execute(this.sqlSession, args);
  24. }
  25. private MapperMethod cachedMapperMethod(Method method) {
  26. MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
  27. if (mapperMethod == null) {
  28. mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
  29. this.methodCache.put(method, mapperMethod);
  30. }
  31. return mapperMethod;
  32. }

        当我们得到代理对象,去执行Mapper接口里面的方法时,Mybatis就会进入MapperProxy类的invoke()方法,得到方法上面的注解(如:@Select("select 'user'")),然后去执行目标方法。

        所有MapperProxy类很重要,上面这些生成代理逻辑,都是Mybatis提供的。

        回到Spring,如果Mybatis要和Spring整合,除了Mybatis自带的jar包之外,还需整合的jar包:

  1. <dependency>
  2. <groupId>org.mybatis</groupId>
  3. <artifactId>mybatis-spring</artifactId>
  4. <version>1.3.1</version>
  5. </dependency>

        为什么呢?举个例子:

  1. @Component
  2. public class UserService {
  3. @Autowired
  4. private UserMapper userMapper;
  5. public void test() {
  6. System.out.println(userMapper.selectById());
  7. }
  8. }

        上面的代码貌似没问题,但是如果没有整合jar包,就会报找不到userMapper这个Bean的错误。

        我们知道,Spring在Bean的实例化中进行属性注入时,找不到userMapper,因为在过程中需要把Mybatis生成的UserMapper代理对象注入到这里,赋值该属性,才能进行后续的操作,Spring和其他框架整合亦如此。会把Mybatis创建的类(或代理对象)注册到Spring容器,Spring在用的时候才能找到。

        这个章节要说的就是如何把Mybatis生成userMapper变成Spring所需的Bean,但是UserMapper是接口,所以不能用固有的思路通过Spring上下文去getBean(),接口是无法实例化的。

        答案就是利用FactoryBean接口,例如:

  1. /**
  2. * @author Kieasar
  3. */
  4. @Component
  5. public class BaecFactoryBean implements FactoryBean {
  6. @Override
  7. public Object getObject() {
  8. // 参数中把类加载器传进去,然后new一个UserMapper,生成代理对象
  9. Object newInstance = Proxy.newProxyInstance(BaecFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
  10. @Override
  11. public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
  12. System.out.println("执行代理对象的方法");
  13. return null;
  14. }
  15. });
  16. return newInstance;
  17. }
  18. // 把生成的代理对象的类型返回
  19. @Override
  20. public Class<?> getObjectType() {
  21. return UserMapper.class;
  22. }
  23. }

        此时,这个Bean对象所对应的Bean就是getObject()方法返回的代理对象(newInstance),接下来,单元测试一下:

  1. @Test
  2. public void main() {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
  4. context.register(AppConfig.class);
  5. context.refresh();
  6. UserService userService = (UserService) context.getBean("userService");
  7. userService.test();
  8. }

        执行结果没有报错,说明属性被赋值了,很明显,因为通过属性去找,能找到上面BaecFactoryBean生成的代理对象。

        但是test()方法打印结果却为null,因为此时执行的是代理对象的invoke()方法。

        上面的例子,貌似我们实现了Mybatis的功能,但是有个问题,此时只有一个UserMapper,如果再有OrderMapper或其他很多的类需要注入,难道我们要写很多个FactoryBean吗?所以,当当前的思路是不行滴~

        于是,又想到,代码不要写死,这样不就可以了嘛:

  1. /**
  2. * @author Kieasar
  3. */
  4. @Component
  5. public class BaecFactoryBean implements FactoryBean {
  6. private Class mapperInterface;
  7. public BaecFactoryBean(Class mapperInterface) {
  8. this.mapperInterface = mapperInterface;
  9. }
  10. @Autowired
  11. public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
  12. sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
  13. this.sqlSession = sqlSessionFactory.openSession();
  14. }
  15. @Override
  16. public Object getObject() {
  17. Object newInstance = Proxy.newProxyInstance(BaecFactoryBean.class.getClassLoader(), new Class[]{mapperInterface}, new InvocationHandler() {
  18. @Override
  19. public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
  20. System.out.println("执行代理对象的方法");
  21. return null;
  22. }
  23. });
  24. return newInstance;
  25. }
  26. // 把生成的代理对象的类型返回
  27. @Override
  28. public Class<?> getObjectType() {
  29. return mapperInterface;
  30. }
  31. }

        但是,@Component注解修饰的类,注入的时候是单例的,支持生成一个代理对象,不能生成我们需要的若干个Bean,但是,这样可以呀~

  1. @Test
  2. public void main() {
  3. AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
  4. context.register(AppConfig.class);
  5. context.refresh();
  6. AbstractBeanDefinition userMapperBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
  7. userMapperBeanDefinition.setBeanClass(BaecFactoryBean.class);
  8. userMapperBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
  9. // 得到一个userMapper代理对象
  10. context.registerBeanDefinition("userMapper",userMapperBeanDefinition);
  11. AbstractBeanDefinition orederMapperBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
  12. orederMapperBeanDefinition.setBeanClass(BaecFactoryBean.class);
  13. orederMapperBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
  14. // 得到一个orderMapper代理对象
  15. context.registerBeanDefinition("orderMapper",orederMapperBeanDefinition);
  16. UserService userServcie = (UserService) context.getBean("userServcie");
  17. userServcie.test();
  18. }

        确实可以,但是如果有很多的类需要注入呢……

        用扫描的方式,则需要一个扫描器,继承ClassPathBeanDefinitionScanner类,就有了扫描器的功能。

  1. public class BaecBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
  2. public BaecBeanDefinitionScanner(BeanDefinitionRegistry registry) {
  3. super(registry);
  4. }
  5. @Override
  6. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  7. Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
  8. for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
  9. BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
  10. // 往构造方法的入参中传值的话,传的就是这个mapper解析之后的BeanDefinition的名称
  11. beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
  12. beanDefinition.setBeanClassName(BaecFactoryBean.class.getName());
  13. }
  14. return beanDefinitionHolders;
  15. }
  16. @Override
  17. protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
  18. return beanDefinition.getMetadata().isInterface();
  19. }
  20. }

        但是Spring不关心接口,Mybatis只关心接口,所以,还需要加工一下,重写isCandidateComponent()方法,如果是接口,返回true,只拦截接口,不拦截类,如上。

        扫描哪儿呢?还是注解方便,自定义一个注解:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.TYPE)
  3. @Import(BaecBeanDefinitionRegistrar.class)
  4. public @interface BaecMapperScan {
  5. String value();
  6. }

        并且需要把BaecBeanDefinitionRegistrar类导入进来,这个类用来注册BeanDefinition:

  1. public class BaecBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
  2. // 参数AnnotationMetadata就是注解的元数据信息
  3. @Override
  4. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {
  5. // 获取注解信息
  6. Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(BaecMapperScan.class.getName());
  7. // 获取扫描路径
  8. String path = (String) annotationAttributes.get("value");
  9. // 引入扫描器
  10. BaecBeanDefinitionScanner scanner = new BaecBeanDefinitionScanner(registry);
  11. // ClassPathBeanDefinitionScanner扫描器默认只扫描@Component注解的类
  12. // 所以,重写IncludeFilter()方法,让它扫描所有的类
  13. scanner.addIncludeFilter(new TypeFilter() {
  14. @Override
  15. public boolean match(@NotNull MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory){
  16. return true;
  17. }
  18. });
  19. // 扫描路径
  20. scanner.scan(path);
  21. }
  22. }

        我们定义的BaecBeanDefinitionScanner继承自ClassPathBeanDefinitionScanner扫描器,默认只扫描@Component注解的类,可以看一下ClassPathBeanDefinitionScanner的扫描过程,doScan()方法主要会调到这里:

所属类:springframework.context.annotation.ClassPathScanningCandidateComponentProvider

  1. private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
  2. Set<BeanDefinition> candidates = new LinkedHashSet<>();
  3. try {
  4. // 获取basePackage下所以的文件资源
  5. String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
  6. resolveBasePackage(basePackage) + '/' + this.resourcePattern;
  7. // 根据路径获取资源,class文件的file对象
  8. Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
  9. boolean traceEnabled = logger.isTraceEnabled();
  10. boolean debugEnabled = logger.isDebugEnabled();
  11. for (Resource resource : resources) {
  12. if (traceEnabled) {
  13. logger.trace("Scanning " + resource);
  14. }
  15. if (resource.isReadable()) {
  16. try {
  17. // 元数据读取器
  18. MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
  19. // excludeFilters includeFilters过滤器判断
  20. if (isCandidateComponent(metadataReader)) {
  21. // 构造BeanDefiniiton
  22. ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
  23. sbd.setSource(resource);
  24. // 又判断一次,符合条件才加入
  25. if (isCandidateComponent(sbd)) {
  26. if (debugEnabled) {
  27. logger.debug("Identified candidate component class: " + resource);
  28. }
  29. candidates.add(sbd);
  30. }
  31. else {
  32. if (debugEnabled) {
  33. logger.debug("Ignored because not a concrete top-level class: " + resource);
  34. }
  35. }
  36. }
  37. else {
  38. if (traceEnabled) {
  39. logger.trace("Ignored because not matching any filter: " + resource);
  40. }
  41. }
  42. }
  43. ----省略无关代码----

         这个方法isCandidateComponent()就是用来判断有没有@Component注解:

  1. protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
  2. // 排除过滤器,返回false
  3. for (TypeFilter tf : this.excludeFilters) {
  4. if (tf.match(metadataReader, getMetadataReaderFactory())) {
  5. return false;
  6. }
  7. }
  8. // 符合includeFilters的才会进行条件匹配,通过了才是Bean,也就是先看有没有@Component,再看是否符合@Conditianal
  9. for (TypeFilter tf : this.includeFilters) {
  10. if (tf.match(metadataReader, getMetadataReaderFactory())) {
  11. return isConditionMatch(metadataReader);
  12. }
  13. }
  14. return false;
  15. }

        所以,UserMapper和OrderMapper要想被扫描到,就重写IncludeFilter()方法,让它扫描所有的类。

  1. scanner.addIncludeFilter(new TypeFilter() {
  2. @Override
  3. public boolean match(@NotNull MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory){
  4. return true;
  5. }
  6. });

        但是自定义的BaecFactoryBean是自己实现代理对象,要整合Mybatis,就需要Mybatis生成得代理对象,顺着这个思路,换成MyBatis的。

  1. @Component
  2. public class BaecFactoryBean implements FactoryBean {
  3. private Class mapperInterface;
  4. private SqlSession sqlSession;
  5. public BaecFactoryBean(Class mapperInterface) {
  6. this.mapperInterface = mapperInterface;
  7. }
  8. // 通过注入SqlSessionFactory拿到SqlSession
  9. @Autowired
  10. public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
  11. sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
  12. this.sqlSession = sqlSessionFactory.openSession();
  13. }
  14. @Override
  15. public Object getObject() {
  16. return sqlSession.getMapper(mapperInterface);
  17. }
  18. // 把生成的代理对象的类型返回
  19. @Override
  20. public Class<?> getObjectType() {
  21. return mapperInterface;
  22. }
  23. }

       那么,只要Spring能拿到SqlSessionFactory这个Bean,就能取到SqlSession对象,如何拿呢?这就需要程序员自己实现了:

  1. @ComponentScan("com.baec")
  2. @BaecMapperScan("com.baec.mapper")
  3. public class AppConfig {
  4. @Bean
  5. public SqlSessionFactory sqlSessionFactory() throws IOException {
  6. InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
  7. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  8. return sqlSessionFactory;
  9. }
  10. }

        当然,也需要提前把Mybatis的配置文件mybatis.xml准备好。

        另外,如果不使用Spring的注解,有什么办法注入SqlSession呢?

  1. public class BaecBeanDefinitionScanner extends ClassPathBeanDefinitionScanner {
  2. public BaecBeanDefinitionScanner(BeanDefinitionRegistry registry) {
  3. super(registry);
  4. }
  5. @Override
  6. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  7. Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
  8. for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
  9. GenericBeanDefinition beanDefinition = (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
  10. // 往构造方法的入参中传值的话,传的就是这个mapper解析之后的BeanDefinition的名称
  11. beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
  12. beanDefinition.setBeanClassName(BaecFactoryBean.class.getName());
  13. // 自动找到BaecFactoryBean类里面的set方法,然后根据类型去找Bean
  14. beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  15. }
  16. return beanDefinitionHolders;
  17. }
  18. @Override
  19. protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
  20. return beanDefinition.getMetadata().isInterface();
  21. }
  22. }

        把BeanDefinition强转成GenericBeanDefinition,位置AutowiredMode属性为按类型注入,Spring会自动找到BaecFactoryBean类里面的set方法,然后根据类型去找Bean。

        然后,上面的BaecFactoryBean就可以修改为:

  1. public class BaecFactoryBean implements FactoryBean {
  2. private Class mapperInterface;
  3. private SqlSession sqlSession;
  4. public BaecFactoryBean(Class mapperInterface) {
  5. this.mapperInterface = mapperInterface;
  6. }
  7. public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
  8. sqlSessionFactory.getConfiguration().addMapper(mapperInterface);
  9. this.sqlSession = sqlSessionFactory.openSession();
  10. }
  11. @Override
  12. public Object getObject() {
  13. return sqlSession.getMapper(mapperInterface);
  14. }
  15. @Override
  16. public Class<?> getObjectType() {
  17. return mapperInterface;
  18. }
  19. }

        没有使用Spring的注解,但是实现了注入。

        接下来,我们看一下Mybatis的原理,从@MapperScan注解入手:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target({ElementType.TYPE})
  3. @Documented
  4. @Import({MapperScannerRegistrar.class})
  5. public @interface MapperScan {
  6. String[] value() default {};
  7. String[] basePackages() default {};
  8. Class<?>[] basePackageClasses() default {};
  9. Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
  10. Class<? extends Annotation> annotationClass() default Annotation.class;
  11. Class<?> markerInterface() default Class.class;
  12. String sqlSessionTemplateRef() default "";
  13. String sqlSessionFactoryRef() default "";
  14. Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
  15. }

        它也是导入了MapperScannerRegistrar类,该类也是实现了ImportBeanDefinitionRegistrar接口:

  1. public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  2. private ResourceLoader resourceLoader;
  3. public MapperScannerRegistrar() {
  4. }
  5. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  6. AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  7. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  8. if (this.resourceLoader != null) {
  9. scanner.setResourceLoader(this.resourceLoader);
  10. }
  11. Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
  12. if (!Annotation.class.equals(annotationClass)) {
  13. scanner.setAnnotationClass(annotationClass);
  14. }
  15. Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  16. if (!Class.class.equals(markerInterface)) {
  17. scanner.setMarkerInterface(markerInterface);
  18. }
  19. Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
  20. if (!BeanNameGenerator.class.equals(generatorClass)) {
  21. scanner.setBeanNameGenerator((BeanNameGenerator)BeanUtils.instantiateClass(generatorClass));
  22. }
  23. Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  24. if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
  25. scanner.setMapperFactoryBean((MapperFactoryBean)BeanUtils.instantiateClass(mapperFactoryBeanClass));
  26. }
  27. scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
  28. scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
  29. List<String> basePackages = new ArrayList();
  30. String[] var10 = annoAttrs.getStringArray("value");
  31. int var11 = var10.length;
  32. int var12;
  33. String pkg;
  34. for(var12 = 0; var12 < var11; ++var12) {
  35. pkg = var10[var12];
  36. if (StringUtils.hasText(pkg)) {
  37. basePackages.add(pkg);
  38. }
  39. }
  40. var10 = annoAttrs.getStringArray("basePackages");
  41. var11 = var10.length;
  42. for(var12 = 0; var12 < var11; ++var12) {
  43. pkg = var10[var12];
  44. if (StringUtils.hasText(pkg)) {
  45. basePackages.add(pkg);
  46. }
  47. }
  48. Class[] var14 = annoAttrs.getClassArray("basePackageClasses");
  49. var11 = var14.length;
  50. for(var12 = 0; var12 < var11; ++var12) {
  51. Class<?> clazz = var14[var12];
  52. basePackages.add(ClassUtils.getPackageName(clazz));
  53. }
  54. scanner.registerFilters();
  55. scanner.doScan(StringUtils.toStringArray(basePackages));
  56. }
  57. public void setResourceLoader(ResourceLoader resourceLoader) {
  58. this.resourceLoader = resourceLoader;
  59. }
  60. }

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

beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

        接下来,再看一下ClassPathMapperScanner中创建的MapperFactoryBean,是把Mybatis生成代理对象转化为Bean最关键的一环,这个类也实现了FactoryBean接口:

  1. public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  2. private Class<T> mapperInterface;
  3. private boolean addToConfig = true;
  4. public MapperFactoryBean() {
  5. }
  6. public MapperFactoryBean(Class<T> mapperInterface) {
  7. this.mapperInterface = mapperInterface;
  8. }
  9. protected void checkDaoConfig() {
  10. super.checkDaoConfig();
  11. Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
  12. Configuration configuration = this.getSqlSession().getConfiguration();
  13. if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
  14. try {
  15. configuration.addMapper(this.mapperInterface);
  16. } catch (Exception var6) {
  17. this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
  18. throw new IllegalArgumentException(var6);
  19. } finally {
  20. ErrorContext.instance().reset();
  21. }
  22. }
  23. }
  24. public T getObject() throws Exception {
  25. return this.getSqlSession().getMapper(this.mapperInterface);
  26. }
  27. public Class<T> getObjectType() {
  28. return this.mapperInterface;
  29. }
  30. public boolean isSingleton() {
  31. return true;
  32. }
  33. public void setMapperInterface(Class<T> mapperInterface) {
  34. this.mapperInterface = mapperInterface;
  35. }
  36. public Class<T> getMapperInterface() {
  37. return this.mapperInterface;
  38. }
  39. public void setAddToConfig(boolean addToConfig) {
  40. this.addToConfig = addToConfig;
  41. }
  42. public boolean isAddToConfig() {
  43. return this.addToConfig;
  44. }
  45. }

        getObject()方法中也是通过getSqlSession().getMapper(this.mapperInterface)拿到的,属性mapperInterface也是通过MapperFactoryBean类的构造方法注入的。

        而getSqlSession()方法在它的父类SqlSessionDaoSupport中定义了,和上面的例子一样。

        我们回到文章一开头的代码,这句代码就相当于开了一个事务。

  1. // 从工厂中获取SqlSession对象
  2. SqlSession sqlSession = sqlSessionFactory.openSession();

        它与Mybatis的一级缓存和二级缓存有关,但是,如果一级缓存打开了,第二次去执行同样的sql时就不会去请求数据库了,因为SqlSession会缓存第一次执行sql的结果,会执行MapperProxy的invoke()方法:

  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. try {
  3. if (Object.class.equals(method.getDeclaringClass())) {
  4. return method.invoke(this, args);
  5. }
  6. if (this.isDefaultMethod(method)) {
  7. return this.invokeDefaultMethod(proxy, method, args);
  8. }
  9. } catch (Throwable var5) {
  10. throw ExceptionUtil.unwrapThrowable(var5);
  11. }
  12. MapperMethod mapperMethod = this.cachedMapperMethod(method);
  13. // 主要执行这里
  14. return mapperMethod.execute(this.sqlSession, args);
  15. }

        当执行某个方法时,会判断方法是@Select、@Insert等注解,以@Select为例,最终会调用

  1. public Object execute(SqlSession sqlSession, Object[] args) {
  2. Object param;
  3. Object result;
  4. switch(this.command.getType()) {
  5. case INSERT:
  6. param = this.method.convertArgsToSqlCommandParam(args);
  7. result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
  8. break;
  9. case UPDATE:
  10. param = this.method.convertArgsToSqlCommandParam(args);
  11. result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
  12. break;
  13. case DELETE:
  14. param = this.method.convertArgsToSqlCommandParam(args);
  15. result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
  16. break;
  17. case SELECT:
  18. if (this.method.returnsVoid() && this.method.hasResultHandler()) {
  19. this.executeWithResultHandler(sqlSession, args);
  20. result = null;
  21. } else if (this.method.returnsMany()) {
  22. result = this.executeForMany(sqlSession, args);
  23. } else if (this.method.returnsMap()) {
  24. result = this.executeForMap(sqlSession, args);
  25. } else if (this.method.returnsCursor()) {
  26. result = this.executeForCursor(sqlSession, args);
  27. } else {
  28. param = this.method.convertArgsToSqlCommandParam(args);
  29. // 最终会调用到这里
  30. result = sqlSession.selectOne(this.command.getName(), param);
  31. }
  32. break;
  33. case FLUSH:
  34. result = sqlSession.flushStatements();
  35. break;
  36. default:
  37. throw new BindingException("Unknown execution method for: " + this.command.getName());
  38. }

        执行到result = sqlSession.selectOne(this.command.getName(), param);拿到了sql和方法的参数。

2. SqlSessionTemplate类的作用

        在MapperFactoryBean的父类SqlSessionDaoSupport中的SqlSession其实是SqlSessionTemplate类:

this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);

        我们用到的UserMapper是通过SqlSessionTemplate.getMapper()返回的代理对象,那这个SqlSessionTemplate是什么东东呢?

        先看它的构造函数:

  1. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  2. this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
  3. }
  4. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
  5. this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
  6. }
  7. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
  8. Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  9. Assert.notNull(executorType, "Property 'executorType' is required");
  10. this.sqlSessionFactory = sqlSessionFactory;
  11. this.executorType = executorType;
  12. this.exceptionTranslator = exceptionTranslator;
  13. this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
  14. }

        传入一个SqlSessionFactory ,最终通过JDK的动态代理生成一个SqlSession的代理对象,并赋值给sqlSessionProxy属性。

        再看它的getMapper()方法:

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

        参数传的是类型和this,去生成代理对象。mapper.selectById()调用的就是SqlSessionTemplate.selectOne()方法:

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

        就是调用的sqlSessionProxy就是构造函数生成的代理对象的selectOne()方法。

        此时还没有真正去执行sql,执行sql是DefaultSqlSession对象,执行sql最终会进入SqlSessionInterceptor类,它是构造方法中生成动态代理导进去的,调用到它的invoke():

  1. private class SqlSessionInterceptor implements InvocationHandler {
  2. private SqlSessionInterceptor() {
  3. }
  4. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  5. SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
  6. Object unwrapped;
  7. try {
  8. // 执行sql就是在这
  9. Object result = method.invoke(sqlSession, args);
  10. if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
  11. sqlSession.commit(true);
  12. }
  13. unwrapped = result;
  14. } catch (Throwable var11) {
  15. unwrapped = ExceptionUtil.unwrapThrowable(var11);
  16. if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
  17. SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  18. sqlSession = null;
  19. Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
  20. if (translated != null) {
  21. unwrapped = translated;
  22. }
  23. }
  24. throw (Throwable)unwrapped;
  25. } finally {
  26. if (sqlSession != null) {
  27. SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
  28. }
  29. }
  30. return unwrapped;
  31. }

        SqlSessionUtils.getSqlSession()方法就是从Session工厂中得到DefaultSqlSession:

        所属类:org.mybatis.spring.SqlSessionUtils

  1. public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
  2. Assert.notNull(sessionFactory, "No SqlSessionFactory specified");
  3. Assert.notNull(executorType, "No ExecutorType specified");
  4. // 通过Spring的事务管理器,从ThreadLocal中是否有SqlSession
  5. SqlSessionHolder holder = (SqlSessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
  6. SqlSession session = sessionHolder(executorType, holder);
  7. // 有则返回
  8. if (session != null) {
  9. return session;
  10. } else {
  11. if (LOGGER.isDebugEnabled()) {
  12. LOGGER.debug("Creating a new SqlSession");
  13. }
  14. // 没有,则从DefaultSqlSessionFactory工厂类中创建一个新的DefaultSqlSession
  15. session = sessionFactory.openSession(executorType);
  16. registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
  17. return session;
  18. }
  19. }

        总之,调用流程就是SqlSessionTemplate.selectOne()---->SqlSessionProxy.selectOne()--->DefaultSqlSession.selectOne()。这儿抛出两个问题:

        SqlSessionTemplate的作用是什么?

       因为DefaultSqlSession是线程不安全的,没有锁和同步方法,当不同线程同时调用DefaultSqlSession时,DefaultSqlSession确是同一个,存在并发安全问题。

        SqlSessionTemplate就是用来解决线程安全这个问题,但该类里面的方法却没有加锁,所以只要保证每个线程有单独的DefaultSqlSession,一个线程执行sql时是同一个DefaultSqlSession就可以了,如何做的?

        通过ThreadLocal,每个线程执行之前,先看ThreadLoca中是否有DefaultSqlSession,有直接拿来用,没有则创建。

        上面SqlSessionUtils.getSqlSession()方法中的TransactionSynchronizationManager.getResource(),就是Spring的事务,底层就是ThreadLocal:

  1. private static Object doGetResource(Object actualKey) {
  2. // resources是ThreadLocal包装的Map,用来缓存资源的,比如缓存当前线程中由某个DataSource所创建的数据库连接
  3. Map<Object, Object> map = resources.get();
  4. if (map == null) {
  5. return null;
  6. }
  7. // 获取DataSource对象所对应的数据库连接对象
  8. Object value = map.get(actualKey);
  9. // Transparently remove ResourceHolder that was marked as void...
  10. if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
  11. map.remove(actualKey);
  12. // Remove entire ThreadLocal if empty...
  13. if (map.isEmpty()) {
  14. resources.remove();
  15. }
  16. value = null;
  17. }
  18. return value;
  19. }

        检查ThreadLocal中是否有SqlSession,返回的是SqlSession的包装类SqlSessionHolder,有则从SqlSessionHolder中取出,没有,则从DefultSqlSession工厂类中创建一个新的:

  1. SqlSessionFactory.openSession(executorType);
  2. registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

        然后执行registerSessionHolder()方法,把DefultSqlSession包装成SqlSessionHolder,存到ThreadLocal。

  1. private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
  2. if (TransactionSynchronizationManager.isSynchronizationActive()) {
  3. Environment environment = sessionFactory.getConfiguration().getEnvironment();
  4. if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
  5. if (LOGGER.isDebugEnabled()) {
  6. LOGGER.debug("Registering transaction synchronization for SqlSession [" + session + "]");
  7. }
  8. SqlSessionHolder holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
  9. // 通过事务管理器,存到ThreadLocal
  10. TransactionSynchronizationManager.bindResource(sessionFactory, holder);
  11. TransactionSynchronizationManager.registerSynchronization(new SqlSessionUtils.SqlSessionSynchronization(holder, sessionFactory));
  12. holder.setSynchronizedWithTransaction(true);
  13. holder.requested();

3. Mybatis一级缓存失效问题

        为什么Spring和Mybatis整合之后一级缓存会失效?

        在上面的源码中,注意有个条件TransactionSynchronizationManager.isSynchronizationActive(),是否开启了Spring事务,如果为true才去执行后面的逻辑。

        不符合带来的结果就是,一个线程去执行不同的sql时,发现ThreadLocal中没有SqlSession,于是每次都去创建,导致一级缓存失效,因为一级缓存的运行机制就是同一个SqlSession执行同一个sql时,返回之前缓存的结果。

        如何解决?就是方法上加@Transactional注解,就会使用Spring的事务管理器建立的数据库连接,Mybatis如何拿到事务管理器的连接呢?就是prepareStatement()方法:

        所属类:org.apache.ibatis.executor.SimpleExecutor

  1. private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  2. // 获取数据库连接
  3. Connection connection = this.getConnection(statementLog);
  4. Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
  5. handler.parameterize(stmt);
  6. return stmt;
  7. }

         进而调用到org.apache.ibatis.executor.BaseExecutor.getConnection()方法:

  1. protected Connection getConnection(Log statementLog) throws SQLException {
  2. Connection connection = this.transaction.getConnection();
  3. return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
  4. }

        这个Transaction是Mybatis的事务管理接口,包括事务的提交、回滚,及数据库连接的获取。

        会先在DefaultSqlSessionFactory.openSessionFromDataSource()方法中通过TransactionFactory创建的,进而创建了Executor执行器。

  1. private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  2. Transaction tx = null;
  3. try {
  4. Environment environment = this.configuration.getEnvironment();
  5. TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
  6. tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
  7. Executor executor = this.configuration.newExecutor(tx, execType);
  8. return new DefaultSqlSession(this.configuration, executor, autoCommit);
  9. ----省略无关代码----
  10. }

        Spring整合Mybatis后得到的是SpringManagedTransaction类,它继承自Transaction接口:

  1. public class SpringManagedTransaction implements Transaction {
  2. private static final Log LOGGER = LogFactory.getLog(SpringManagedTransaction.class);
  3. private final DataSource dataSource;
  4. private Connection connection;
  5. private boolean isConnectionTransactional;
  6. private boolean autoCommit;
  7. public SpringManagedTransaction(DataSource dataSource) {
  8. Assert.notNull(dataSource, "No DataSource specified");
  9. this.dataSource = dataSource;
  10. }
  11. public Connection getConnection() throws SQLException {
  12. if (this.connection == null) {
  13. this.openConnection();
  14. }
  15. return this.connection;
  16. }
  17. private void openConnection() throws SQLException {
  18. this.connection = DataSourceUtils.getConnection(this.dataSource);
  19. this.autoCommit = this.connection.getAutoCommit();
  20. this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
  21. }
  22. public void commit() throws SQLException {
  23. if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
  24. this.connection.commit();
  25. }
  26. }
  27. public void rollback() throws SQLException {
  28. if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
  29. this.connection.rollback();
  30. }
  31. }
  32. public void close() throws SQLException {
  33. DataSourceUtils.releaseConnection(this.connection, this.dataSource);
  34. }
  35. public Integer getTimeout() throws SQLException {
  36. ConnectionHolder holder = (ConnectionHolder)TransactionSynchronizationManager.getResource(this.dataSource);
  37. return holder != null && holder.hasTimeout() ? holder.getTimeToLiveInSeconds() : null;
  38. }
  39. }

        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:

  1. @ComponentScan("com.baec")
  2. @BaecMapperScan("com.baec.mapper")
  3. public class AppConfig {
  4. @Autowired
  5. public SqlSessionFactory sqlSessionFactory() throws IOException {
  6. InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
  7. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  8. return sqlSessionFactory;
  9. }
  10. @Bean
  11. public SqlSession sqlSession() throws IOException {
  12. return sqlSessionFactory().openSession();
  13. }

        这样,在Service中就可以直接使用了:

  1. @Component
  2. public class UserService {
  3. @Autowired
  4. private UserMapper userMapper;
  5. @Autowired
  6. private SqlSession sqlSession;
  7. public void test() {
  8. sqlSession.selectOne("cn.kieasar.mybatis.mapper.UserMapper.selectById");
  9. sqlSession.selectOne("cn.kieasar.mybatis.mapper.UserMapper.selectById");
  10. sqlSession.selectOne("cn.kieasar.mybatis.mapper.UserMapper.selectById");
  11. }
  12. }

        这样一来,这三个Sql执行使用的SqlSession 就是同一个了,而且是同一个线程。

        如果加了@MapperScan注解,会扫描、注入BeanDefinition,Mapper的代理对象Bean,即MapperFactoryBean<T>,MapperFactoryBean继承了SqlSessionDaoSupport,SqlSessionDaoSupport又继承了DaoSupport,而DaoSupport类实现了InitializingBean接口,所以各种Mapper创建时会执行afterPropertiesSet()方法:

  1. public abstract class DaoSupport implements InitializingBean {
  2. /** Logger available to subclasses. */
  3. protected final Log logger = LogFactory.getLog(getClass());
  4. @Override
  5. public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
  6. // Let abstract subclasses check their configuration.
  7. checkDaoConfig();
  8. // Let concrete implementations initialize themselves.
  9. try {
  10. initDao();
  11. }
  12. catch (Exception ex) {
  13. throw new BeanInitializationException("Initialization of DAO failed", ex);
  14. }
  15. }

        初始化的时候首先会调用checkDaoConfig(),检查SqlSession是否为空,会调到这里:

  1. public abstract class SqlSessionDaoSupport extends DaoSupport {
  2. private SqlSession sqlSession;
  3. private boolean externalSqlSession;
  4. public SqlSessionDaoSupport() {
  5. }
  6. public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
  7. if (!this.externalSqlSession) {
  8. this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
  9. }
  10. }
  11. public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
  12. this.sqlSession = sqlSessionTemplate;
  13. this.externalSqlSession = true;
  14. }
  15. public SqlSession getSqlSession() {
  16. return this.sqlSession;
  17. }
  18. protected void checkDaoConfig() {
  19. Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
  20. }
  21. }

        接下来,会进入到子类MapperFactoryBean.checkDaoConfig():

  1. protected void checkDaoConfig() {
  2. super.checkDaoConfig();
  3. Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
  4. Configuration configuration = this.getSqlSession().getConfiguration();
  5. if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
  6. try {
  7. // 把泛型Class<T>的mapperInterface添加到Mapper中
  8. configuration.addMapper(this.mapperInterface);
  9. } catch (Exception var6) {
  10. this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
  11. throw new IllegalArgumentException(var6);
  12. } finally {
  13. ErrorContext.instance().reset();
  14. }
  15. }
  16. }

        把泛型Class<T>的mapperInterface添加到Mapper中。

        另外,如果不想写@MapperScan注解,还有一种方式可以扫描注入Mybatis生成的代理对象Bean,整合Mybatis:

  1. @ComponentScan("com.baec")
  2. public class AppConfig {
  3. @Bean
  4. public MapperScannerConfigurer mapperScannerConfigurer(){
  5. MapperScannerConfigurer configurer = new MapperScannerConfigurer();
  6. // 指定扫描路径
  7. configurer.setBasePackage("com.baec.mapper");
  8. return configurer;
  9. }
  10. @Bean
  11. public JdbcTemplate jdbcTemplate() {
  12. return new JdbcTemplate(dataSource());
  13. }
  14. @Bean
  15. public DataSource dataSource() {
  16. DriverManagerDataSource dataSource = new DriverManagerDataSource();
  17. dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/tuling?characterEncoding=utf-8&amp;useSSL=false");
  18. dataSource.setUsername("root");
  19. dataSource.setPassword("Zhouyu123456***");
  20. return dataSource;
  21. }
  22. @Bean
  23. public SqlSessionFactory sqlSessionFactory() throws Exception {
  24. SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
  25. sessionFactoryBean.setDataSource(dataSource());
  26. return sessionFactoryBean.getObject();
  27. }
  28. }

        SqlSessionFactory类实现了InitializingBean和FactoryBean接口,它的getObject()方法调用了afterPropertiesSet()方法:

  1. public SqlSessionFactory getObject() throws Exception {
  2. if (this.sqlSessionFactory == null) {
  3. this.afterPropertiesSet();
  4. }
  5. return this.sqlSessionFactory;
  6. }
  1. public void afterPropertiesSet() throws Exception {
  2. this.sqlSessionFactory = this.buildSqlSessionFactory();
  3. }

        buildSqlSessionFactory()方法中会构建出Mybatis的核心配置类Configuration,在这里面创建了Spring的事务管理器SpringManagedTransactionFactory工厂,通过该类可以拿到SpringManagedTransaction。

        MapperScannerConfigurer实现了BenDefinitionRegistryPostProcessor接口,在postProcessBeanDefinitionRegistry()方法中创建了扫描器,实现了扫描BeanDefiniiton的功能:

  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  2. if (this.processPropertyPlaceHolders) {
  3. this.processPropertyPlaceHolders();
  4. }
  5. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  6. scanner.setAddToConfig(this.addToConfig);
  7. scanner.setAnnotationClass(this.annotationClass);
  8. scanner.setMarkerInterface(this.markerInterface);
  9. scanner.setSqlSessionFactory(this.sqlSessionFactory);
  10. scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  11. scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  12. scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  13. scanner.setResourceLoader(this.applicationContext);
  14. scanner.setBeanNameGenerator(this.nameGenerator);
  15. scanner.registerFilters();
  16. scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
  17. }

        最后,我们总结一下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框架的底层执行流程,执行流程看下图:

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/158283
推荐阅读
相关标签
  

闽ICP备14008679号