简介
在上几篇文章中,解析了 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
@Service
public 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
@Service
public 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);
}
复制代码
总结
本篇中大致探索了:
评论