Spring整合mybatis源码分析

在解析源码之前,我们需要知道Mybatis如何独立使用

  1. 简历POJO
    public class User(){
        private String name;
        private String age;
        //省略set get
    }
  2. 建立Mapper
    public Integerface UserMapper{
        public void insertUser(User user);
        public User getUser(Integer id);
    }
  3. 建立配置文件
      <?xml version="1.0" encoding="UTF-8"?>  
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDSQL Map Config 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-config.dtd">  
    <configuration>  
       
        <!-- 引入外部资源配置文件 -->  
        <properties resource="jdbc.properties" />  
       
        <!-- 配置环境,制定数据库连接信息 -->  
        <environments default="development">  
            <environment id="development">  
                <transactionManager type="JDBC" />  
                <dataSource type="POOLED">  
                    <property name="driver" value="${jdbc.driver}"/>  
                    <property name="url" value="${jdbc.url}"/>  
                    <property name="username" value="${jdbc.username}"/>  
                    <property name="password" value="${jdbc.password}"/>  
                </dataSource>  
            </environment>  
        </environments>  
       
        <mappers>  
            <mapper resource="com/test/dao/UserMapper.xml"/>  
        </mappers>  
       
    </configuration>  


  4. 建立映射文件
    <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDSQL Map Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <mapper namespace="Mapper.UserMapper">     <insert id="insertUser" parameterType="User">    INSERT INTO USER(NAME,AGE) VALUES(#{name},#{age})</insert>    <select id="getUser" resultType="User" parameterType="java.lang.Integer">    select * from user where id=#{id}</select></configuration>
  5. 建立测试类
public class TestUser {  
          
         privateUserMapper userDao;  
          
                 @Before  
             public void setUp() throws Exception {  
                 InputStream inputStream =Resources.getResourceAsStream("mybatis-config.xml");  
                 SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);  
                 // 是否自动提交  
                 SqlSession session =sqlSessionFactory.openSession(true);  
                 this.userDao =session.getMapper(UserMapper.class);  
             }  
                  
                 @Test  
                 publicvoid run(){  
                          System.out.println("-------------------------------------");  
                          Useruser = this.userDao.queryUserById(1);  
                          System.out.println(user);  
                          System.out.println("-------------------------------------");  
                 }  
   
} 

整理下上面的东西,mybatis独立使用需要提供

  • 一个接口Mapper类,内容是数据库操作的方法
  • 一个配置文件,内容是数据源信息
  • 一个sqlSessionFacotry用来承载控制数据库回话的类


先看下spring如何配置


  1. spring的配置文件
    <?xmlversionxmlversion="1.0"encoding="UTF-8"?>  
    <beansxmlns:xsibeansxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"  
      xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"  
      xmlns:tx="http://www.springframework.org/schema/tx"xmlns:cache="http://www.springframework.org/schema/cache"  
      xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans  
         http://www.springframework.org/schema/beans/spring-beans.xsd  
        http://www.springframework.org/schema/aop  
        http://www.springframework.org/schema/aop/spring-aop.xsd  
        http://www.springframework.org/schema/context  
        http://www.springframework.org/schema/context/spring-context.xsd  
        http://www.springframework.org/schema/tx  
        http://www.springframework.org/schema/tx/spring-tx.xsd  
         http://www.springframework.org/schema/cache  
        http://www.springframework.org/schema/cache/spring-cache.xsd">  
          
        <!-- 自动扫描 -->  
        <context:component-scanbase-packagecontext:component-scanbase-package="com.hys.app.**.service,com.hys.app.**.dao,com.hys.app.**.action"/>  
        <!-- 引入外置文件 -->  
        <beanidbeanid="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
            <propertynamepropertyname="location"value="classpath:jdbc.properties"/>  
        </bean>  
         
        <!--数据库连接池配置-->  
        <beanidbeanid="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close">    
            <propertynamepropertyname="driverClassName"value="${jdbc.driverClassName}"/>  
            <propertynamepropertyname="url"value="${jdbc.url}"/>  
            <propertynamepropertyname="username"value="${jdbc.username}"/>  
            <propertynamepropertyname="password"value="${jdbc.password}"/>  
        </bean>  
       
        <!-- spring和MyBatis完美整合 -->  
        <beanidbeanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean">  
            <!-- 指定数据源 -->  
            <propertynamepropertyname="dataSource"ref="dataSource"/>  
            <!-- 具体指定xml文件,可不配 -->  
            <propertynamepropertyname="configLocation" value="classpath:mybatis-config.xml"/>  
          
        </bean >  
              
        <bean id="UserMapper" class="org.mybatis.Spring.mapper.MapperFactoryBean">
    
               <property name="mapperInterface" value="test.mybatis.dao.UserMapper">
               <peroperty name="sqlSessionFactory" ref="sqlSessionFactory">
    </bean>
    </beans>

  2. Mybatis配置文件 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDSQL Map Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases>          <typeAliase alias="User" type="test.mybatis.User"></typeAliase> </typeAliases> <mappers> <mapper resource="com/test/dao/UserMapper.xml"/> </mappers> </configuration>
     <?xml version="1.0" encoding="UTF-8"?>  
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDSQL Map Config 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-config.dtd">  
    <configuration>  
       
       <typeAliases>
            <typeAlise></typeAliase
    </typeAliases>
        <mappers>  
            <mapper resource="com/test/dao/UserMapper.xml"/>  
        </mappers>  
       
    </configuration>  

  3. 测试
    public class UserServiceTest{
        public static void main(String args[]){
            ApplicationContext context = new ClassPassXmlApplicationContext("test/mybatis/applicationContext.xml");
            UserMapper userDao = (UserMapper)context.getBean("userMapper");
        }
    }

    

