Spring-Mybatis框架使用与源码解析

前言:

    在上一篇文章 https://blog.csdn.net/qq_26323323/article/details/81335058 中,我们说了使用mybatis有三种方式, 而上篇文章介绍了关于使用原生Mybatis的源码解析。

    实际在常规项目开发中,大部分都会使用mybatis与Spring结合起来使用,毕竟现在不用Spring开发的项目实在太少了。

    那么该篇文章便来介绍下Mybatis如何与Spring结合起来使用,并介绍下其源码是如何实现的。建议读者先看下上篇文章,对mybatis原生情况下的使用有一定的了解

 

1.Spring-Mybatis使用

    1)添加maven依赖

		
		    org.mybatis
		    mybatis
     		3.2.8
		
	
		
		    org.projectlombok
		    lombok
		    1.18.2
		    provided
		
		
			mysql
			mysql-connector-java
            5.1.32
		
		
		
		
		    org.mybatis
		    mybatis-spring
 		    1.3.2
		
		
		
		    org.springframework
		    spring-jdbc
		    4.3.8.RELEASE
		
		

    2)在src/main/resources下添加spring-mybatis-config.xml文件




	
		
	
	
		
	

   在src/main/resources/mapper路径下添加User.xml



    

    

    在src/main/resources/路径下添加beans.xml




	
		
		
		
		
	

	
		
		
	

	
		
		
	

     3)添加User.java

package cn.itcast.springmvc.bean;
import org.springframework.stereotype.Component;
import lombok.Data;

@Data
@Component
public class User {
	private int id;
	private String name;
	private String dept;
	private String phone;
	private String website;
}

    4)添加IUser.java

public interface IUser {
    public User getUser(int id);
}

    5)测试类

public class SpringMybatisTest {

	public static void main(String[] args) {
		ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
		IUser userDao = (IUser)ac.getBean("userDao");
		User user = userDao.getUser(3);
		System.out.println(user);//User(id=3, name=jack, dept=devp, phone=xxx, website=www.baidu.com)
	}
}

    测试结果正确。

    通过以上的过程可知,Mybatis与Spring结合的最重要的配置就是beans.xml。

    beans.xml将dataSource、SQLSessionFactory及IUser注入到Spring容器中,然后再使用的时候获取到对应的IUser,然后进行CRUD操作

 

2.分析beans.xml文件

    1)通过对原生Mybatis使用过程的分析,我们知道其主要过程可以分为:

    * 解析spring-mybatis-config.xml,生成Resource

    * 使用SqlSessionFactoryBuilder创建SqlSessionFactory

    * 使用SqlSessionFactory创建SqlSession

    * 从SqlSession中获取Mapper接口(也就是本例中的IUser接口)

    * 使用Mapper接口进行CRUD操作

    

    注意:上篇文章中讲的示例是直接使用SqlSession.selectOne()来进行查询操作的;

    实际还有另一种更面向对象的查询方式,就是从SqlSession.getMapper()中获取Mapper接口,然后通过Mapper接口来进行查询操作

    Spring-Mybatis使用的就是这种方式

 

    2)对照Spring-Mybatis的方式,也就是对照beans.xml文件来看

	
		
		
	

    就对应着SqlSessionFactory的生成,类似于原生Mybatis使用时的以下代码

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

    而以下userDao的获取

	
		
		
	

    就对应着Mapper接口的获取,类似于原生Mybatis使用时的以下代码:

SqlSession session = sqlSessionFactory.openSession();
IUser mapper = session.getMapper(IUser.class);

    总结:所以我们现在就主要分析下在Spring中是如何生成SqlSessionFactory和Mapper接口的

 

 

3.Spring中sqlSessionFactory的生成

    由beans.xml中的配置可知,其实现类为org.mybatis.spring.SqlSessionFactoryBean ,并定义了属性configLocation和dataSource,下面我们就来看下

SqlSessionFactoryBean 这个类

