深度分析:Spring 如何整合 Mybatis

一,mybatis基本用法

1,mybatis的常用api

// inputStream是配置文件流
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSession sqlSession = factory.openSession();

// UserDao声明与mapper.xml文件的映射关系
UserDao userDao = sqlSession.getMapper(UserDao.class);

 

2,spring与mybatis的整合

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--加载配置文件-->
    <context:property-placeholder location="jdbc.properties"/>

    <!--配置数据源,这里只进行一个简单的配置-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--为mybatis的sqlSessionFactory注入数据源-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--注入数据源后,sqlSessionFactory就可以创建sqlSession对象了,然后我们需要配置mapper文件的位置-->
        <property name="mapperLocations" value="classpath:com/zs/dao/mapper/*.xml"/>
    </bean>

    <!--上面配置了mapper文件的位置,我们之前创建dao对象时,使用sqlSession的getMapper(dao.class)来创建对象
    那么使用spring来创建对象,spring如何找到dao接口的位置呢?-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.zs.dao"/>
    </bean>
    
</beans>

 

二,spring整合mybatis需要解决的问题

 

1,如何创建SqlSessionFactory和SqlSession对象?

2,如何获取UserDao对象?

 

实际上,当spring和mybatis整合后,我们并没有直接用到SqlSessionFactory和SqlSession的api。

要做的工作仅限于编写sql的mapper文件和Mapper接口定义文件,然后在Service中注入Mapper接口,通过注入的mapper就可以操作数据库了,如下图:

 

sql配置文件:

 

Mapper接口文件:

 

三,Spring如何创建SqlSessionFactory

 

SqlSessionFactory的创建要归功于SqlSessionFactoryBean,SqlSessionFactoryBean继承了三个接口

 

 

1,FactoryBean接口:实现了该接口的类,在调getBean的时候会返回该工厂返回的实例对象,也就是再调一次getObject方法返回工厂的实例

 

继承这个接口,就会实现getObject方法。当通过getBean获取SqlSessionFactory时,实际上调用SqlSessionFactoryBean的getObject获取的:

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

 

 

 

这里有个问题,SqlSessionFactory何时创建?

 

这要归功于SqlSessionFactoryBean实现的另外一个接口InitializingBean。

 

2,InitializingBean接口,在spring初始化时,实现了InitializingBean的类都会被实例化,然后调用afterPropertiesSet。

 

于是在afterPropertiesSet方法中,spring完成了SqlSessionFactory的创建。

 

四,spring如何屏蔽SqlSessionFactory,SqlSession,通过依赖注入Mapper接口,就可以操作数据库?

 

1,Mapper对象的创建

 

原来,有另外一个FactoryBean在起作用,它就是MapperFactoryBean,MapperFactoryBean也实现了FactoryBean和InitializingBean接口。

 

MapperFactoryBean继承了SqlSessionDaoSupport,SqlSessionDaoSupport继承DaoSupport,DaoSupport实现了InitializingBean接口,让我们开看看它这接口的实现:

	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()方法,一个是调用initDao方法。checkDaoConfig方法在DaoSupport是抽象方法,让我看看它在MapperFactoryBean的实现:

 /**
   * {@inheritDoc}
   */
  @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 (Throwable t) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
        throw new IllegalArgumentException(t);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }
  

 

该方法主要是检查dao的配置,主要是检验sqlSessionFactory和mapperInterface属性不能为空,以及检测接口对于的映射文件是否存在,如果存在,那么就把它添加到configuration里面去,注册mapper。

 

2,获取Mapper对象

 

因为MapperFactoryBean实现了FactoryBean,所以在依赖注入Mapper时调用了MapperFactoryBean.getObject方法,该方法的实现如下:

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

 

熟悉的味道,熟悉的api。

 

 

 

3,但是,这里又有一个问题:难道每一个Mapper我都要去创建一个MapperFactoryBean吗?当然不是。

 

 

spring为了整合mybatis,提供了一个扩展类:MapperScannerConfigurer。

 

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,如果MapperScannerConfigurer实现了该接口,那么说明在application初始化的时候该接口会被调用,具体实现:

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }
 
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

这里我们重点关注三个主要的方法,分别是
 

processPropertyPlaceHolders();
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));

 

① processPropertyPlaceHolders属性处理

 

执行属性的处理,简单的说,就是把xml中${XXX}中的XXX替换成属性文件中的相应的值

 

② 根据配置属性生成过滤器

 

scanner.registerFilters();方法会根据配置的属性生成对应的过滤器,然后这些过滤器在扫描的时候会起作用。

③ 扫描java文件

 

scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));


该方法主要做了以下操作:


1)扫描basePackage下面的java文件
2)解析扫描到的java文件
3)调用各个在上一步骤注册的过滤器,执行相应的方法。
4)为解析后的java注册bean,注册方式采用编码的动态注册实现。
5)构造MapperFactoryBean的属性,mapperInterface,sqlSessionFactory等等,填充到BeanDefinition里面去。

 

做完这些,MapperFactoryBean对象也就构造完成了,扫描方式添加dao的工作也完成了,所有Mapper对应的FactoryBean就被注入到spring容器了,当spring容器在为其他类自动注入Mapper时,就如上一部分的讲解,调用sqlSession.getMapper方法。

 

参考文献:https://blog.csdn.net/qq_43193797/article/details/85011683

 

 

 

 

发布了101 篇原创文章 · 获赞 15 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/epitomizelu/article/details/104717068