Mybatis和Spring整合的源码解析

我们知道引入MyBatis包,就能单独使用Mybatis了,但如果想整合到Spring中使用还需要再引入mybatis-spring包,这个包是MyBatis自己开发的,就是为了把Mybatis整合到Spring中。那Mybatis是怎么对Spring做扩展的呢?


MyBatis的使用:

老程序猿可能用过iBatis,MyBatis的前生就是iBatis,二者用法稍有不同,但区别不大。目前MyBatis对两种用法都支持。看过源码会发现,MyBatis底层实现还是iBatis,只是对iBatis进行了包装,让其使用起来更面向对象面向接口。
在这里插入图片描述

MyBatis的getMapper()原理剖析

跟踪源码看看sqlSession.getMapper()的实现流程:

public interface SqlSession extends Closeable {

  // ...

  <T> T getMapper(Class<T> type);
  
  // ...
}

接口的实现:

public class DefaultSqlSession implements SqlSession {

  // ...

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

  // ...
}

在配置类里通过MapperRegistry获取,继续进入:

public class Configuration {

  // ...

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

  // ...
}

可以看到在MapperProxyFactory中获取的,继续进入:

public class MapperRegistry {

  // ...

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

  // ...
}

到MapperProxyFactory就明朗了:

public class MapperProxyFactory<T> {

  // ...

  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

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

  // ...
}

在MapperProxyFactory我们找到了答案,getMapper()为什么能返回接口,并且调用接口的方法就能实现增删改查的操作,原来它使用了JDK的动态代理(Java代理模式之静态代理和动态代理)产生了代理对象。


MyBatis和Spring整合的步骤

可以查看MyBatis官方文档,这里只列举其中几个步骤:

引入mybatis-spring的jar包:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.5</version>
</dependency>

把Mapper接口Bean对象添加到Spring容器:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

或者使用Java Config的方式,添加@Bean把Mapper接口Bean对象添加到Spring容器:

@Bean
public UserMapper userMapper() throws Exception {
  SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
  return sqlSessionTemplate.getMapper(UserMapper.class);
}

这样就可以在Service类中注入Mapper对象进行增删改查了:

public class FooServiceImpl implements FooService {

  @Autowried
  private final UserMapper userMapper;

  public User doSomeBusinessStuff(String userId) {
    return userMapper.getUser(userId);
  }
}

以上是官网给的简单使用示例,但在实际项目中并非采用这种手动配置方式,因为实际项目中类似UserMapper的接口太多了,手动配置麻烦且冗余。


MyBatis怎么把Bean添加到Spring

我们来看看Mybatis和Spring整合时,是怎么实现创建多个Mapper接口的Bean对象并添加到Spring容器的?

MapperFactoryBean实现FactoryBean接口

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;
  
  // ...
  
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }
  // ...
}

这里完成通用Mapper接口对象的获取生成,但怎么把Bean对象添加到Spring容器呢?通过@Component注解可以,但这里为了通用把Mapper接口定义成了Class的泛型,通过注解初始化会报错。不能通过注解自动扫描注册,那就只能手动注册了。


MapperScannerRegistrar实现ImportBeanDefinitionRegistrar接口

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
  }
  
  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
  // ...
  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
  }
  // ...
}

在这里获取MapperScan扫描路径下的Mapper,把扫描到的Bean注册到Spring的容器。


@MapperScan扫描触发

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

  String[] basePackages() default {};

  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}

主流程就是这样,后续会继续补充其中的细节。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值