Spring(八)- Spring整合MyBatis框架原理剖析

一、Spring xml方式整合第三方框架

xml整合第三方框架有两种整合方案:
⚫ 不需要自定义命名空间,不需要使用Spring的配置文件配置第三方框架本身内容,例如:MyBatis
⚫ 需要引入第三方框架命名空间,需要使用Spring的配置文件配置第三方框架本身内容,例如:Dubbo

在这里插入图片描述

1. Spring整合MyBatis

(1)原始方式

需要mybatis-config.xml 配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="com.itheima.mapper"></package>
    </mappers>

</configuration>

编写Mapper和Mapper.xml

public interface UserMapper {
    List<User> findAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.UserMapper">
    <select id="findAll" resultType="com.itheima.pojo.User">
        select * from tb_user
    </select>
</mapper>

测试方法

public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = builder.build(in);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> all = mapper.findAll();
        for (User user : all) {
            System.out.println(user);
        }
    }
}

(2)引入mybatis-spring.jar方式

不再需要mybatis-config.xml文件
MyBatis提供了mybatis-spring.jar专门用于两大框架的整合。
Spring整合MyBatis的步骤如下:
⚫ 导入MyBatis整合Spring的相关坐标;
⚫ 编写Mapper和Mapper.xml;
⚫ 配置SqlSessionFactoryBean和MapperScannerConfigurer;
⚫ 编写测试代码

①导入MyBatis整合Spring的相关坐标

<dependency>
   <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.5</version>
</dependency>

②编写Mapper和Mapper.xml

public interface UserMapper {
    List<User> findAll();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.UserMapper">
    <select id="findAll" resultType="com.itheima.pojo.User">
        select * from tb_user
    </select>
</mapper>

③在applicationContext.xml中配置SqlSessionFactoryBean和MapperScannerConfigurer

<!--配置数据源--> 
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> 
	<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
	<property name="url" value="jdbc:mysql://localhost:3306/mybatis"></property> 
	<property name="username" value="root"></property> 
	<property name="password" value="root"></property>
</bean>
<!--配置SqlSessionFactoryBean,作用将SqlSessionFactory存储到spring容器-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean"> 
	<property name="dataSource" ref="dataSource"></property>
</bean>
<!--MapperScannerConfigurer,作用扫描指定的包,产生Mapper对象存储到Spring容器-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> 
	<property name="basePackage" value="com.itheima.dao"></property>
</bean>

④编写测试代码

public static void main(String[] args) {
	ClassPathxmlApplicationContext applicationContext = new ClassPathxmlApplicationContext("applicationContext.xml");
	UserMapper userMapper = applicationContext.getBean(UserMapper.class);
	List<User> all = userMapper.findAll();
	System.out.println(all);
}

2. Spring整合MyBatis的原理剖析

整合包里提供了一个SqlSessionFactoryBean和一个扫描Mapper的配置对象(MapperScannerConfigurer ),SqlSessionFactoryBean一旦被实例化,就开始扫描Mapper并通过动态代理产生Mapper的实现类存储到Spring容器中。相关的有如下四个类:
⚫ SqlSessionFactoryBean:需要进行配置,用于提供SqlSessionFactory;
⚫ MapperScannerConfigurer:需要进行配置,用于扫描指定mapper注册BeanDefinition;
⚫ MapperFactoryBean:Mapper的FactoryBean,调用getObject方法创建指定的Mapper;
⚫ ClassPathMapperScanner:definition.setAutowireMode(2) 修改了自动注入状态,所以MapperFactoryBean中的setSqlSessionFactory会自动注入进去。

SqlSessionFactoryBean源码:
配置SqlSessionFactoryBean作用是向容器中提供SqlSessionFactory,SqlSessionFactoryBean实现了FactoryBean和InitializingBean两个接口,所以会自动执行getObject() 和afterPropertiesSet()方法

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
  // spring生命周期中,属性填充后,会执行InitializingBean的afterPropertiesSet方法
  // SqlSessionFactoryBean重写了afterPropertiesSet方法,用于创建SqlSessionFactory对象存入容器中
  @Override
  public void afterPropertiesSet() throws Exception {
    this.sqlSessionFactory = buildSqlSessionFactory();
  }
  // 其他对象要引用SqlSessionFactory对象时,可以通过工厂方法getObject()从容器中获取SqlSessionFactory对象
  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }
}

