mybatis-spring 可以为我们做什么
mybatis框架已经很不错了,它把配置和执行sql的通用过程抽象出来。只要你符合mybatis框架的要求,首先有正确的配置,然后有model,interface层,sql语句,还有bean定义让interface和sql关联起来,那么当你执行interface中的方法的时候,mybatis框架就会为你找到对应的sql的可执行的statement,然后执行并返回结果。
可是这样还不够,最大的问题在于许多bean定义,必须一个一个手写,并且要保证interface和sql的名称和位置要填写正确。mybatis-spring最大的贡献在于它的bean扫描机制,只要注解使用正确,那么它可以为你自动扫描所有interface和sql语句,并且建立bean定义让它们关联起来。Spring还可以为动态的Bean定义创建缓存,这非常酷。
另外,既然融入了Spring框架,那么mybatis配置信息和SqlSessionFactory之类的信息,也可以用 Spring Bean 来管理。Spring 可以在它的层面上为它们做一些缓存。
mybatis执行sql的完整流程
我们回顾一下在单一的mybatis的机制中,配置的加载和sql的执行的完整流程。
// 解析配置文件,生成配置 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); // 根据配置,构建一个SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 得到一个真正可用的SqlSession SqlSession sqlSession = sqlSessionFactory.openSession(); // 从SqlSession获取interface的代理 ArticleMapper articleMapperProxy = sqlSession.getMapper(ArticleMapper.class); // 执行代理类中的方法 Article article = articleMapperProxy.selectByPrimaryKey("123"); // 以下省略对 article 的操作
mybatis-spring 启动过程
我们先来说说mybatis-spring框架的启动过程。mybatis-spring 决定接管 sqlSessionFactory 和 sqlSession,并且为 sqlSession 创建代理类 sqlSessionProxy。除此之外,mybatis-spring 决定扫描所有interface层的mapper,然后接管所有 mapper 的代理类。2条线我们分开来看。
线1:sqlSessionFactory 和 sqlSession 的初始化
- 创建 sqlSessionFactoryBean,这是一个被Spring管理的工厂bean
- 创建 sqlSessionFactory,这是一个 Spring 管理的 Bean,属于 mybatis 范畴
- 创建 sqlSessionTemplate,其中使用到了 sqlSessionFactory,属于 mybatis-spring 范畴
- 创建 sqlSessionProxy
SqlSessionFactory是一个十分重要的工厂类,让我们来回顾一下SqlSessionFactory中有哪些信息:
# sqlSessionFactory 中的重要信息 sqlSessionFactory configuration environment # 里面有 dataSource 信息 mapperRegistry config # 里面有配置信息 knownMappers # 里面有所有的 mapper mappedStatements # 里面有所有 mapper 的所有方法 resultMaps # 里面有所有 xml 中的所有 resultMap sqlFragments # 里面有所有的 sql 片段
这些信息非常重要,在不久的将来创建 sqlSessionProxy 和 将来创建 mapperProxy 的时候,都需要使用里面的信息。
在 mybatis-spring 框架中,sqlSessionFactory由Spring管理,让我们来看一下 sqlSessionFactory 是如何创建出来的。
1 @Bean(name = "sqlSessionFactory") 2 public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { 3 SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); 4 factory.setDataSource(dataSource); 5 if (StringUtils.hasText(this.properties.getConfig())) { 6 factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfig())); 7 } else { 8 if (this.interceptors != null && this.interceptors.length > 0) { 9 factory.setPlugins(this.interceptors); 10 } 11 factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); 12 factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); 13 factory.setMapperLocations(this.properties.getMapperLocations()); 14 } 15 return factory.getObject(); 16 }
注意:SqlSessionFactoryBean
是一个工厂bean,它的作用就是解析mybatis 配置(数据源、别名等),然后通过 getObject方法返回一个 SqlSessionFactory 实例。我们先看下SqlSessionFactoryBean是在初始化的时候作了哪些工作。
让我们来看一下 SqlSessionFactoryBean 的源码:
1 public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> { 2 private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class); 3 private Resource configLocation; 4 private Configuration configuration; 5 private Resource[] mapperLocations; 6 private DataSource dataSource; 7 private TransactionFactory transactionFactory; 8 private Properties configurationProperties; 9 private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); 10 private SqlSessionFactory sqlSessionFactory; 11 private String environment = SqlSessionFactoryBean.class.getSimpleName(); 12 private boolean failFast; 13 private Interceptor[] plugins; 14 private TypeHandler<?>[] typeHandlers; 15 private String typeHandlersPackage; 16 private Class<?>[] typeAliases; 17 private String typeAliasesPackage; 18 private Class<?> typeAliasesSuperType; 19 private DatabaseIdProvider databaseIdProvider; 20 private Class<? extends VFS> vfs; 21 private Cache cache; 22 private ObjectFactory objectFactory; 23 private ObjectWrapperFactory objectWrapperFactory; 24 25 public SqlSessionFactoryBean() { 26 } 27 ... 28 }
我们可以看到,这个类实现了FactoryBean、InitializingBean和ApplicationListener接口,对应的接口在bean初始化的时候又执行了一些特定的方法,此处不再展开。现在来看看都有哪些重要的方法会被执行,这些方法又做了哪些工作。
// FactoryBean中的方法 public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { this.afterPropertiesSet(); } return this.sqlSessionFactory; }
通过观察代码,getObject方法最终返回 sqlSessionFactory,如果 sqlSessionFactory 为空就会执行 afterPropertiesSet 中的 buildSqlSessionFactory 构建sqlSessionFactory,在构建sqlSessionFactory时mybatis会去解析配置文件,构建configuation。后面的onApplicationEvent主要是监听应用事件时做的一些事情(不展开,有兴趣的同学可以自己去了解下)。
我们来看看 afterPropertiesSet 方法是怎么将属性设置进去的:
// InitializingBean中的方法 public void afterPropertiesSet() throws Exception { Assert.notNull(this.dataSource, "Property 'dataSource' is required"); Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together"); // 看到了我们熟悉的build方法 this.sqlSessionFactory = this.buildSqlSessionFactory(); }
buildSqlSessionFactory 的主要方法是:this.sqlSessionFactoryBuilder.build(configuration); 此处不再展开。到这里为止,sqlSessionFactory 已经创建完成,下面我们来简单看看 sqlSessionTemplate 的创建过程:
@Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory, this.properties.getExecutorType()); }
如果跟踪进去,就会发现 new SqlSessionTemplate 的同时,会创建 sqlSessionProxy,此处不再展开。
线2:mapper 的扫描和代理类的创建
interface首先要被扫描,然后挨个生成代理类,等待调用。下面我们来看看这个过程:
- 使用 MapperScannerConfigurer
- scan() 扫描mapper
- setBeanClass
- 得到 MapperFactoryBean
- MapperFactoryBean.getObject() 方法
- configuration.getMapper
- getMapper
- MapperRegistry
- knownMappers
- 最终得到一组 MapperProxy,他们是原始 mapper 的代理。MapperProxy 实现了 InvocationHandler 接口,其中有 invoke 方法,在实际调用的时候执行。
说明:对于第4点,MapperFactoryBean
是一个工厂bean,在spring容器里,工厂bean是有特殊用途的,当spring将工厂bean注入到其他bean里时,它不是注入工厂bean本身,而是调用bean的getObject方法。
mybatis-spring 调用过程
sqlSessionFactory的初始化完成后,mapper的扫描和代理类被创建出来后,有了这两个前提条件,我们就可以来最终捋一捋 mybatis-spring 的调用过程了。
-
业务代码中,需要查询数据库,于是调用 mapper 中的一个方法
- MapperProxy invoke
- 2.1 if 判断
- 2.2 else if 判断
- 2.3 cachedMapperMethod 重点方法
- 2.4 mapperMethod.execute 重点方法,实际执行的方法
- 根据sql语句的类型,分情况处理
- case INSERT
- case UPDATE
- case DELETE
- case SELECT
- case FLUSH
-
以 SELECT 情况举例,将会执行 sqlSession.selectList。此时取出的,就是mapper的一个代理类
-
sqlSessionTemplate.sqlSessionProxy.selectList
- SqlSessionInterceptor invoke
- 6.1 getSqlSession,用到了 sqlSessionFactory,使用了sqlSessionHolder 技术,有就拿一个,没有就新建一个。无论如何,都会有一个 sqlSession
- 6.2 defaultSqlSession.selectList 重点,之后是 query -> queryFromDatabase -> doQuery -> 1. prepareStatement 2. execute
- 6.3 closeSqlSession
我们可以发现,在 mybatis-spring 框架中,真正 sqlSession 的创建,是在调用interface中的方法的时候才进行的。更细节的过程可以参考上文。
参考资料
- https://segmentfault.com/a/1190000015165470
创作时间:06/08/2019 21:00