注意:阅读本文须要有必定的Spring和SpringBoot基础java
先上一个Mybatis-Spring官网连接,打开一个SSM整合的案例项目一块儿食用本文效果更佳哦。web
官网上说的很清楚,要和 Spring 一块儿使用 MyBatis,须要在 Spring 应用上下文中定义至少两样东西:一个SqlSessionFactory 和至少一个数据映射器类。面试
在 MyBatis-Spring 中,使用 SqlSessionFactoryBean来建立 SqlSessionFactory。spring
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> </bean>
至于在SpringBoot中SqlSessionFactoryBean是怎么被建立而且被执行的,就不赘述了,感兴趣的能够去看 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory 方法,简单来讲是利用的SpringBoot的自动装配机制sql
须要更多Java知识点和大厂面试题的朋友能够点一点下方连接免费领取数据库
连接:1103806531暗号:CSDN缓存
SqlSessionFactory是MyBatis中的一个重要的对象,它是用来建立SqlSession对象的,而SqlSession用来操做数据库的。咱们经过SqlSessionFactoryBean的继承体系能够看出,它实现了FactoryBean和InitialzingBean,这两个类的做用我也不赘述了安全
咱们直接来看重要的代码mybatis
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负责Spring和Mybatis整合时mapper的扫描和注册
@Import(MapperScannerRegistrar.class)
@Import注解也是spring Framework提供的将普通javaBean注册到容器中,该注解导入了MapperScannerRegistrar 类来实现功能,咱们看这个类的继承结构
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware
它实现了ImportBeanDefinitionRegistrar,Spring中手动注册bean的两种方式:
须要注意的是,若是某一个Bean实现了BeanDefinitionRegistryPostProcessor或者ImportBeanDefinitionRegistrar接口,那咱们在这个类中使用@Autowired或者@Value注解,咱们会发现失效了。缘由是,spring容器执行接口的方法时,此时尚未去解析@Autowired或者@Value注解。若是咱们要使用获取配置文件属性,能够经过原始方式,直接用IO读取配置文件,而后获得Properties对象,而后再获取配置值。
这里会调用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); ... /** * 这里会将basePackages下的mapper所有扫描成bd并实例化成bean * 注意这里注册的全部bean实际均为MapperFactoryBean[代理] */ 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 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); } /** * 该对象包含一些和 SQL 相关的信息 * 经过它能够找到MappedStatement * 咱们能够看到熟悉的 Invalid bound statement (not found) 异常 */ 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的整合流程了。
这篇文章到这就结束啦,喜欢的话就给个赞 + 收藏 + 关注吧!🤓 有什么想看的欢迎留言!!!
我这边也整理了一份 架构师全套视频教程和关于java的系统化资料,包括java核心知识点、面试专题和20年最新的互联网真题、电子书等都有。有须要的朋友能够点一点下方连接免费领取!
连接:1103806531暗号:CSDN