spring在和mybatis整合时,有两个主要的配置项;
一是sqlSessionFactory的配置,在这个配置里会配置数据库连接池,mapper的xml的路径,比如
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(boneCPDataSource());
// 配置mapper的扫描,找到所有的mapper.xml映射文件
Resource[] resources = new PathMatchingResourcePatternResolver()
.getResources("classpath:mapper/*.xml");
factoryBean.setMapperLocations(resources);
return factoryBean;
}
@Bean(destroyMethod="close")
public BoneCPDataSource boneCPDataSource(){
//配置数据库连接池对象
BoneCPDataSource boneCPDataSource=new BoneCPDataSource();
boneCPDataSource.setDriverClass("com.mysql.jdbc.Driver");
boneCPDataSource.setUsername("root");
boneCPDataSource.setPassword("root");
boneCPDataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/mytest?serverTimezone=GMT%2B7");
return boneCPDataSource;
}
二是mapper类路径的配置,这个路径用一个注解就可以了,在启动的配置类里添加
@MapperScan("com.cn.spring.mybatis.mapper")
此处,我们先看下sqlSessionFactory都做了哪些事
1、说下SqlSessionFactoryBean和SqlSessionFactory的不同。SqlSessionFactoryBean实现了FactoryBean的接口,我们都知道在创建spring的bean时,有一种方式就是实现此接口。所以SqlSessionFactoryBean和SqlSessionFactory本质上是一样的。
2、SqlSessionFactoryBean还实现了InitializingBean接口,这个是在spring中bean的生命周期中被回调的,具体是在bean初始化完之后回调InitializingBean的afterPropertiesSet方法,就是下面这个方法
/**
* {@inheritDoc}
*/
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
3、上面的这个方法,主要是调用了buildSqlSessionFactory()这个方法。这个方法里有很多配置属性的解析,主要是配置的
configLocation里的XML的属性。当然这次我们比较关心mapper.xml的解析,所以暂时先放过它们,看下mapper的解析。
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
4、上面的主要是读取了配置了mapperLocations下的文件。然后遍历解析。调用的方法是xmlMapperBuilder.parse();我们看下这个方法的实现
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
//解析resultMap
parsePendingResultMaps();
//解析cacheref
parsePendingCacheRefs();
//解析statements
parsePendingStatements();
}
这个方法里,主要看bindMapperForNamespace();这个是注册所有mapper的映射类
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
最终是注册到了一个map里,
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
这个map是knownMappers,记住这个map,后期会用到的,至此sqlSessionFactory在初始化阶段的工作基本完成。
二,我们再看下@MapperScan都做了哪些事
1、在这个注解里引入了另一个组件@Import(MapperScannerRegistrar.class),这个也是整合spring的惯用套路。
2、MapperScannerRegistrar,实现了ImportBeanDefinitionRegistrar,接口,这个也是注册bean的一个常用方法。可以实现
registerBeanDefinitions这个方法,然后在此方法里注册bean。看下它注册了什么bean
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
generateBaseBeanName(importingClassMetadata, 0));
}
}
void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
BeanDefinitionRegistry registry, String beanName) {
//其它代码删除了,只留了这一行,因为MapperScannerConfigurer是接下来的最重要的一步
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}
可以看到这个类注册了MapperScannerConfigurer类,从名字,也可以看出这个类,主要是用来扫描mapper的类的。MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,这也是spring生命周期中,比较重要的一个后置处理器。是在beanFactory初始完之后调用的。此处可以对bean容器进行修改,包括添加自己的bean组件。
而mybatis也是在这个后置处理器里添加自己的mapper组件的。看下他在postProcessBeanDefinitionRegistry这个方法里,扫描并注册了所有的mapper类。
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
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));
}
3、接下来看下ClassPathMapperScanner,这个扫描类,他继承了spring的扫描类ClassPathBeanDefinitionScanner,重写了它的doScan方法。
@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;
}
4、这里mybatis也是用了spring的扫描功能,然后调用了processBeanDefinitions这个方法,来处理自己的逻辑,看下这个方法的实现,方法进行了一些删减。
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
//1、注意此处,选择了只有一个参数的构造方法,
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
//2、注意此处,把原来的mapper的类的类型修改了,统一改成了MapperFactoryBean这个类
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);
if (!explicitFactoryUsed) {
LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
//3、注意此处,类的注入方法修改成了AUTOWIRE_BY_TYPE,此时只要有set方法,就会实现按类型进行spring的注入
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
//4、注意此处,lazyInitialization为false,在初始化时,进行赋值的
definition.setLazyInit(lazyInitialization);
}
}
此处把mapper的类统一修改成了MapperFactoryBean,我们看下它的实现
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
1)、它继承了SqlSessionDaoSupport,这个类里有下面这个方法,所以可以完成sqlSessionFactory的自动注入(可以看到上面代码第3点的说明)及sqlSessionTemplate的创建。
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
2)、实现了FactoryBean接口,这个接口上面说过,是注册bean的一个常用方法,当获取bean时,会调用这个的getObect方法,现在看下它的getObject方法。
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
这个是从那里获取的呢?我们找下根源,根据调用路径找到了下面的类,就是下面的 getMapper这个方法
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//删除了多余的代码
}
1)、从这里我们看到了熟悉的knownMappers 这个就是刚才在解析SqlSessionFactoryBean时,最后mapper.xml里信息存储的地方,现在在获取mapper类信息时,也最终到了这里, 从这里也就实现了mapper.xml和mapper类的汇聚。
2)、再接下来调用了mapperProxyFactory.newInstance(sqlSession);来返回对象,我们再看下这个方法
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
这个类的代码很少,都是干货。这个代码里,我们可以看到newInstance返回的是一个代码里,而代理方式就是jdk自带的动态代理的实现。这是返回的就是一个代理对象。
我们看下这个动态代码的调用的 invoke方法。
@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);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
1)、final MapperMethod mapperMethod = cachedMapperMethod(method);是获取mapper.xml里的解析出来的方法,
2)、mapperMethod.execute(sqlSession, args)调用并返回结果。