简介
在上几篇文章中,解析了 MyBatis 的核心原理部分,我们大致对其有了一定的了解,接下来我们看看在日常的开发中 MyBatis 是如何与 Spring 框架结合的
源码解析
在我们的日常开发中,使用 Spring 框架结合 MyBatis,只需简单的进行相关的配置,在代码中使用注解即可轻松使用,而不用像下面示例中的,需要很多的代码:
public class MybatisTest {
// 从SqlSessionFactory中获取Mapper进行使用 @Test public void test() { try(SqlSession session = buildSqlSessionFactory().openSession()) { PersonMapper personMapper = session.getMapper(PersonMapper.class); personMapper.createTable(); personMapper.save(Person.builder().id(1L).name(new String[]{"1", "2"}).build()); personMapper.save(Person.builder().id(2L).name(new String[]{"1", "2"}).build());
Map<String, Object> query = new HashMap<>(2); query.put("id", 1); query.put("name", new String[]{"1", "2"}); System.out.println(personMapper.getPersonByMap(query)); } }
// 初始化SqlSessionFactory public static SqlSessionFactory buildSqlSessionFactory() { String JDBC_DRIVER = "org.h2.Driver"; String DB_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"; String USER = "sa"; String PASS = ""; DataSource dataSource = new PooledDataSource(JDBC_DRIVER, DB_URL, USER, PASS); Environment environment = new Environment("Development", new JdbcTransactionFactory(), dataSource); Configuration configuration = new Configuration(environment); configuration.getTypeHandlerRegistry().register(String[].class, JdbcType.VARCHAR, StringArrayTypeHandler.class); configuration.addMapper(PersonMapper.class); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); return builder.build(configuration); }}
复制代码
在上面的代码中,我们可以明显的看到大段的 SqlSessionFactory 初始化和 Mapper 的获取的繁琐代码,但我们在 Spring 中没有这些繁琐的操作,如下变可以使用:
@AllArgsConstructor@Servicepublic class PersonService {
private final PersonMapper personMapper;
public void select() { personMapper.getPersonById(0); }}
复制代码
可以说非常省心和简单了
以前自己也不知道其原理,通过这段时间的学习,了解了 MyBatis 的原始使用和 Spring 的相关原理,知道了其背后的原理,在开发中使用起来感觉心中比以前有数了,希望通过这些源码解析的文章,也能对大家有所帮助
SqlSessionFactory 的初始化
这部分涉及到 Spring 的 Bean 和 Spring Boot 自动配置
可以尝试去自己写一个 Spring Boot Starter,这样会有比较大帮助(以前好像写过一篇,但找不到了,可以参考下下面其他人的链接):
这里我们直接去看包:org.mybatis.spring.boot.autoconfigure
可以看到下面一段我们熟悉的内容:
可以看到 Spring 在启动时便自动初始化了 SqlSessionFactory,其中的细节这里就不赘述了
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); }
this.applyConfiguration(factory); if (this.properties.getConfigurationProperties() != null) { factory.setConfigurationProperties(this.properties.getConfigurationProperties()); }
if (!ObjectUtils.isEmpty(this.interceptors)) { factory.setPlugins(this.interceptors); }
if (this.databaseIdProvider != null) { factory.setDatabaseIdProvider(this.databaseIdProvider); }
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); }
if (this.properties.getTypeAliasesSuperType() != null) { factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType()); }
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage()); }
if (!ObjectUtils.isEmpty(this.typeHandlers)) { factory.setTypeHandlers(this.typeHandlers); }
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); }
Set<String> factoryPropertyNames = (Set)Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet()); Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver(); if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) { factory.setScriptingLanguageDrivers(this.languageDrivers); if (defaultLanguageDriver == null && this.languageDrivers.length == 1) { defaultLanguageDriver = this.languageDrivers[0].getClass(); } }
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) { factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver); }
this.applySqlSessionFactoryBeanCustomizers(factory); return factory.getObject(); }
复制代码
Mapper 的生成使用
这部分涉及到 Spring 的依赖注入相关的原理了,自己以前写过一个 demo,但文章还没完善,后面抽空完善后,再发出来
简答来说,核心是:
比如下面的:
@AllArgsConstructor@Servicepublic class PersonService {
private final PersonMapper personMapper;}
复制代码
在 Spring 容器中已经有了类 PersonService 和 PersonMapper 的实例
在初始化阶段,调用 PersonService 构造函数时,发现需要 PersonMapper,由于从容器中取 PersonMapper 给其传递进去
大体的意思应该是这样,可能细节上会有些出入
下面是大致一些 Debug 的信息,通过调试就会发现,SqlSessionFactory 和 Mapper 初始化和获取等代码都是在应用程序其中就会执行触发
这个是 PersonService 这个 bean 初始化的相关栈函数:
@Nullable protected Object resolveAutowiredArgument(MethodParameter param, String beanName, @Nullable Set<String> autowiredBeanNames, TypeConverter typeConverter, boolean fallback) { Class<?> paramType = param.getParameterType(); if (InjectionPoint.class.isAssignableFrom(paramType)) { InjectionPoint injectionPoint = (InjectionPoint)currentInjectionPoint.get(); if (injectionPoint == null) { throw new IllegalStateException("No current InjectionPoint available for " + param); } else { return injectionPoint; } } else { try { return this.beanFactory.resolveDependency(new DependencyDescriptor(param, true), beanName, autowiredBeanNames, typeConverter); } catch (NoUniqueBeanDefinitionException var8) { throw var8; } catch (NoSuchBeanDefinitionException var9) { if (fallback) { if (paramType.isArray()) { return Array.newInstance(paramType.getComponentType(), 0); }
if (CollectionFactory.isApproximableCollectionType(paramType)) { return CollectionFactory.createCollection(paramType, 0); }
if (CollectionFactory.isApproximableMapType(paramType)) { return CollectionFactory.createMap(paramType, 0); } }
throw var9; } } }
复制代码
调试的相关信息如下:
这个 beanName 是 PersonService,需要的构造函数参数是 PersonMapper
一直跟着下面,它会从 SqlSessionTemplate 中取
public <T> T getMapper(Class<T> type) { return this.getConfiguration().getMapper(type, this); }
复制代码
然后就跳到我们熟悉的 Mybatis 类中 Configuration:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
复制代码
总结
本篇中大致探索了:
评论