Spring xml 方式整合MyBatis(含源码剖析)
整合主要是配好配置 SqlSessionFactoryBean 和 MapperScannerConfigurer。
Spring xml 方式整合MyBatis(含源码剖析)
一、概述
整合主要是配好配置 SqlSessionFactoryBean 和 MapperScannerConfigurer。
二、实验
2.1不整合之前
UserMapper:
package com.itheima.mapper;
import com.itheima.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> findAll();
}
UserServiceImpl:
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class UserServiceImpl implements UserService {
@Override
public void show() {
}
}
测试文件
package com.itheima.test;
import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
public class MyBatisTest {
public static void main(String[] args) throws Exception {
// 先是加载配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
// 再是得到sqlSessionFactroyBuilder
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 再是得到sqlSessionFactory
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(resourceAsStream);
// 再是得到sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 再是找到mapper
UserMapper userMapper =sqlSession.getMapper(UserMapper.class);
// 然后调用方法
List<User> all = userMapper.findAll();
// 遍历输出
for (User user :all){
System.out.println(user);
}
}
}
2.2整合之后
UserServiceImpl:
package com.itheima.service.impl;
import com.itheima.dao.UserDao;
import com.itheima.mapper.UserMapper;
import com.itheima.pojo.User;
import com.itheima.service.UserService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class UserServiceImpl implements UserService {
//需要UserMapper就注入
private UserMapper userMapper;
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public void show() {
List<User> all = userMapper.findAll();
for (User user :all){
System.out.println(user);
}
}
}
applicationContext.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring"/>
<property name="username" value="root"/>
<property name="password" value="guangxun.kg"/>
</bean>
<!-- 作用是将SqlSessionFactory存储到Spring容器中-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 最终是要得到由Sqlsession得到Conection,需要数据源属性-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配给接口扫描,目的是扫描接口,将相应的mapper接口存储到Spring容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定要扫描的包-->
<property name="basePackage" value="com.itheima.mapper"></property>
</bean>
<bean class="com.itheima.service.impl.UserServiceImpl" id="userService">
<!-- 需要Mapper属性就注入,因为上面的MapperScannerConfigurer已经将mapper扫描且添加到spring容器当中了
不需要再配置了-->
<property name="userMapper" ref="userMapper"/>
</bean>
</beans>
测试:
package com.itheima.test;
import com.alibaba.druid.pool.DruidDataSource;
import com.itheima.beans.OtherBean;
import com.itheima.beans.XxxBean;
import com.itheima.dao.PersonDao;
import com.itheima.dao.UserDao;
import com.itheima.service.UserService;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ApplicationContextTest {
public static void main(String[] args) throws Exception {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = applicationContext.getBean(UserService.class);
// System.out.println(/userService);
userService.show();
}
}
三、原理剖析
3.1SqlSessionFactoryBean
首先在使用上我们是谁要使用SqlSessionFactory就去调用SqlSessionFactory的getObject方法,下面将从阐述理由(温馨提示:看源码时不要什么都看,只看自己需要的即可,不然会很乱):
首先进入SqlSessionFactoryBean的源码:
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>
从开头我们就知道SqlSessionFactoryBean实现了FactoryBean、InitializingBean,那么就必然有它们的方法,我们先来看InitializingBean,我们知道InitializingBean是用来进行初始化化的,它的方法只有一个:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory;
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
这个方法是在bean的实例化结束、postProcessBeforeInitialization结束,postProcessAfterInitialization执行之前执行的,实际上就是进行初始化。
当然从源码中我们也可以得知afterPropertiesSet()是在属性填充之前执行,因为setDataSource方法先执行,注意看行号:
从上面这张图片我们可以得知afterPropertiesSet方法执行了**“this.sqlSessionFactory = this.buildSqlSessionFactory();”**,紧接着我们看buildSqlSessionFactory()方法(主要是看它的返回值)
我们可以清晰地看到buildSqlSessionFactory()的返回值是:”return this.sqlSessionFactoryBuilder.build(targetConfiguration);“,从Mybatis的客户端代码我们可以得知sqlSessionFactoryBuilder.build()方法返回的是一个SqlSessionFactory对象。
然后是再回到**“this.sqlSessionFactory = this.buildSqlSessionFactory();”**,我们就知道afterPropertiesSet()已经将sqlSessionFactory赋好值,完成初始化。
然后是再来看FactoryBean:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.beans.factory;
import org.springframework.lang.Nullable;
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
它有一个getObject()方法,SqlSessionFactoryBean实现如下:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
this.afterPropertiesSet();
}
return this.sqlSessionFactory;
}
所以结论得证:谁要使用SqlSessionFactory就去调用SqlSessionFactory的getObject方法。
3.2MapperScannerConfigurer
首先是可以看到MapperScannerConfigurer是实现了BeanDefinitionRegistryPostProcessor, InitializingBean等接口
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware
那么我们就继续分析MapperScannerConfigurer实现的方法,先看实现的InitializingBean的afterPropertiesSet方法:
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.basePackage, "Property 'basePackage' is required");
}
可以看到它只是实现了一个断言,并没有多大的分析意义。
那么就继续看实现的BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
this.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(this.lazyInitialization)) {
scanner.setLazyInitialization(Boolean.valueOf(this.lazyInitialization));
}
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
}
可以看到在postProcessBeanDefinitionRegistry方法中是创建了ClassPathMapperScanner,且是调用了scan方法,那我们就来分析一下ClassPathMapperScanner这个类:
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 {
this.processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
我们发现它只有doscan方法并没有scan方法,但是我们发现它有一个父类:
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner
它的父类有scan,那么就是调用它的父类的scan方法了:
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
this.doScan(basePackages);
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return this.registry.getBeanDefinitionCount() - beanCountAtScanStart;
}
在它的父类方法中又去调用doScan方法,由于其子类有了doScan方法,所以是调用它的子类的doScan方法,在它的子类中我们可以看到这样一行代码:
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
子类又去调用它父类的doScan方法:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet();
for(String basePackage : basePackages) {
for(BeanDefinition candidate : this.findCandidateComponents(basePackage)) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
this.postProcessBeanDefinition((AbstractBeanDefinition)candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition)candidate);
}
if (this.checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
this.registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
我们主要看这行:
this.registerBeanDefinition(definitionHolder, this.registry);
可以知道只是将BeanDefinition注册到BeanDefinitionMap中,实际上definitionHolder就是将BeanDefinition再包一层。最后将所有的BeanDefinition注册好后,返回beanDefinitions的集合给子类。
然后子类 ClassPathMapperScanner去调用processBeanDefinitions方法:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
for(BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName + "' mapperInterface");
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
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(2);
}
definition.setLazyInit(this.lazyInitialization);
}
}
可以看到里面执行了**definition.setBeanClass(this.mapperFactoryBeanClass);**这段代码,就是将当前的BeanClass进行覆盖了,因为当前的BeanClass是Mappper接口,不是对象。紧接着看到参数列表的mapperFactoryBeanClass,继续挖:
private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;
发现它又等于MapperFactoryBean.class,那就挖:
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T>
我们发现MapperFactoryBean实现了FactoryBean,那就必然有getObject方法:
public T getObject() throws Exception {
return (T)this.getSqlSession().getMapper(this.mapperInterface);
}
到这里恍然大悟,这逻辑不是和Mybatis的客户端代码一样吗?
紧接着继续分析,ClassPathMapperScanner的processBeanDefinitions方法有这样一段代码definition.setAutowireMode(2);,见名知意就是设置自动注入,其中“2”是代表按照类型自动注入。
四、小结
至此,Spring xml 方式整合MyBatis具体操作、原理剖析就全部完成了,让我们进行一个总结吧:
Spring 整合 MyBatis 的原理剖析:
整合包里提供了一个 SqlSessionFactoryBean 和一个扫描 Mapper 的配置对象,SqlSessionFactoryBean 一旦被实例化,就开始扫描 Mapper 并通过动态代理产生 Mapper 的实现类存储到 Spring 容器中。相关的有如下四个类:
-
SqlSessionFactoryBean:需要进行配置,用于提供 SqlSessionFactory;
-
MapperScannerConfigurer:需要进行配置,用于扫描指定 mapper 注册 BeanDefinition;
-
MapperFactoryBean:Mapper 的 FactoryBean,获得指定 Mapper 时调用 getObject 方法;
-
ClassPathMapperScanner:definition.setAutowireMode (2) 修改了自动注入状态,所以 MapperFactoryBean 中的 setSqlSessionFactory 会自动注入进去。
拓展:
撒花:
^ <
✿✿✿✿✿✿✿✿✿✿✿✿✿✿✿
✿✿✿✿✿✿✿✿✿✿✿✿✿✿✿
✿✿✿✿✿✿✿✿✿✿✿✿✿✿✿
昇腾计算产业是基于昇腾系列(HUAWEI Ascend)处理器和基础软件构建的全栈 AI计算基础设施、行业应用及服务,https://devpress.csdn.net/organization/setting/general/146749包括昇腾系列处理器、系列硬件、CANN、AI计算框架、应用使能、开发工具链、管理运维工具、行业应用及服务等全产业链
更多推荐



所有评论(0)