1.创建 Maven 工程。
2.添加依赖,代码如下
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7-ybe</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.6-ybe</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.3.16</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.8</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.20.RELEASE</version> </dependency>3.添加实体如下,
package com.ybe.entity;import java.io.Serializable;public class Book implements Serializable { int id; double price; public int getId() { return id; } public void setId(int id) { this.id = id; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; }}4.添加 Mapper接口以及BookMapper.xml文件,
public interface BookMapper { Book getBook(@Param("id") int id);}<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.ybe.mapper.BookMapper"> <cache></cache> <select id="getBook" resultType="com.ybe.entity.Book"> select * from book where id = #{id} </select></mapper>5.添加 BookService 和 BookServiceImpl代码如下,
package com.ybe.service;import com.ybe.entity.Book;public interface BookSerivce { Book getBook(int id);}package com.ybe.service.impl;import com.ybe.entity.Book;import com.ybe.mapper.BookMapper;import com.ybe.service.BookSerivce;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;@Componentpublic class BookServiceImpl implements BookSerivce { @Autowired BookMapper bookMapper; public Book getBook(int id) { return bookMapper.getBook(id); }}6.添加配置类,代码如下
package com.ybe.config;@Configuration@MapperScan(basePackages = {"com.ybe.mapper"})@ComponentScan(basePackages = {"com.ybe"})public class MyBatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml")); factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:com/ybe/mapper/*.xml")); factoryBean.setTypeAliases(Book.class); return factoryBean; } @Bean public DataSource dataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.setUsername("xxx"); dataSource.setPassword("xxx"); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/aopTest?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8"); return dataSource; } @Bean public DataSourceTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }}7.添加主类代码,代码如下
AnnotationConfigApplicationContext configApplicationContext = new AnnotationConfigApplicationContext(MyBatisConfig.class);BookSerivce bookSerivce = configApplicationContext.getBean(BookSerivce.class);Book book = bookSerivce.getBook(1);System.out.println(book.getId());? 因为Mybatis中使用的是Mapper.class 接口来找到数据库sql语句,并且是通过SqlSessionFactory的SqlSession来连接数据库和执行Sql语句的。所以Mybatis和Spring的整合,其实就是把Mybatis的SqlSessionFactory类和Mapper.Class接口注入到SpringIOC中。并且SqlSessionFactory类中的事务管理对象(SpringManagedTransactionFactory )会集成Spring的事务。
? 整个整合的过程分为两部分,第一部分 Mapper接口注入;第二部分 SqlSessionFactoryBean 注入。
? 试想一下我们在写Mapper接口的时候并没有写实现类,只是写了Mapper.xml文件。那在注入到Spring容器中,具体的实现类是啥?我这里直接给答案,Mapper接口在Spring容器中对应的实现类是一个MapperFactoryBean
? 通过配置@MapperScan(basePackages = {"com.ybe.mapper"}) 注解,向 BeanDefinitionRegistry 中添加类型为 MapperScannerConfigurer 的BeanDefinition对象并且初始化对象相关属性,在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors 中会进行调用MapperScannerConfigurer 的postProcessBeanDefinitionRegistry 方法,该方法会扫描配置的包路径下的Mapper接口class文件,生成BeanClass为MapperFactoryBean的ScannedGenericBeanDefinition,注册到 BeanDefinitionRegistry 中。
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)@Documented@Import(MapperScannerRegistrar.class)@Repeatable(MapperScans.class)BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);registry.registerBeanDefinition(beanName, builder.getBeanDefinition());MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口。MapperScannerConfigurer主要用来扫描具体的 Mapper接口class文件,生成BeanClass类型为MapperFactoryBean的ScannedGenericBeanDefinition对象后注入 BeanDefinitionRegistry 中。具体类图如下
在org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors中会接着执行,MapperScannerConfigurer的postProcessBeanDefinitionRegistry 方法。postProcessBeanDefinitionRegistry中大概逻辑为
搜索指定包下面的Mapper接口,
// 开始搜索 basePackagescanner.scan( StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));生成ScannedGenericBeanDefinition,并以Mapper接口名称为BeanName注入到BeanDefinitionRegistry对象中。
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);registerBeanDefinition(definitionHolder, this.registry);然后再设置ScannedGenericBeanDefinition的BeanClass类型为MapperFactoryBean类。 关键代码代码如下
// 设置 definition 的 构造函数的参数值类型为 beanClassName,// 在创建 MapperFactoryBean 时,会根据beanClassName创建类,然后把类作为参数调用MapperFactoryBean带参数的构造方法definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59// 设置 definition 的 Bean 类型为 MapperFactoryBean.classdefinition.setBeanClass(this.mapperFactoryBeanClass);每个Mapper接口文件对应的BeanDefinition为 ScannedGenericBeanDefinition,BeanDefinition的BeanClass实现类为 MapperFactoryBean 类。此时查看beanFactory的 beanDefinitionMap 中的值,如下图
MapperFactoryBean是一个泛型类,泛型用来表示不同接口类型。继承了SqlSessionDaoSupport类,该类中存储了Maybatis的SqlSession工厂类。MapperFactoryBean也是一个实现了FactoryBean
@Overridepublic T getObject() throws Exception { // 返回根据接口类型返回 SqlSession中的Mapper代理对象 return getSqlSession().getMapper(this.mapperInterface);}/** * {@inheritDoc} */@Overridepublic Class<T> getObjectType() { return this.mapperInterface;}在beanFactory.preInstantiateSingletons()方法中会把BeanDefinition生成具体的Bean对象,在创建 MapperFactoryBean 对象的时候会调用带参数的构造方法(上面有具体说明)。因为在配置类中我们注入了SqlSessionFactoryBean对象(具体解析过程在下面章节讲解),SqlSessionFactoryBean对象实现了FactoryBean
在获取Mapper接口的Bean对象的时候,会调用getObject()方法,其中会调SqlSessionTemplate的getMapper(),代码如下
@Overridepublic <T> T getMapper(Class<T> type) { return getConfiguration().getMapper(type, this);}public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 根据Mapper接口类型获取已经注册的对象 return mapperRegistry.getMapper(type, sqlSession);}@SuppressWarnings("unchecked")public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 根据 Mapper 接口类型获取已经注册的对象 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); }}通过@Bean方式可以将SqlSessionFactoryBean对象注入到Spring容器,SqlSessionFactoryBean对象在MapperFactoryBean对象中会用到。注入代码如下,
@Beanpublic SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws IOException { // 创建 SqlSessionFactoryBean 的类 SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); // 设置数据源 factoryBean.setDataSource(dataSource); // 设置配置文件路径 factoryBean.setConfigLocation(new ClassPathResource("mybatis.xml")); // 设置 mybaits.xml 文件 factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources("classpath:com/ybe/mapper/*.xml")); // 设置别名 factoryBean.setTypeAliases(Book.class); return factoryBean;}SqlSessionFactoryBean的类继承关系图如下,
SqlSessionFactoryBean实现了InitializingBean接口 ,会在初始化后会执行afterPropertiesSet方法,其中会调用buildSqlSessionFactory()方法进行SqlSessionFactory的创建。SqlSessionFactoryBean也实现了FactoryBean
@Overridepublic SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory;}? 1. 根据 configLocation 创建 XMLConfigBuilder 以及 Configuration对象
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);targetConfiguration = xmlConfigBuilder.getConfiguration();? 2. 读取SqlSessionFactoryBean的属性对象给 targetConfiguration 赋值
Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);if (hasLength(this.typeAliasesPackage)) { scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream() .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface()) .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);}......? 3. 解析主配置文件
xmlConfigBuilder.parse();? 4. targetConfiguration设置环境变量,如果配置的transactionFactory 事务工厂类为 null,则创建 SpringManagedTransactionFactory 事务工厂类,该事务工厂会直接调用org.springframework.jdbc.datasource.DataSourceUtils去获取数据库连接对象,所以和Spring的事务进行了集成。代码如下,
// 设置环境变量,如果事务工程类为 null,则创建 SpringManagedTransactionFactory 事务工厂类targetConfiguration.setEnvironment(new Environment(this.environment,this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,this.dataSource));? 5. 如果 mapperLocations 不为null ,则循环遍历 xxxMapper.xml 文件流,解析之后给 targetConfiguration 的相关对象赋值。代码如下,
for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { // 构建 XMLMapperBuilder 对象,进行mapper.xml 文件资源解析 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 + "'");}