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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_26323323/article/details/81357549

前言:

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

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

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

1.Spring-Mybatis使用

    1)添加maven依赖

		<dependency>
		    <groupId>org.mybatis</groupId>
		    <artifactId>mybatis</artifactId>
     		<version>3.2.8</version>
		</dependency>
	
		<dependency>
		    <groupId>org.projectlombok</groupId>
		    <artifactId>lombok</artifactId>
		    <version>1.18.2</version>
		    <scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
		</dependency>
		
		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
		<dependency>
		    <groupId>org.mybatis</groupId>
		    <artifactId>mybatis-spring</artifactId>
 		    <version>1.3.2</version>
		</dependency>
		
		<dependency>
		    <groupId>org.springframework</groupId>
		    <artifactId>spring-jdbc</artifactId>
		    <version>4.3.8.RELEASE</version>
		</dependency>
		

    2)在src/main/resources下添加spring-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>
	<typeAliases>
		<typeAlias alias="User" type="cn.itcast.springmvc.bean.User" />
	</typeAliases>
	<mappers>
		<mapper resource="mapper/User.xml" />
	</mappers>
</configuration>

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

<?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="cn.itcast.springmvc.dao.IUser">
    <select id="getUser" parameterType="int"
        resultType="cn.itcast.springmvc.bean.User">
        SELECT *
        FROM USER
        WHERE id = #{userId}
    </select>
</mapper>

    在src/main/resources/路径下添加beans.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="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"></property>
		<property name="username" value="root"></property>
		<property name="password" value="root"></property>
	</bean>

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:spring-mybatis-config.xml"></property>
		<property name="dataSource" ref="dataSource" />
	</bean>

	<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
		<property name="mapperInterface" value="cn.itcast.springmvc.dao.IUser"></property>
		<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
	</bean>
</beans>

     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文件来看

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:spring-mybatis-config.xml"></property>
		<property name="dataSource" ref="dataSource" />
	</bean>

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

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

    而以下userDao的获取

	<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
		<property name="mapperInterface" value="cn.itcast.springmvc.dao.IUser"></property>
		<property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
	</bean>

    就对应着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<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
  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<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> 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

猜你喜欢

转载自blog.csdn.net/qq_26323323/article/details/81357549