进阶篇---Mybatis与Spring整合源码阅读


前言

大家都知道Spring中mybatis的调用是接口形式,那么SpringIOC是不会注入接口形式的bean。那么它是怎么和Mybatis进行集成的呢?
本篇需要对SpringIOC的源码有一定的了解才可以,否则是不太好理解的

本文所用的代码:

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
</configuration>

User类:

@Component
public class User {

    private Long id ;
    private String userName ;
    private Date createTime;

	public User() {
		//System.out.println("user加载");
	}

	public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", createTime=" + createTime +
                '}';
    }
}

UserMapper接口
在这里插入图片描述

public interface UserMapper {
    User selectById(Long id);

    List<User> selectAllUser();
}

配置类

@Configuration
@MapperScan(basePackages = {"com.tuling.mapper"})
@ComponentScan(basePackages = {"com.tuling"})
@Repository
public class MyBatisConfig {    // =====>   spring.xml


	/**
	  创建的过程会初始化  sqlsession 数据源  事务  mapperProxyFactory
	 * @return
	 * @throws IOException
	 */
	@Bean    // =====  >    <bean  class="org.mybatis.spring.SqlSessionFactoryBean">
	public SqlSessionFactoryBean sqlSessionFactory( ) throws IOException {
		SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
		factoryBean.setDataSource(dataSource());
		// 设置 MyBatis 配置文件路径
		factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
		// 设置 SQL 映射文件路径
		factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
		factoryBean.setTypeAliases(User.class);
		return factoryBean;
	}

    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis_example");
        return dataSource;
    }
}

调用:

public class MainStarter {

    public static void main(String[] args) {
    	// 记载spring上下文
        AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MyBatisConfig.class);
		UserMapper bean = (UserMapper) ioc.getBean("userMapper");
        System.out.println(bean.selectById(1L));
	}
}

一、SqlSessionFactoryBean

之前在SSM框架中我们在配置文件里有过

<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
        <!-- 指定spring中的数据源 -->
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath:cn/tulingxueyuan/mapper/*.xml"></property>
 </bean>

当ioc容器加载完bean定义后,配置文件中的

@Bean    // =====  >    <bean  class="org.mybatis.spring.SqlSessionFactoryBean">
	public SqlSessionFactoryBean sqlSessionFactory( ) throws IOException {
		SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
		factoryBean.setDataSource(dataSource());
		// 设置 MyBatis 配置文件路径
		factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
		// 设置 SQL 映射文件路径
		factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
		factoryBean.setTypeAliases(User.class);
		return factoryBean;
	}

SqlSessionFactoryBean这个类我们看下
在这里插入图片描述
实现了这个接口的类,之前说过会在初始化调用他的

void afterPropertiesSet() throws Exception;

在这里插入图片描述
在这里插入图片描述
中间省略一些,在这里可以对mybatis进行一些配置

在这里插入图片描述

解析我们的像UserMapper文件

/**
 * 通过建造者模式构建我们的SqlSessionFactory对象 默认是DefaultSqlSessionFactory
 */
 return this.sqlSessionFactoryBuilder.build(targetConfiguration);

以便后续mybitis进行调用

在这里插入图片描述
最后存到Configuration对象里去,后续执行数据库操作的东西都有了。

和声明式事务的集成

在这里插入图片描述
当我们项目中存在这样代码时,mybitis和Spring是怎么做的呢?

开启事务是在动态代理中去开启,开启事务时就会获得Connection对象,会放到事务同步管理器的
在这里插入图片描述
这里使用了TheadLocal进行了存储,本地线程副本,mybatis在进行操作数据库时,只要拿到这个Connection就行了。比如说Spring去回滚,mybatis只要拿到Connection就算是集成上了。

那我们看下源码,afterPropertiesSet方法
在这里插入图片描述
从这里面去获得数据源
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在构建SqlSessionFactoryBean会new一个mybatis-spring适配的新的事务工厂。mybatis获得Connection时就会从这里获取

二、Mapper接口是如何和Spring进行集成的?

1.自动注入的Mapper接口是怎样进行集成的?

代码如下:

在这里插入图片描述在这里插入图片描述
Mapper接口底层实现是jdk动态代理。
Mybatis源码
在这里插入图片描述
最后来到这里
在这里插入图片描述

2.怎么实现的呢?

给大家看个例子

代码如下(示例):

