1.1、Mybatis Starter作用
1) 自动检测工程中的DataSource
2) 创建并注册SqlSessionFactory实例
3) 创建并注册SqlSessionTemplate实例
4) 自动扫描mappers并将其注入BeanFactory,这样我们就直接可以@Autowired使用
1.2、Mybatis使用指南
下面介绍下在SpringBoot中引入Mybatis starter的步骤
1) 在pom文件中引入相关依赖,使用的SpringBoot的版本是2.1.7.RELEASE
<dependency>
<groupid>org.mybatis.spring.boot</groupid>
<artifactid>mybatis-spring-boot-starter</artifactid>
<version>2.1.0</version>
</dependency>
<dependency>
<groupid>mysql</groupid>
<artifactid>mysql-connector-java</artifactid>
<version>5.1.39</version>
</dependency>
2) 使用mybatis逆向工程,现在pom中引入依赖
<build>
<!--单元测试时引用src/main/resources下的资源文件-->
<testresources>
<testresource>
<directory>src/test/resources</directory>
</testresource>
<testresource>
<directory>src/main/resources</directory>
</testresource>
</testresources>
<plugins>
<plugin>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-maven-plugin</artifactid>
</plugin>
<plugin>
<groupid>org.mybatis.generator</groupid>
<artifactid>mybatis-generator-maven-plugin</artifactid>
<version>1.3.5</version>
<dependencies>
<dependency>
<groupid>mysql</groupid>
<artifactid>mysql-connector-java</artifactid>
<version>5.1.38</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
3) 在resources目录下新建generatorConfig.xml文件
4) 配置数据库即Mybatis属性
spring.datasource.username=root
spring.datasource.password=2109
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
mybatis.mapper-locations=classpath:mapper/*.xml
mybatis.type-aliases-package=com.lwh.springboot.bean
#开启下划线-驼峰命名转换
mybatis.configuration.map-underscore-to-camel-case=true
5) 在主配置类上加上包扫描
@SpringBootApplication
@MapperScan("com.lwh.springboot.mapper")
public class SpringbootApplication {
也可以是这样,二选一即可
@Mapper
public interface UserMapper {
6) 运行generator逆向工程,如下图,发现在对应的位置中生成了bean、Mapper接口、Mapper文件
6) 写一个单元测试,发现可以插入表数据
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {SpringbootApplication.class})
public class Springboot2ApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testInsert(){
User user = new User();
user.setName("xx");
user.setAge(25);
userMapper.insert(user);
}
}
1.3、Mybatis starter原理解析
1.3.1、配置类引入原理
// 发现里面有两个自动配置类,MybatisAutoConfiguration和MybatisLanguageDriverAutoConfiguration,
MybatisLanguageDriverAutoConfiguration主要是用来处理注解版的SQL语句,观察其类,发现很多报红,不满足Condition条件.
我们主要看MybatisAutoConfiguration
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
// 数据源的相关配置
@Configuration
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
// MybatisAutoConfiguration会给容器中注入两个关键的bean
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// SqlSessionTemplate是Mybatis为了接入Spring引入的,其内部也是封装了SqlSession和SqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
// 里面的方法是通过SqlSessionProxy来执行的
private final SqlSession sqlSessionProxy;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 发现sqlSessionProxy是SqlSession的代理对象,其方法会被SqlSessionInterceptor代理执行
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
return result;
}
}
}
1.3.2、注解扫描原理
1.3.2.1、@Mapper注解扫描
// MybatisAutoConfiguration里面有两个静态内部类
@org.springframework.context.annotation.Configuration
// 发现这个类主要是给容器中导入了一个组件,是AutoConfiguredMapperScannerRegistrar,也就是下面的类
@Import(AutoConfiguredMapperScannerRegistrar.class)
// 条件是容器中没有这个bean,MapperScannerConfigurer
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
// 这个类是ImportBeanDefinitionRegistrar实现,之前已经分析过了,可以给容器中导入一些bean定义
// This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use
// {@link org.mybatis.spring.annotation.MapperScan}
public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware,
ImportBeanDefinitionRegistrar {
private BeanFactory beanFactory;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取的就是主配置类所在包
List<string> packages = AutoConfigurationPackages.get(this.beanFactory);
// 生成一个BeanDefinition,并设置相关属性,后面要用到
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
builder.addPropertyValue("processPropertyPlaceHolders", true);
builder.addPropertyValue("annotationClass", Mapper.class);
builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
Stream.of(beanWrapper.getPropertyDescriptors())
// Need to mybatis-spring 2.0.2+
.filter(x -> x.getName().equals("lazyInitialization")).findAny()
.ifPresent(x -> builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}"));
// 将这个BeanDefinition注册到容器中
registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
}
}
// 上面注册的bean是MapperScannerConfigurer,发现它是BeanDefinitionRegistryPostProcessor
public class MapperScannerConfigurer
implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
// 会调用这个方法
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// 上面设置的,为true,这边会做属性占位符替换
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
// 创建一个ClassPathMapperScanner对象
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
if (StringUtils.hasText(lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
}
// 关注一下这个方法
scanner.registerFilters();
scanner.scan(
StringUtils.tokenizeToStringArray(this.basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
public void registerFilters() {
boolean acceptAllInterfaces = true;
// if specified, use the given annotation and / or marker interface
if (this.annotationClass != null) {
// 添加一个IncludeFilter,实现注解扫描
addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
acceptAllInterfaces = false;
}
// scanner.scan
@Override
public Set<beandefinitionholder> doScan(String... basePackages) {
// 这边就是扫描这个路径,然后通过IncludeFilter去过滤
Set<beandefinitionholder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
// 打个日志记录下
} else {
// 走这边,下面在1.3.3中分析这边
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
1.3.2.2、@MapperScan注解扫描
@SpringBootApplication
@MapperScan("com.lwh.springboot.mapper")
public class SpringbootApplication {
// 上面我们分析了@Mapper实现扫描,现在看下@MapperScan,发现它注解上通过@Import引入了一个
// MapperScannerRegistrar.class,而上面我们@Mapper生效的条件就是容器中没有这个bean
// 那么我们可以知道,如果指定了@MapperScan扫描,@Mapper扫描就失效了,如果两者共存,@Mapper负责的Mapper不在
// @MapperScan扫描的包路径下,这个Mapper就不会注入到容器中,使用就会报错
// No qualifying bean of type 'com.lwh.springboot.test.UserMapper' available: expected at least 1 bean which
// qualifies as autowire candidate
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
// 条件是容器中没有这个bean,MapperScannerConfigurer
@ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取@MapperScan注解上的属性
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
// 该方法最关键的就是往容器中注入了一个MapperScannerConfigurer,这个我们上面已经分析过了
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
1.3.3、Mapper类生成原理
private void processBeanDefinitions(Set<beandefinitionholder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
// 遍历这些BeanDefinitions,修改其beanClass
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
// 比如com.lwh.springboot.mapper.UserMapper,将其替换为MapperFactoryBean.class
// mapperFactoryBeanClass = MapperFactoryBean.class
// MapperFactoryBean是FactoryBean的实现,那么以后我们创建UserMapper的实例时,实际上会调用MapperFactoryBean的
// getObject方法
definition.setBeanClass(this.mapperFactoryBeanClass);
public class MapperFactoryBean<t> extends SqlSessionDaoSupport implements FactoryBean<t> {
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
public T newInstance(SqlSession sqlSession) {
// 上面的getMapper方法会走到这里,创建mapperInterface的代理对象,由mapperProxy代理
final MapperProxy<t> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
1.3.4、Mapper类执行原理
@Test
public void testInsert(){
User user = new User();
user.setName("xx");
user.setAge(25);
// 经过上述分析,我们知道userMapper是MapperProxy类型的代理对象,那么它会先走到MapperProxy的invoke方法
userMapper.insert(user);
}
public class MapperProxy<t> implements InvocationHandler, Serializable
@Override
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 (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 一般情况下会走到这里,下面就是Mybatis的源码了,根据传入SQL类型,进行增删改查操作
return mapperMethod.execute(sqlSession, args);
}
来源:oschina
链接:https://my.oschina.net/liwanghong/blog/3168714