前言
注意:阅读本文需要有一定的Spring和SpringBoot基础
先上一个Mybatis-Spring官网链接,打开一个SSM整合的案例项目一起食用本文效果更佳哦。
官网上说的很清楚,要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个SqlSessionFactory 和至少一个数据映射器类。
在 MyBatis-Spring 中,使用 SqlSessionFactoryBean来创建 SqlSessionFactory。
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
至于在SpringBoot中SqlSessionFactoryBean是怎么被创建并且被执行的,就不赘述了,感兴趣的可以去看 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory 方法,简单来说是利用的SpringBoot的自动装配机制
需要更多Java知识点和大厂面试题的朋友可以点一点下方链接免费领取
链接:1103806531暗号:CSDN
SqlSessionFactoryBean是怎么创建SqlSessionFactory的?
SqlSessionFactory是MyBatis中的一个重要的对象,它是用来创建SqlSession对象的,而SqlSession用来操作数据库的。我们通过SqlSessionFactoryBean的继承体系可以看出,它实现了FactoryBean和InitialzingBean,这两个类的作用我也不赘述了
我们直接来看重要的代码
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
// afterPropertiesSet方法在Bean的生命周期中会被调用,这里为什么会手动调用一次我也不知道
// 或许为了防止getObject被提前调用,也可能是兼容SpringBoot和Spring整合的区别
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
// 一直往下追就是下面的build方法
this.sqlSessionFactory = buildSqlSessionFactory();
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
走到buildSqlSessionFactory方法,最后还是通过 SqlSessionFactoryBuilder#build 去构建的DefaultSqlSessionFactory
@MapperScan注解到底做了什么?
@MapperScan负责Spring和Mybatis整合时mapper的扫描和注册
@Import(MapperScannerRegistrar.class)
@Import注解也是spring Framework提供的将普通javaBean注册到容器中,该注解导入了MapperScannerRegistrar 类来实现功能,我们看这个类的继承结构
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware
它实现了ImportBeanDefinitionRegistrar,Spring中手动注册bean的两种方式:
- 实现ImportBeanDefinitionRegistrar
- 实现BeanDefinitionRegistryPostProcessor
需要注意的是,如果某一个Bean实现了BeanDefinitionRegistryPostProcessor或者ImportBeanDefinitionRegistrar接口,那我们在这个类中使用@Autowired或者@Value注解,我们会发现失效了。原因是,spring容器执行接口的方法时,此时还没有去解析@Autowired或者@Value注解。如果我们要使用获取配置文件属性,可以通过原始方式,直接用IO读取配置文件,然后得到Properties对象,然后再获取配置值。
MapperScannerRegistrar是何方神圣?
这里会调用registerBeanDefinitions方法
ImportBeanDefinitionRegistrar在ConfigurationClassPostProcessor处理Configuration类期间被调用,用来生成该Configuration类所需要的BeanDefinition。而ConfigurationClassPostProcessor正实现了BeanDefinitionRegistryPostProcessor接口(所以支持mapper注册成bean,并注入到spring容器中)。
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry);
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
...
scanner.doScan(StringUtils.toStringArray(basePackages));
}
我们接着看doScan实现的细节:
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 扫描出所有mybatis的mapper,此时的bd里的class还是自己
// 假设UserMapper,那么此时bd中的beanclass = UserMapper.class
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
...
} else {
// 对bd进行处理
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
...
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
// bean的实际类是MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
...
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
这里特别要注意最后一行setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);Spring默认情况下的自动装配级别为AUTOWIRE_NO,需要修改为AUTOWIRE_BY_TYPE按类型注入。这样Spring就会通过set方法,并且再根据bean的类型帮我们注入属性,比如后会说到的MapperFactoryBean中的sqlSessionFactory和sqlSessionTemplate。
Spring中自动装配有四种
// 默认不注入,也是默认级别 = 0
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
// 按name = 1
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
// 按type = 2
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
// 按构造器 = 3
public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
// 还有一种已经不建议使用了,这里就不赘述自动装配的细节了
为什么mybatis不用@AutoWired呢? 主要是解耦吧,可以不依赖Spring进行编译,加了Spring的注解,那么就是强耦合了。
到了这一步,那么此时的mapper,我们可以认为都变成了bd加入到spring中了,那么下一步就是实例化了,那么我们就要来看看MapperFactoryBean了
MapperFactoryBean
因为MapperFactoryBean extends SqlSessionDaoSupport extends DaoSupport,DaoSupport implements InitializingBean ,所以在mfb实例化之后会执行afterPropertiesSet
@Override
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try { b
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
afterPropertiesSet其中checkDaoConfig()方法很重要,我们来看看,在每一个mfb实例化的时候,其实方法和sql是在实例化的时候就已经绑定并且放在Map<String, MappedStatement> mappedStatements当中,而不是在你调用的时候才去绑定。
protected void checkDaoConfig() {
super.checkDaoConfig();
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 在这一步,最后会调用一个parse解析,将所有的方法和sql语句绑定
// 放进一个map当中,所以实际上当你执行一个mapper的方法时,底层其实是去一个map当中get
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
this.mapperInterface这个实际上就是指的当前类,比如UserMapper在实例化的时候,这个mfb里面的mapperInterface就是它自己,因为mfb实现了FactoryBean,所以当你使用注解注入mapper的时候,此时的mapper是由getObject()方法产生的
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
getSqlSession()在和Spring整合中,拿到的一直是SqlSessionTemplate
org.mybatis.spring.support.SqlSessionDaoSupport#getSqlSession
// 这个SqlSession是由Spring管理,它会自动注入
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
最后通过SqlSession的getMapper去获得代理类
// 底层调用的是MapperRegistry的getMapper方法
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);
}
}
说到这里,我们大概知道了mybatis的dao接口是怎么变成代理类并且放入Spring容器中的过程了。
代理类执行逻辑
放入到Spring容器后,我们就可以注入代理对象进行数据库操作了。由于接口方法会被代理逻辑拦截,所以下面我们把目光聚焦在代理逻辑上面,看看代理逻辑会做哪些事情。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存中获取 MapperMethod 对象,若缓存未命中,则创建 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 这里的sqlSession在不同情况下不同,比如有defaultSqlSession 和 sqlSessionManger
// 最后还是调用的sqlSession的方法,但是sqlSessionManger解决自动关闭问题和线程安全问题
// 调用 execute 方法执行 SQL
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 创建 SqlCommand 对象,该对象包含一些和 SQL 相关的信息
this.command = new SqlCommand(config, mapperInterface, method);
// 创建 MethodSignature 对象,由类名可知,该对象包含了被拦截方法的一些信息
this.method = new MethodSignature(config, mapperInterface, method);
}
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
// 解析 MappedStatement
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
// 检测当前方法是否有对应的 MappedStatement
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH;
} else {
// 熟悉的异常
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
前面已经分析了 MapperMethod 的初始化过程,现在 MapperMethod 创建好了。那么,接下来要做的事情是调用 MapperMethod 的 execute 方法,执行 SQL,后面的就不分析了,看到这,你应该知道Mybatis是怎么实现的和Spring的整合流程了。
这篇文章到这就结束啦,喜欢的话就给个赞 + 收藏 + 关注吧!