我们知道引入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;
}
主流程就是这样,后续会继续补充其中的细节。