@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 并不是直接注册动态代理, 而是注册FactoryBean

        // 扫描所有mapper接口{   所有mapper接口类
        GenericBeanDefinition beanDefinition=new GenericBeanDefinition();
        beanDefinition.setBeanClass(MyFactoryBean.class);

        //传 UserMapper.class到MyFactoryBean的mapperInterFace属性里
        beanDefinition.getPropertyValues().addPropertyValue("mapperInterFace", UserMapper.class);

         registry.registerBeanDefinition("tuserMapper",beanDefinition);


        // 扫描所有mapper接口 }

    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

public class MyFactoryBean implements FactoryBean {

    public Class getMapperInterFace() {
        return mapperInterFace;
    }

    public void setMapperInterFace(Class mapperInterFace) {
        this.mapperInterFace = mapperInterFace;
    }

    // 动态传进来
    private Class mapperInterFace;

    @Override
    public Object getObject() throws Exception {

		System.out.println("进入factoryBean的getObject方法");
        return (UserMapper) Proxy.newProxyInstance(mapperInterFace.getClassLoader(), new Class[]{mapperInterFace}, (proxy, method, args1) -> {
            // 查询数据库...   mybatis
            System.out.println("查询");
            return null;
        });
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterFace;
    }

    public MyFactoryBean() {
        System.out.println("加载");
    }
}

这就是简化版的调用,源码里也是这么调用的


讲解:
因Usermapper是接口,所以需要重写Spring的isCandidateComponent方法,
在这里插入图片描述
拿到bean定义修改beanclass
在这里插入图片描述
那么就可以用FactoryBean进行偷天换日了。那么看源码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过builder注册了

void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {

    /**
     * 创建bean定义构造器 通过够构造器来构建出我们的bean定义<MapperScannerConfigurer> 应用到的设计模式[建造者模式]
     */
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);

  }

和mybatis进行集成,MapperScannerConfigurer类
在这里插入图片描述
会来到这个方法在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
调用scan方法
在这里插入图片描述

在这里插入图片描述
调用父类方法
在这里插入图片描述
注意看这个方法
进行了重写在这里插入图片描述
在这里插入图片描述
判断的是只有接口类型的并且是顶级的,加入到集合中,注意和Spring是不同的在这里插入图片描述
在这里插入图片描述
扫描到了UserMapper接口
在这里插入图片描述
在这里插入图片描述
进入这个方法,创建动态代理要指定为哪个类创建

definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName)

在这里插入图片描述

进行了偷天换日,我们看下mapperFactoryBeanClass
在这里插入图片描述
最终我们实例化UserMapper就是我们的MapperFactoryBean类型。然后会为我们的UserMapper绑定个属性sqlSessionTemplate,因为毕竟要和数据库进行交互。
在这里插入图片描述

实际上是就是为我们的MapperFactoryBean添加一个sqlSessionTemplate的属性)
然后SpringIoc在实例话我们的MapperFactoryBean的时候会经历populate()方法为UserMapper(MapperFactoryBean)
的sqlSessionTemplate赋值(调用set方法),这个是根据byType自动装配的

在这里插入图片描述

在这里插入图片描述
最终会在mybatis里创建动态代理
在这里插入图片描述
工厂bean的返回结果:
在这里插入图片描述
之后的在这里插入图片描述
查询数据库的功能就可以交给mybatis去做了,当我selectOne时,在这里插入图片描述

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      /**
       * 判断我们的方法是不是我们的Object类定义的方法,若是直接通过反射调用
       */
      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);
    }
    /**
     * 真正的进行调用,做了二个事情
     * 第一步:把我们的方法对象封装成一个MapperMethod对象(带有缓存作用的)
     */
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    /**
     *通过sqlSessionTemplate来调用我们的目标方法
     * 那么我们就需要去研究下sqlSessionTemplate是什么初始化的
     * 我们知道spring 跟mybatis整合的时候,进行了偷天换日
     * 把我们mapper接口包下的所有接口类型都变为了MapperFactoryBean
     * 然后我们发现实现了SqlSessionDaoSupport,我们还记得在整合的时候,
     * 把我们EmployeeMapper(案例class类型属性为MapperFactoryBean)
     * 的注入模型给改了,改成了by_type,所以会调用SqlSessionDaoSupport
     * 的setXXX方法进行赋值,从而创建了我们的sqlSessionTemplate
     * 而在实例化我们的sqlSessionTemplate对象的时候,为我们创建了sqlSessionTemplate的代理对象
     *     this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
            new Class[] { SqlSession.class }, new SqlSessionInterceptor());
     */
    return mapperMethod.execute(sqlSession, args);

在这里插入图片描述
来到了mybatis的selectOne的方法了。在这里插入图片描述
至此Spring就集成了mybatis。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值