public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {
  private Resource configLocation;
  private Resource[] mapperLocations;
  private DataSource dataSource;
  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
  private SqlSessionFactory sqlSessionFactory;
  ...
}

    SqlSessionFactoryBean实现了FactoryBean接口,有关于FactoryBean的使用,读者可自行查询下,笔者不再赘述

    那我们从容器中获取SqlSessionFactoryBean的时候,实际是调用其getObject()方法,该方法的返回值才是容器真正给我们生成的对象,下面我们来看下该方法

 

    1)SqlSessionFactoryBean.getObject()

  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      // sqlSessionFactory默认为空,直接走afterPropertiesSet()方法
      // 实际不是这样的,由于SqlSessionFactoryBean实现了InitializingBean,则再该bean生成之后,
      // 会直接调用afterPropertiesSet()方法,来创建sqlSessionFactory,故sqlSessionFactory应该
      // 是已经被创建好的
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }

// afterPropertiesSet()
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");

    this.sqlSessionFactory = buildSqlSessionFactory();//关键方法
  }


// buildSqlSessionFactory()
  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configLocation != null) {
      // 1.解析spring-mybatis-config.xml配置
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      // 2.获取配置中心Configuration
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      configuration.setVariables(this.configurationProperties);
    }

    // 3.以下的操作就是对Configuration中的各种属性进行set操作
    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }

    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

    if (hasLength(this.typeAliasesPackage)) {
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        if (logger.isDebugEnabled()) {
          logger.debug("Scanned package: '" + packageToScan + "' for aliases");
        }
      }
    }

    ...

    Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
    configuration.setEnvironment(environment);

    ...
    // 4.最终还是通过sqlSessionFactoryBuilder来生成sqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

    总结:通过以上代码的分析,可知,SqlSessionFactoryBean在容器中最终返回的就是通过sqlSessionFactoryBuilder.build(configuration)返回的SqlSessionFactory,与我们使用原生方式获取SqlSessionFactory没有什么差别

 

4.Spring中Mapper接口(也就是本例中的userDao实例,对应着IUser接口)

 

    由beans.xml中的配置可知,其实现类为org.mybatis.spring.mapper.MapperFactoryBean,并且定义了属性mapperInterface和sqlSessionFactory。

    注意:mapperInterface需要指定我们的IUser接口全路径

 

    1)MapperFactoryBean

public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {

  private Class mapperInterface;

}

    类似于SqlSessionFactoryBean,MapperFactoryBean也实现了FactoryBean接口,那容器真正返回给我们的同样也是getObject()返回的对象。

 

    2)MapperFactoryBean.getObject()

  public T getObject() throws Exception {
    // getSqlSession()返回的是SqlSessionDaoSupport的sqlSession属性
    // 那么这个属性是什么时候被赋值的呢?
    // 读者可自行思考下
    return getSqlSession().getMapper(this.mapperInterface);
  }

    注意:MapperFactoryBean在Spring中真正返回的也就是SqlSession.getMapper()方法中返回的Mapper接口

    问题:那么Mapper接口是什么时候被放入Configuration的呢?

    通过分析MapperFactoryBean的结构可知,其也实现了InitializingBean接口,那么在该bean加载进Spring的时候,也会在初始化的时候自动调用其afterPropertiesSet()方法,我们来看下这个方法

 

    3)MapperFactoryBean.afterPropertiesSet()

    具体实现在DaoSupport类中

	public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// 该方法在MapperFactoryBean中有具体实现
		checkDaoConfig();

		// Spring没有关于其的实现,作者可自行实现
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}

    MapperFactoryBean.checkDaoConfig()

  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        // 核心代码在这里,主要是将该Mapper接口添加进Configuration,以便后面使用
        configuration.addMapper(this.mapperInterface);
      } catch (Throwable t) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
        throw new IllegalArgumentException(t);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

    总结:

    MapperFactoryBean初始化的时候就通过afterPropertiesSet()方法把Mapper接口添加进Configuration;

    MapperFactoryBean.getObject()返回的也就是从Configuration中获取的Mapper接口。

    整个MapperFactoryBean.getObject()方法类似于原生Mybatis中

SqlSession session = sqlSessionFactory.openSession();
IUser mapper = session.getMapper(IUser.class);

 

总结:    

    通过以上对Spring-Mybatis的源码分析,可知,其本质还是Mybatis。

    只不过,容器帮我们生成了SqlSessionFactory和SqlSession,同时把Mapper加载进Configuration,可以让我们直接以getBean()的方式来获取Mapper

 

 

你可能感兴趣的:(Mybatis,Mybatis源码解析)