1、基于java Config配置
@Configuration
@MapperScan(basePackages = "org.wfs.mybatis.**.mapper",sqlSessionTemplateRef = "sqlSessionTemplate")
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSources(dataSource);
PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(patternResolver.getResource("classpath:org/wfs/mybatis/**/mapper/*.*.xml"));
return bean;
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
数据源DataSource则自己选择
这边Mapper的xml放在包下,需要配置pom文件将其打包到jar中:
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
当然你也可以将Mapper文件放在resources资源文件下
2、@MapperScan实现原理
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
...
}
可以看到上面使用了spring的@Import(MapperScannerRegistrar.class)注解来实现
ImportBeanDefinitionRegistrar是一个接口,该接口的实现作用于spring的解析bean的配置阶段,当解析@Configuration注解时,可以通过ImportBeanDefinitionRegistrar接口的实现类,向BeanDefinitionRegistry容器中添加额外的BeanDefinition,需要配合@Import使用
2.1、ImportBeanDefinitionRegistrar接口
可以根据@Configuration注解类上的注解数据来注册一些自定义的Bean;BeanDefinitionRegistryPostProcessor由于生命周期的限制最好不要在这里注册
之前的版本就是在这里注册MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor这个类,使用BeanDefinitionRegistryPostProcessorpost.ProcessBeanDefinitionRegistry来扫描注册@Mapper接口
public interface ImportBeanDefinitionRegistrar {
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
2.2、registerBeanDefinitions方法的实现
首先是获取@MapperScanner注解数据,然后调用具体的注册方法
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(mapperScanAttrs, registry);
}
}
注册流程其实很简单:
-
创建ClassPathMapperScanner ,用来扫描配置的包,将@Mapper注解的接口注册到spring容器中
-
处理注解数据,设置到ClassPathMapperScanner中
-
ClassPathMapperScanner.doScan方法,扫描并注册@Mapper接口
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass);
}
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
.map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
2.3、如何扫描包并注册
ClassPathMapperScanner 继承ClassPathBeanDefinitionScanner
在上面看到scanner.registerFilters();这个主要是注册过滤,只扫描自己需要的类,这个我们不必研究,这里将扫描所有的类,排除package-info.java
2.3.1、具体扫描方法
重写父类的方法,其实扫描注册还是使用父类的方法,只是需要自定义给bean赋值
@Override
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;
}
接下来看看给Bean定义赋值方法:
-
遍历bean定义
-
给构造函数设置入参为接口的class
-
设置beanClass为MapperFactoryBean,使用MapperFactoryBean.getObject创建代理类
-
addToConfig默认设置为true,这个配置会将接口同包下同名的Mapper文件加载到Mybatis中,可以无需配置Mapper文件的扫描地址
-
设置sqlSessionTemplate、sqlSessionFactory
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + beanClassName + "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
LOGGER.warn(() -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}
3、MapperFactoryBean创建代理
通过实现FactoryBean.getObject方法创建并且生成bean注入到spring容器中。
创建mapper接口代理主要就是将SqlSession放入代理对象;
所以我们需要了解一下SqlSessionTemplate,因为spring放入的是SqlSessionTemplate单例;
SqlSession不是一个线程安全的对象,spring是如何做到单例的线程安全:
其内部持有一个SqlSession的代理属性
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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
对数据库的操作都是通过这个sqlSessionProxy去调用的;
下面看一下代理的实现类,是SqlSessionTemplate的私有内部类:
-
将获取线程安全的SqlSession,本质就是利用ThreadLocal来存放
-
反射调用SqlSession对象方法
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);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}