MyBatis+Spring 基于接口编程的原理分析

整合Spring3及MyBatis3

对于整合Spring及Mybatis不作详细介绍,可以参考: MyBatis 3 User Guide Simplified Chinese.pdf,贴出我的主要代码如下:

package org.denger.mapper;    import org.apache.ibatis.annotations.Param;  import org.apache.ibatis.annotations.Select;  import org.denger.po.User;    public interface UserMapper {        @Select("select * from tab_uc_account where id=#{userId}")      User getUser(@Param("userId") Long userId);  }
application-context.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" xmlns:context="http://www.springframework.org/schema/context"      xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"      xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">          <!-- Provided by annotation-based configuration  -->      <context:annotation-config/>        <!--JDBC Transaction  Manage -->      <bean id="dataSourceProxy" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">          <constructor-arg>              <ref bean="dataSource" />          </constructor-arg>      </bean>        <!-- The JDBC c3p0 dataSource bean-->      <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">          <property name="driverClass" value="com.mysql.jdbc.Driver" />          <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/noah" />          <property name="user" value="root" />          <property name="password" value="123456" />      </bean>        <!--MyBatis integration with Spring as define sqlSessionFactory  -->      <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">          <property name="dataSource" ref="dataSource" />      </bean>        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">          <property name="sqlSessionFactory"  ref="sqlSessionFactory"/>          <property name="basePackage" value="org.denger.mapper"></property>      </bean>  </beans>
Test Case
package org.denger.mapper;    import org.junit.Assert;  import org.junit.Test;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.test.context.ContextConfiguration;  import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;    @ContextConfiguration(locations = { "/application-context.xml"})  public class UserMapperTest extends AbstractJUnit4SpringContextTests{        @Autowired      public UserMapper userMapper;            @Test      public void testGetUser(){          Assert.assertNotNull(userMapper.getUser(300L));      }  }
实现原理分析

对于以上极其简单代码看上去并无特殊之处,主要亮点在于 UserMapper 居然不用实现类,而且在调用 getUser 的时候,也是使用直接调用了UserMapper实现类,那么Mybatis是如何去实现 UserMapper的接口的呢? 可能你马上能想到的实现机制就是通过动态代理方式,好吧,看看MyBatis整个的代理过程吧。

首先在Spring的配置文件中看到下面的Bean:

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">      <property name="sqlSessionFactory"  ref="sqlSessionFactory"/>      <property name="basePackage" value="org.denger.mapper"></property>  </bean>

以上的MapperScannerConfigurer class的注释中描述道:

从base 包中搜索所有下面所有 interface,并将其注册到 Spring Bean容器中,其注册的class bean是MapperFactoryBean。

好吧,看看它的注册过程,下面方法来从 MapperScannerConfigurer中的Scanner类中抽取,下面方法在初始化以上application-content.xml文件时就会进行调用。 主要用于是搜索 base packages 下的所有mapper class,并将其注册至 spring 的 benfinitionHolder中。

