spring源码剖析(八)spring整合mybatis原理

spring源码剖析(八)spring整合mybatis原理MyBatis相信很多人都会使用,但是当MyBatis整合到了spring中,我们发现在Spring中使用更加方便了。既然那么方便,Spring到底为我们做了哪些工作呢,它如何将MyBatis整合到Spring中的呢,Spring在整合MyBatis时候做了哪些封装,以及做了哪些拓展,又是怎么实现这些封装以及拓展的,让我们来打开这一部分的源代码,一探究竟。

大家好,又见面了,我是你们的朋友全栈君。

前言

               MyBatis相信很多人都会使用,但是当MyBatis整合到了Spring中,我们发现在Spring中使用更加方便了。例如获取Dao的实例,在Spring的我们只需要使用注入的方式就可以了使用Dao了,完全不需要调用SqlSession的getMapper方法去获取Dao的实例,更不需要我们去管理SqlSessionFactory,也不需要去创建SqlSession之类的了,对于插入操作也不需要我们commit。

               既然那么方便,Spring到底为我们做了哪些工作呢,它如何将MyBatis整合到Spring中的呢,Spring在整合MyBatis时候做了哪些封装,以及做了哪些拓展,又是怎么实现这些封装以及拓展的,让我们来打开这一部分的源代码,一探究竟。

               首先我们来先回顾下MyBatis的用法,以及Spring中MyBatis的使用方法。

MyBatis使用介绍

         单单MyBatis的使用比较简单,让我们来简单回顾一下他的使用方法。

建立PO 

po用于对于数据库中数据的映射,使得开发者更加专注于Java类的使用,而不是对数据库的操作

/**
 * @author: Fighter168
 */
public class Person {
	private String id;
	private String name;
//set get 方法、、、
}

建立Mapper

mapper是数据库操作的映射文件,也就是我们常说的dao文件

/**
 * @author: Fighter168
 */
public interface PersonDao {

	public List<Person> query();
	
	public void save(Person p);
	
	public void delete(String id);
}

建立配置文件

        配置文件主要用于程序中可变性高的设置,Mybatis的配置文件主要存在于configuration.xml中,当然configuration.xml中省略了其他mybatis的配置,例如settings里面的配置等等,如果没有玩过MyBatis的同学可以去参考网上MyBatis的教程自己去了解了解。

<?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>  
    <!-- 对事务的管理和连接池的配置 -->  
    <environments default="development">  
        <environment id="development">  
            <transactionManager type="JDBC" />  
            <dataSource type="POOLED">  
                   <property name="driver" value="com.mysql.jdbc.Driver" />
                   <property name="url" value="jdbc:mysql://localhost:3306/test" />
                   <property name="username" value="root" />
                   <property name="password" value="root" />  
            </dataSource>  
        </environment>  
    </environments>  
      
    <!-- mapping 文件路径配置 -->  
    <mappers>  
        <mapper resource="resource/PersonMapper.xml" />  
    </mappers>  
</configuration>

建立映射文件

映射文件对应于Mybatis全局配置中的mappers配置属性,主要用于建立对应数据库操作接口的SQL映射。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://www.mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="net.itaem.dao.PersonDao" >
  
  <resultMap id="resultMap" type="net.itaem.po.Person" >
	    <result column="id" property="id" jdbcType="CHAR" />
	    <result column="name" property="name" jdbcType="CHAR" />
  </resultMap>