总结,对比spring整合方式 和 mybatis独立使用的方式,spring将数据源的配置囊括到spring的控制范围内,注册了SqlSessionFactoryBean类型的bean用于控制与数据库的会话,以及用于接口映射的MapperFacotryBean


源码分析

 第一步 我们从控制会话的SqlSessionFactoryBean入手进行分析。

这个类实现了FacotryBean 接口 和 InitializingBean接口,这两个接口阅读过spring源码bean生成逻辑的应该非常熟悉。我们这里再回顾下

  • FacotryBean :一旦某个bean实现的此接口,那么在spring bean获取的时候就会通过getBean方法获取bean实例。
  • InitializingBean:实现此接口的bean会在bean初始化时调用afterPropertiesSet方法来进行bean的逻辑初始化。

好,我们定位到getObject方法

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }

进入

afterPropertiesSet();//这里的afterPropertiesSet方法其实就是实现InitializingBean后的覆盖方法

看看afterPropertiesSset中的逻辑

  @Override
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

进入buildSqlSessionFacotry

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }

    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }

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

    if (this.vfs != null) {
      configuration.setVfsImpl(this.vfs);
    }

    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);
        LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
      }
    }

    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      }
    }

    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      }
    }

    if (hasLength(this.typeHandlersPackage)) {
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan);
        LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
      }
    }

    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      }
    }

    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    if (this.cache != null) {
      configuration.addCache(this.cache);
    }

    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }

    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }

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

    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.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 + "'");
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found");
    }

    return this.sqlSessionFactoryBuilder.build(configuration);
  }

上面的代码比较多,但是逻辑其实比较简单,重要的代码只有下面这里两行

xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
其余的都是spring为了加载mybatis的一些配置做的兼容,上面的逻辑主要是设置mybatis的相关配置信息,如果存在配置文件,则从配置文件加载配置信息。新建configuration类用于承载所有的配置项。并最后通过xmlConfigBuilder来创建sqlSessionFactory实例。


第二步,为了使用mybaits,我们新建了mapper映射类,MapperFacotryBean


查看MapperFactoryBean代码,发现同样实现了FactoryBean和InializingBean接口。

查找后发现当前类中没有afterProperties方法,层层追踪其父类,在DaoSupport中找到额afterProperties方法

public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
		// Let abstract subclasses check their configuration.
		checkDaoConfig();

		// Let concrete implementations initialize themselves.
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex);
		}
	}

查看checkDaoConfig方法

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

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

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

上面代码主要是实现了注册加入到映射类型中

因为MapperFactoryBean实现了Factorybean接口,所以当调用获取bean方法的时候调用的是getObject方法返回的实例。

  /**
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

     这段代码正式我们提供给mybatis独立使用的时候获取Mapper的方式。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值