第一篇 Mybatis源码阅读:Mybatis和Spring集成实现原理
一、Mybatis的启动流程
Spring能够与Mybatis的完美集成,只要引入mybatis-spring.jar包,都不需要配置mybatis-Config.xml文件,就可以通过Spring的IOC获取到Mybatis的Mapper来进行持久化操作,这篇文章将会从源码级别讲述mybatis与spring的集成机制。
public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 初始化配置, 创建SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 创建会话 SqlSession sqlSession = sqlSessionFactory.openSession(); // 获取Mapper代理类 BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class); // 执行用户请求 Blog blog = blogMapper.selectBlog(1L); }
以上面代码为例,在Mybatis启动的过程中,首先会通过加载xml配置,生成SqlSessionFactory,然后当发起Mapper请求时,则通过Mapper代理类中的SqlSession完成一次数据库会话,这个具体过程可参考我的其他Mybatis源码文章,在这个过程中,Mybatis的四大组件都参与了会话,但是他们的生命周期却是不同,如下图:
- Configuration 管理Mybatis的全局配置,包括缓存、日志、映射器等,加载之后就不会改变,作用于整个应用
- SqlSessionFactory 会话工厂,负责创建SqlSession, 作用于整个应用
- SqlSession 会话,负责和数据库交互,真正实现持久化操作,和Mapper、Connection、事务都是一对一,作用域是一次请求,不支持跨线程使用
- Mapper 映射器,映射用户调用的接口,内部维护一个SqlSession,使用SqlSession实现持久化操作,因为是通过SqlSession的getMapper使用动态代理创建,所以作用域和会话相同,也不支持跨线程。
通过上面的描述,可以发现Mybatis的Mapper的使用并不符合Spring中使用的条件,在Spring中,需要将Mapper作为Bean注入到Service中,实现重复使用,上面和SqlSession绑定的Mapper显然不符合需求
二、集成机制原理
@Configuration public class MybatisConfig { @Bean public MapperScannerConfigurer mapperScannerConfigurer(SqlSessionFactory sqlSessionFactory) { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.example.dao"); return mapperScannerConfigurer; } @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } @Bean public DataSource dataSource() { DataSource dataSource = new DriverManagerDataSource("jdbc:mysql://192.168.117.128:3306/pattern?characterEncoding=UTF-8&serverTimeZone=GMT", "root", "root"); return dataSource; } public static void main(String[] args) { // 初始化Spring容器 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MybatisConfig.class); // 获取Mapper代理类 BlogMapper blogMapper = applicationContext.getBean("blogMapper", BlogMapper.class); // 执行用户请求 Blog one = blogMapper.findOne(1L); System.out.println(one); } }
上面是Spring集成Mybatis的代码配置,配置中初始化了两个Bean,分别是MapperScannerConfigurer和SqlSessionFactoryBean,MapperScannerConfigurer类的作用扫描Mapper包下的Mapper接口,并将他们的Bean信息保存IOC中。SqlSessionFactoryBean类用于加载和初始化Mybatis的配置,完成Configuration、SqlSessionFactory的初始化。
如上图,就是集成简单流程,下面将从包扫描、Mybatis配置初始化、请求过程三个部分分析集成的源码。
Mapper包扫描
Mapper包扫描的流程如上图,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,以postProcessBeanDefinitionRegistry()方法为入口,开始包扫描,源码如下:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); // ... // 扫描包 scanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
之后,会通过ClassPathMapperScanner的doScan方法获取BeanDefinitionHolder,BeanDefinitionHolder封装了Mapper代理类的name和描述,之后会调用processBeanDefinitions()处理BeanDefinitionHolder,将获取BeanDefinitionHolder中的BeanDefinition,并封装为MapperFactoryBean的BeanDefinition注册到IOC中, 到此,就完成了整个包扫描流程,源码如下:
public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { AbstractBeanDefinition definition; BeanDefinitionRegistry registry = getRegistry(); // 遍历beanDefinitions for (BeanDefinitionHolder holder : beanDefinitions) { definition = (AbstractBeanDefinition) holder.getBeanDefinition(); // ... // the mapper interface is the original class of the bean // but, the actual class of the bean is MapperFactoryBean 真实的Bean类型为MapperFactoryBean definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); definition.setBeanClass(this.mapperFactoryBeanClass); // ... if (!definition.isSingleton()) { BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true); if (registry.containsBeanDefinition(proxyHolder.getBeanName())) { registry.removeBeanDefinition(proxyHolder.getBeanName()); } // 注册Bean到IOC registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition()); } } }
Mybatis配置初始化
如上图,包扫描完成之后,就是SqlSessionFactoryBean的初始化,SqlSessionFactoryBean实现了InitializingBean接口实现了afterPropertiesSet方法,在该方法调用 buildSqlSessionFactory()方法,首先初始化了Configuration,之后使用SqlSessionFactoryBuilder创建SqlSessionFactory,这个SqlSessionFactory之后会注入到SqlSessionTemplate中,并在SqlSessionTemplate中创建SqlSession,这里Configuration和SqlSessionFactory都是Mybatis的组件,所以这里首先完成了Mybatis的配置集成。
Mapper请求过程
集成之后的Mapper请求要比原生Mybatis的Mapper复杂很多,Mapper也是通过Spring IOC获取,前面已经说过IOC存储的是MapperFactoryBean,所以Mapper代理类的也是通过MapperFactoryBean创建,所以代码分析也从这里开始。
如图,MapperFactoryBean实现了FactoryBean和SqlSessionDaoSupport接口,FactoryBean接口用于Spring中创建复杂Bean,SqlSessionDaoSupport抽象类持有一个SqlSessionTemplate,这个SqlSessionTemplate之后会被注入到Mapper代理类中, MapperFactoryBean创建Mapper代理类时,并不是使用Spring代理创建,而是委托给Mybatis的Configuration组件创建,这里Mapper的获取和原生Mybatis相同,不同是注入代理类的SqlSession不再是DefaultSqlSession对象,而是SqlSessionTemplate,在SqlSessionTemplate中又持有一个SqlSession的代理类.
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { private Class<T> mapperInterface; // ... // 获取Mapper @Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); } // ... }
public abstract class SqlSessionDaoSupport extends DaoSupport { private SqlSessionTemplate sqlSessionTemplate; // ... // 注入sqlSessionFactory到SqlSessionTemplate中 public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) { if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) { this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory); } } // ... protected SqlSessionTemplate createSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } // ... // 获取SqlSessionTemplate public SqlSession getSqlSession() { return this.sqlSessionTemplate; } // ... }
public class SqlSessionTemplate implements SqlSession, DisposableBean { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; // ... // 从Configuration中获取Mapper @Override public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); } // ... @Override public Configuration getConfiguration() { return this.sqlSessionFactory.getConfiguration(); } // ... }
根据上面可以代码可知,SqlSessionTemplate是集成实现的关键,所以下面来分析SqlSessionTemplate的代码,首先SqlSessionTemplate中持有一个SqlSession的代理类,也就是sqlSessionProxy对象,SqlSessionTemplate的增删改查都是通过这个代理类实现.同时可以发现不支持commit等事务方法,由此可以推断,集成之后,Mybatis不在管理事务,而是交给Spring管理.
public class SqlSessionTemplate implements SqlSession, DisposableBean { private final SqlSessionFactory sqlSessionFactory; private final ExecutorType executorType; private final SqlSession sqlSessionProxy; // ... // 从Configuration中获取Mapper @Override public <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this); } // ... @Override public Configuration getConfiguration() { return this.sqlSessionFactory.getConfiguration(); } // ... public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required"); notNull(executorType, "Property 'executorType' is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; // 创建sqlSessionProxy代理类 this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); } // ... // 通过sqlSessionProxy代理类实现数据库操作 @Override public <T> T selectOne(String statement, Object parameter) { return this.sqlSessionProxy.selectOne(statement, parameter); } @Override public <K, V> Map<K, V> selectMap(String statement, String mapKey) { return this.sqlSessionProxy.selectMap(statement, mapKey); } // ... // Spring事务无法被覆盖 @Override public void commit(boolean force) { throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession"); } @Override public void rollback() { throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession"); } // ... }
之后最重要的一个点就是SqlSessionInterceptor类,SqlSessionInterceptor类InvocationHandler接口,实现了invoke方法,这个方法中记录了事务处理以及集成Mybatis一级缓存失效的原因.
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 创建一个DefaultSqlSession对象 SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { // 执行代理方法 Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // 判断SqlSession有没有被Spring事务管理 // 如果被管理,强制提交 sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); sqlSession = null; Throwable translated = SqlSessionTemplate.this.exceptionTranslator .translateExceptionIfPossible((PersistenceException) unwrapped); if (translated != null) { unwrapped = translated; } } throw unwrapped; } finally { // 关闭DefaultSqlSession对象 if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
可以发现在invoke方法中,创建了DefaultSqlSession执行数据库操作,同时,Mybatis事务交由Spring管理,这也是之前commit方法不支持使用的原因,另外就是每次操作都会关闭DefaultSqlSession对象,因为Mybatis的一级缓存是SqlSession域缓存,所以这也导致一级缓存失效.