<!--添加--> 
 <insert id="save"  parameterType="net.itaem.po.Person">
 insert into person(id,name) value(#{id,jdbcType=CHAR},#{name,jdbcType=CHAR})
 </insert>
<!--查询-->
 <select id="query"  resultMap="resultMap">
 select * from person
 </select>
 <!--删除-->
<delete id="delete" parameterType="java.lang.String">
 delete from person  where id=#{id,jdbcType=CHAR}
</delete>
</mapper>

建立测试类

           下面通过编写使用MyBatis实现查询的测试用例,比较简单,所以就不写太多注释了。

/**
 * @author: Fighter168
 */
public class Test {

	public static void main(String[] args) throws Exception {
		Reader reader=Resources.getResourceAsReader("resource/configuration.xml");
		SqlSessionFactory sessionFactory=new SqlSessionFactoryBuilder().build(reader);
		SqlSession session=sessionFactory.openSession();
		PersonDao personDao=session.getMapper(PersonDao.class);
		Person person=new Person("11","Fighter168");
		personDao.save(person);
		//这里一定要提交,不然数据无法插入
		session.commit();
		session.close();
	}
}

Spring中使用MyBatis介绍


             Spring中使用MyBatis更加简单,为了对比一下,我们来先看看下面的小例子。

创建Spring配置文件

          里面主要配置的是数据源,sqlSessionFactory和Dao的信息。
<?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-3.0.xsd">
	 <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
	     <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
	     <property name="url" value="jdbc:mysql://localhost:3306/test"/>
	     <property name="username" value="root"/>
	     <property name="password" value="123abc"/>
	     <!-- 连接池启动时候的初始连接数 -->
	     <property name="initialSize" value="10"/>
	     <!-- 最小空闲值 -->
	     <property name="minIdle" value="5"/>
	     <!-- 最大空闲值 -->
	     <property name="maxIdle" value="20"/>
	     <property name="maxWait" value="2000"/>
	     <!-- 连接池最大值 -->
	     <property name="maxActive" value="50"/>
	     <property name="logAbandoned" value="true"/>
	     <property name="removeAbandoned" value="true"/>
	     <property name="removeAbandonedTimeout" value="180"/>
	</bean>
	
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:/resource/cfg.xml"/>
		<property name="dataSource" ref="dataSource"/>
	</bean>
		
	<bean id="personDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
		<property name="mapperInterface" value="net.itaem.dao.PersonDao"/>
		<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
	</bean>
</beans>
            看到上面配置personDao,我们单独的使用一个Bean去创建它,也许我们会想到如果我们又几十甚至是几百个dao,那配置就可能很恐怖了,不过,Spring的作者也考虑到了这样的问题,针对这种情况,如果dao很多的情况,其实也是可以采取扫描dao的形式注入spring的,具体怎么实现,我们下面会继续介绍。


创建MyBatis配置文件

           mybatis的配置文件除了去掉environment标签,其他没啥区别。
<?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>  
    <!-- mapping 文件路径配置 -->  
    <mappers>  
        <mapper resource="resource/PersonMapper.xml" />  
    </mappers>  
</configuration>

创建映射文件

            映射文件和之前的映射文件保持一致。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://www.mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="net.itaem.dao.PersonDao" >
  
  <resultMap id="resultMap" type="net.itaem.po.Person" >
	    <result column="id" property="id" jdbcType="CHAR" />
	    <result column="name" property="name" jdbcType="CHAR" />
  </resultMap>
<!--添加--> 
 <insert id="save"  parameterType="net.itaem.po.Person">
 insert into person(id,name) value(#{id,jdbcType=CHAR},#{name,jdbcType=CHAR})
 </insert>
<!--查询-->
 <select id="query"  resultMap="resultMap">
 select * from person
 </select>
 <!--删除-->
<delete id="delete" parameterType="java.lang.String">
 delete from person  where id=#{id,jdbcType=CHAR}
</delete>
</mapper>

Spring使用MyBatis测试

         下面,让我先看看spring的测试用例中如何使用mybatis。
/**
 * @author: Fighter168
 */
public class SpringTest {

	public static void main(String[] args) {
		ApplicationContext context=new ClassPathXmlApplicationContext("resource/ApplicationContext.xml");
		PersonDao personDao=(PersonDao) context.getBean("personDao");
		Person person=new Person("12","Fighter168");
		personDao.save(person);
	}
}
         通过上面的比较,我们发现,在Spring中使用MyBatis是相当方便的,不需要我们去管理sqlSessionFactory以及SqlSession的管理,更不需要我们去操作简单的事务。


          至于Spring到底做了上面工作,下面我们进入主题吧,一探究竟。


spring整合mybatis实现


SqlSessionFactoryBean的创建

            SqlSessionFactoryBean这个类实现了三个接口,一个是InitializingBean,另一个是FactoryBean,还有就是ApplicationListener接口。下面说明一下实现了这三个接口的,有什么作用

InitializingBean接口:实现了这个接口,那么当bean初始化的时候,spring就会调用该接口的实现类的afterPropertiesSet方法,去实现当spring初始化该Bean 的时候所需要的逻辑。


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


ApplicationListener接口:实现了该接口,如果注册了该监听的话,那么就可以了监听到Spring的一些事件,然后做相应的处理


SqlSessionFactoryBean的初始化

              SqlSessionFactory的初始化是在Spring初始化该Bean 的时候就会初始化,实现InitializingBean接口的目的就是为了这个,让我们看看SqlSessionFactoryBean中实现InitializingBean的afterPropertiesSet方法。
           

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

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

          从中我们可以看到,sqlSessionFactory的实例化便在这个方法里面实例化,buildSqlSessionFactory()方法会对我们的sqlSessionFactory做定制的初始化,初始化sqlSessionFactory有两种方式,一种是我们直接通过property直接注入到改实例中,另一种是通过解析xml的方式,就是我们在configuration.xml里面的配置,根据这些配置做了相应的初始化操作,里面也是一些标签的解析属性的获取,操作,和Spring的默认标签解析有点类似,这里就不再重复说明。

获取SqlSessionFactoryBean实例

           因为SqlSessionFactoryBean实现了FactoryBean接口,所以当我们通过getBean获取它的实例的时候实际是调用他的getObject方法,获取到的是sqlSessionFactory。

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

        所以我们在给dao注入sqlSessionFactory的时候,依赖填写SqlSessionFactoryBean 的实例就可以了。




MapperFactoryBean的创建

       在使用mybatis的时候,我们获取dao的方式一般是这样:
		SqlSession session=sessionFactory.openSession();
		PersonDao personDao=session.getMapper(PersonDao.class);

      但在我们在spring的测试用例中使用mybatis的时候是这样使用的:

                PersonDao personDao=(PersonDao) context.getBean("personDao");

    为什么spring可以这样做呢,答案就在MapperFactoryBean这里


让我先来看看这个类的类层次结构图:
spring源码剖析(八)spring整合mybatis原理


MapperFactoryBean也实现了FactoryBean和InitializingBean接口,我们也从MapperFactoryBean的初始化开始吧,看看它如何初始化。


MapperFactoryBean初始化

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。

获取MapperFactoryBean的实例

           MapperFactoryBean实现了FactoryBean接口,那么在调用getBean方法获取MapperFactoryBean实例的时候,实际上调用的就是getObject方法,让我们来看看getObject的实现:

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

          

        看到这里,我们会恍然大悟,原来在这里封装了getMapper操作,返回接口的实例,怪不得在Spring中使用MyBatis不用管理sqlSession了。
        所以对于上面的测试用例,Spring怎么封装了MyBatis,如何把sqlSessionFactory和sqlSession隐藏了起来,又怎么方便的获取dao接口实例,我们大概有了一个了解。
        
        那么让我在回到之前提到的那个问题,如果有成百上千个dao接口呢,那我们岂不是要配置添加成百上千个bean,当然不是这样,spring还为MyBatis添加了拓展的功能,可以通过扫描包目录的方式,添加dao,让我看看具体使用和实现。




MapperScannerConfigurer介绍


         如果我们的dao在一个包下面又好几十个,那么我可以可以通过扫描的方式添加dao,像下面一样使用

	<!-- 去掉该配置
	<bean id="personDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
		<property name="mapperInterface" value="net.itaem.dao.PersonDao"/>
		<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
	</bean>
	 -->
	 <!-- 如果 net.itaem.dao 包下面有很多dao需要注册,那么可以使用这种扫描的方式添加dao-->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="net.itaem.dao"/>
	</bean>

           看到上面的配置,我们会很好奇,在spring这样添加就可以扫描的方式添加dao配置,怎么做到的?让我打开类实现,具体看一下。



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


  /**   * {@inheritDoc}   *    * @since 1.0.2   */  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的工作也完成了。



总结

                   其实了解了Spring整合MyBatis的流程,我们也就大体知道Spring整合一些框架所使用的扩展方法,不过大多是都是通过继承接口的方式,然后通过spring回调该接口的方式,实现我们自己想要的扩展逻辑,所以了解spring提供的一些扩展的接口以及抽象类是扩展的关键,就像InitializingBean,BeanDefinitionRegistryPostProcessor这些接口,知道了这些接口调用的方式,以及上面时候会调用,我们就可以知道,我们需要扩展的功能应该实现哪个接口,或者集成哪个抽象类。目前这些接口,官方没有整理出一份比较好的文档,不过在后续的博客中,我会把这些常用的拓展接口以及抽象类都提出来,介绍下,让大家熟悉下这些可以对spring进行扩展的接口以及抽象类。



版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/136333.html原文链接:https://javaforall.net

(0)
上一篇 2022年5月4日 下午3:00
下一篇 2022年5月4日 下午3:00


相关推荐

  • fastDB_最近使用

    fastDB_最近使用fastdb的官网fastdb怎么在Linux上面安装?$su-$cd/usr/local/src$wgethttp://www.garret.ru/fastdb-3.76.tar

    2022年8月2日
    12
  • Django(55)GenericAPIView源码分析

    Django(55)GenericAPIView源码分析源码分析GenericAPIView继承自APIView,也就是在APIView基础上再做了一层封装,源码如下:classGenericAPIView(views.APIView):query

    2022年7月29日
    10
  • 如何给mysql的海量数据查询优化

    如何给mysql的海量数据查询优化公司订单系统每日订单量庞大 有很多表数据超千万 公司 SQL 优化这块做的很不好 可以说是没有做 所以导致查询很慢 nbsp 正题 节选某个功能中的一句 SQL nbsp EXPLAIN nbsp 查看执行计划 EXPLAIN SQL 查看 SQL 执行计划 一个索引没用到 受影响行接近 2000 万 难怪会慢 nbsp 原来的 SQL 打印出来估计有好几张 A4 纸 我发个整理后的简版

    2026年3月18日
    2
  • tiff与tfw

    tiff与tfwTIFFWorldFil TFW 格式说明摘要 TFW 文件包含相关的 TIFF 文件的空间参数 spatialrefer 数据 本文详细描述了 TFW 文件中定义的参数 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp 概述 nbsp nbsp nbsp nbsp nbsp 两个文件构成 TFW 格式 nbsp nbsp nbsp nbsp nbsp nbsp nbsp ASCII 头文件 nbsp nbsp nbsp nbsp nbsp 图象文件 nbsp nbsp nbsp nbsp nbsp 图象文件为 TIFF 格式 未压缩 nbsp nbsp nbsp nbsp nbsp 详细

    2026年3月26日
    2
  • Mysql端口设置

    Mysql端口设置1.查看showglobalvariableslike’port’;2.修改修改配置文件(注意重启MySQL)

    2022年10月3日
    5
  • kubernetes ingress更改日志格式

    kubernetes ingress更改日志格式 IngressNginx默认访问日志都输出到/var/log/nginx/access.log文件中,但是对于一般的生产环境来说,不可能把所有日志都输到一个日志文件中,一般情况都是根据域名分别输出到各个文件中。所以这里区分http指令域默认配置以及单独域名的日志的配置方式。1.默认日志格式更改为json  修改mandatory.yaml部署文件nginx-configurationConfigMap配置中log-format-upstream字段,具体修改如下:log-form

    2022年6月10日
    51

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号