MapperScannerConfigurer源码:
配置MapperScannerConfigurer作用是扫描Mapper,向容器中注册Mapper对应的MapperFactoryBean,MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,会在postProcessBeanDefinitionRegistry方法中向容器中注册MapperFactoryBean

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
    // 类加载路径下的Mapper扫描器
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    。。。
    // ClassPathMapperScanner中没有scan方法,所以调的是父类ClassPathBeanDefinitionScanner的scan方法
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
  }
  
}

ClassPathBeanDefinitionScanner 源码:

public class ClassPathBeanDefinitionScanner {
	// 步骤一:父类scan方法
	public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
		// 步骤二:这里会先执行子类的doScan方法
		doScan(basePackages);
		。。。
		return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
	}
	// 步骤四:父类doScan方法
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				。。。
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					// 步骤五:注册beanDefinition,definitionHolder其实就是将beanDefinition包装了一层
					// 将扫描到的类注册到beanDefinitionMap中,此时beanClass是当前类全限定名
					registerBeanDefinition(definitionHolder, this.registry);
				}
			}
		}
		return beanDefinitions;
	}
}

步骤五:definitionHolder其实就是将beanDefinition包装了一层
在这里插入图片描述
此时注册的是UserMapper接口的beanDefinition信息,然后将beanDefinition存入beanDefinitionMap中后续就可以通过spring创建对象了
在这里插入图片描述

ClassPathMapperScanner 源码:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
	// 步骤三:子类doScan方法
	public Set<BeanDefinitionHolder> doScan(String... basePackages) {
			// 步骤四:从子类中去调用父类doScan方法,获取BeanDefinitionHolder集合
			Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
			if (beanDefinitions.isEmpty()) {
				。。。
			} else {
				// 步骤六:处理扫描后的所有beanDefinitions定义信息
				this.processBeanDefinitions(beanDefinitions);
			}
	}
	// 步骤六:处理扫描后的所有Mapper接口的beanDefinitions定义信息
	private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
		 。。。
		// 设置Mapper的beanClass是org.mybatis.spring.mapper.MapperFactoryBean
		definition.setBeanClass(this.mapperFactoryBeanClass);
		// 步骤七:
		// autowireMode取值:1是根据名称自动装配,2是根据类型自动装配
		definition.setAutowireMode(2); 
		。。。
	}
}

步骤六:处理扫描后的所有Mapper接口的beanDefinitions定义信息
此时的beanClass=“com.itheima.mapper.UserMapper”,由于接口不能通过反射创建对象,所以需要通definition.setBeanClass(this.mapperFactoryBeanClass);将原有的"com.itheima.mapper.UserMapper"覆盖,设置为this.mapperFactoryBeanClass(mapperFactoryBeanClass实现了FactoryBean接口,所以会重写getObject方法)
@Override
public T getObject() throws Exception {
// 步骤八:底层调用的是mybatis源码通过sqlSession去创建Mapper对象
return getSqlSession().getMapper(this.mapperInterface);
}
// 步骤八:mybatis源码通过sqlSession去创建Mapper对象
public < T > T getMapper(Class< T> type, SqlSession sqlSession) {
MapperProxyFactory< T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
return mapperProxyFactory.newInstance(sqlSession);
}

在这里插入图片描述

步骤七:覆盖完beanClass后,此时被扫描的Mapperbean类型就是MapperFactoryBean类型,所以接下来上面的步骤八会去调用getObject方法创建Mapper对象,由于创建Mapper对象会用到sqlSession,所以需要先注入sqlSession对象,因此这里definition.setAutowireMode(2);是为了通过类型进行注入sqlSession对象
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值