/** * Calls the parent search that will search and register all the candidates. Then the * registered objects are post processed to set them as MapperFactoryBeans */  @Override  protected Set<BeanDefinitionHolder> doScan(String... basePackages) {      //#1      Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);      if (beanDefinitions.isEmpty()) {              logger.warn("No MyBatis mapper was found in ‘" + MapperScannerConfigurer.this.basePackage                          + "‘ package. Please check your configuration.");      } else {           //#2           for (BeanDefinitionHolder holder : beanDefinitions) {                  GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();              if (logger.isDebugEnabled()) {                  logger.debug("Creating MapperFactoryBean with name ‘" + holder.getBeanName() + "‘ and ‘"+ definition.getBeanClassName() + "‘ mapperInterface");              }             //#3            definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());            definition.setBeanClass(MapperFactoryBean.class);       }      return beanDefinitions;  }

#1: 了解Spring初始化Bean过程的人可能都知道,Spring 首先会将需要初始化的所有class先通过BeanDefinitionRegistry进行注册,并且将该Bean的一些属性信息(如scope、className、beanName等)保存至BeanDefinitionHolder中;Mybatis这里首先会调用Spring中的ClassPathBeanDefinitionScanner.doScan方法,将所有Mapper接口的class注册至BeanDefinitionHolder实例中,然后返回一个Set,其它包含了所有搜索到的Mapper class BeanDefinitionHolder对象。

#2: 首先,由于 #1 中注册的都是接口class, 可以肯定的是接口是不能直接初始化的;实际 #2 中for循环中替换当前所有 holder的 className为 MapperFactoryBean.class,并且将 mapper interface的class name setter 至 MapperFactoryBean 属性为 mapperInterface 中,也就是 #3 代码所看到的。

再看看 MapperFactoryBean,它是直接实现了 Spring 的 FactoryBean及InitializingBean 接口。其实既使不看这两个接口,当看MapperFactoryBean的classname就知道它是一个专门用于创建 Mapper 实例Bean的工厂。

至此,已经完成了Spring与mybatis的整合的初始化配置的过程。

接着,当我在以上的 Test类中,需要注入 UserMapper接口实例时,由于mybatis给所有的Mapper 实例注册都是一个MapperFactory的工厂,所以产生UserMapper实现仍需要 MapperFactoryBean来进行创建。接下来看看 MapperFactoryBean的处理过程。

先需要创建Mapper实例时,首先在 MapperFactoryBean中执行的方法是:

/**  * {@inheritDoc}  */  public void afterPropertiesSet() throws Exception {      Assert.notNull(this.sqlSession, "Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required");      Assert.notNull(this.mapperInterface, "Property ‘mapperInterface‘ is required");  

    Configuration configuration = this.sqlSession.getConfiguration();      if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {          configuration.addMapper(this.mapperInterface);      }  }

上面方法中先会检测当前需要创建的 mapperInterface在全局的 Configuration中是否存在,如果不存在则添加。呆会再说他为什么要将 mapperInterface 添加至 Configuration中。该方法调用完成之后,就开始调用 getObject方法来产生mapper实例,看到MapperFactoryBean.getObject的该方法代码如下:

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

通过Debug可以看到 sqlSession及mapperInterface对象:

到目前为止我们的 UserMapper 实例实际上还并未产生;再进入 org.mybatis.spring.SqlSessionTemplate 中的 getMapper 方法,该方法将 this 及 mapper interface class 作为参数传入 org.apache.ibatis.session.Configuration的getMapper() 方法中,代码如下:

/**  * {@inheritDoc}  */  public <T> T getMapper(Class<T> type) {      return getConfiguration().getMapper(type, this);  }

于是,再进入 org.apache.ibatis.session.Configuration.getMapper 中代码如下:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {      return mapperRegistry.getMapper(type, sqlSession);  }

其中 mapperRegistry保存着当前所有的 mapperInterface class. 那么它在什么时候将 mapperinterface class 保存进入的呢?其实就是在上面的 afterPropertiesSet 中通过 configuration.addMapper(this.mapperInterface) 添加进入的。

再进入 org.apache.ibatis.binding.MapperRegistry.getMapper 方法,代码如下:

/** public <T> T getMapper(Class<T> type, SqlSession sqlSession) {    //首先判断当前knownMappers是否存在mapper interface class.   //因为前面说到 afterPropertiesSet 中已经将当前的 mapperinterfaceclass 添加进入了。    if (!knownMappers.contains(type))      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");    try {      return MapperProxy.newMapperProxy(type, sqlSession);    } catch (Exception e) {      throw new BindingException("Error getting mapper instance. Cause: " + e, e);    }  }

嗯,没错,看到 MapperProxy.newMapperProxy后可以肯定的是它确实采用的代理模式,再进入一看究竟吧:

public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {      ClassLoader classLoader = mapperInterface.getClassLoader();      Class[] interfaces = new Class[]{mapperInterface};      MapperProxy proxy = new MapperProxy(sqlSession);      return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);    }

JDK的动态代理就不说了,至此,基本Mybatis已经完成了Mapper实例的整个创建过程,也就是你在具体使用 UserMapper.getUser 时,它实际上调用的是 MapperProxy,因为此时 所返回的 MapperProxy是实现了 UserMapper接口的。只不过 MapperProxy拦截了所有对userMapper中方法的调用,如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {     try {      //如果调用不是 object 中默认的方法(如equals之类的)       if (!OBJECT_METHODS.contains(method.getName())) {         //因为当前MapperProxy代理了所有 Mapper,所以他需要根据当前拦截到的方法及代理对象获取 MapperInterface  class,也就是我这里的 UserMapper.class         final Class declaringInterface = findDeclaringInterface(proxy, method);         //然后再根据UserMapper.class、及当前调用的Method(也就是getUser)及SqlSession构造一个 MapperMethod,在这里面会获取到 getUser方法上的 @Select() 的SQL,然后再通过 sqlSession来进行执行         final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);         //execute执行数据库操作,并返回结果集         final Object result = mapperMethod.execute(args);         if (result == null && method.getReturnType().isPrimitive()) {           throw new BindingException("Mapper method ‘" + method.getName() + "‘ (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");         }         return result;       }     } catch (SQLException e) {       e.printStackTrace();     }     return null;   }

为什么说它拦截了呢?可以看到, 它并没有调用 method.invoke(object)方法,因为实际上 MapperProxy只是动态的 implement 了UserMapper接口,但它没有真正实现 UserMapper中的任何方法。至于结果的返回,它也是通过 MapperMethod.execute 中进行数据库操作来返回结果的。说白了,就是一个实现了 MapperInterface 的 MapperProxy 实例被MapperProxy代理了,可以debug看看 userMapper实例就是 MapperProxy。

--

时间: 2024-08-10 19:48:00

MyBatis+Spring 基于接口编程的原理分析的相关文章

Eclipse 基于接口编程的时候,快速跳转到实现类的方法(图文)

Eclipse 基于接口编程的时候,要跳转到实现类很麻烦,其实Eclipse已经实现该功能. 只要按照Ctrl键,把鼠标的光标放在要跳转的方法上面,第一个是跳转到接口里面,第二个方法是跳转到实现类的位置

MyBatis 中 Mapper 接口的使用原理

MyBatis 中 Mapper 接口的使用原理 MyBatis 3 推荐使用 Mapper 接口的方式来执行 xml 配置中的 SQL,用起来很方便,也很灵活.在方便之余,想了解一下这是如何实现的,之前也大致知道是通过 JDK 的动态代理做到的,但这次想知道细节. 东西越多就越复杂,所以就以一个简单的仅依赖 MyBatis 3.4.0 的 CRUD 来逐步了解 Mapper 接口的调用. 通常是通过 xml 配置文件来创建SqlSessionFactory对象,然后再获取SqlSession对

spring基于接口的代理报错

报错: 1.在service层加上@Transactional注解.浏览器端报错(如下),eclipse控制台无信息 2.去掉@Transactional注解,无报错,但是数据库没有信息插入. 解决方法:添加proxy-target-class="true",并将属性值改为true proxy-target-class="true" 与proxy-target-class="false"的区别:        proxy-target-class

Spring Data 常用 API之原理分析 和 基本 API

Spring data 出现目的 为了简化.统一 持久层 各种实现技术 API所以 spring data 提供一套标准 API 和 不同持久层整合技术实现spring-data-commons 一套标准 APIspring-data-jpa 基于整合 JPA 实现 自己开发 Repository 只需要继承 JpaRepository 接口lCrudRepositorysave.delete.deteleAll.findAll.findOne.countPagingAndSortingRepo

spring面向接口编程

(1)创建一个接口 package com.min.dao; public interface UserDao { public void save(String uname, String pwd); } (2)创建一个实现类将用户信息保存到mysql数据库中 package com.min.dao.impl; import com.min.dao.UserDao; public class UserDaoMysqlImpl implements UserDao { @Override pub

Dubbo的SPI机制与JDK机制的不同及原理分析

从今天开始,将会逐步介绍关于DUbbo的有关知识.首先先简单介绍一下DUbbo的整体概述. 概述 Dubbo是SOA(面向服务架构)服务治理方案的核心框架.用于分布式调用,其重点在于分布式的治理. 简单的来说,可以把它分为四个角色.服务提供方(Provider).服务消费方(Consumer).注册中心和监控中心.通过注册中心对服务进行注册和订阅,通过监控中心对服务进行监控. 核心功能 Remoting:远程通讯,提供对多种NIO框架抽象封装,包括"同步转异步"和"请求-响应

MyBatis的深入原理分析之1-架构设计以及实例分析

MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单.优雅.本文主要讲述MyBatis的架构设计思路,并且讨论MyBatis的几个核心部件,然后结合一个select查询实例,深入代码,来探究MyBatis的实现. 一.MyBatis的框架设计        注:上图很大程度上参考了iteye 上的chenjc_it所写的博文原理分析之二:框架整体设计 中的MyBatis架构体图,chenjc_it总结的非常好,赞一个! 1.接口层---和数据库交互的方式 MyBatis

Spring AOP 切面编程记录日志和接口执行时间

最近客户现在提出系统访问非常慢,需要优化提升访问速度,在排查了nginx.tomcat内存和服务器负载之后,判断是数据库查询速度慢,进一步排查发现是因为部分视图和表查询特别慢导致了整个系统的响应时间特别长.知道了问题之后,就需要对查询比较慢的接口进行优化,但哪些接口需要优化.哪些不需要呢?只能通过日志里的执行时间来判断,那么如何才能知道每一个接口的执行时间呢? 如果想学习Java工程化.高性能及分布式.深入浅出.微服务.Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级

[yueqian_scut]蓝牙防丢器原理、实现与Android BLE接口编程

本文是对已实现的蓝牙防丢器项目的总结,阐述蓝牙防丢器的原理.实现与Android客户端的蓝牙BLE接口编程.在这里重点关注如何利用BLE接口来进行工程实现,对于BLE的协议.涉及到JNI的BLE接口内部源码实现,笔者以后再详细剖析.但要求读者对BLE协议有一定的认识,如GAP.GATTprofile在BLE中的角色和作用,如何使用Service.Characteristic等. 一.蓝牙防丢器原理和产品需求 蓝牙防丢器的核心原理是根据接收到的蓝牙设备端的无线信号强度(RSSI)来估算距离.其计算