`
zengshaotao
  • 浏览: 787267 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

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

 
阅读更多
  1. package org.denger.mapper;
  2. import org.apache.ibatis.annotations.Param;
  3. import org.apache.ibatis.annotations.Select;
  4. import org.denger.po.User;
  5. public interface UserMapper {
  6. @Select("select * from tab_uc_account where id=#{userId}")
  7. User getUser(@Param("userId") Long userId);
  8. }



application-context.xml:

Xml代码 复制代码 收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"
  4. xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"
  5. 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">
  6. <!-- Provided by annotation-based configuration -->
  7. <context:annotation-config/>
  8. <!--JDBC Transaction Manage -->
  9. <bean id="dataSourceProxy"class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
  10. <constructor-arg>
  11. <ref bean="dataSource" />
  12. </constructor-arg>
  13. </bean>
  14. <!-- The JDBC c3p0 dataSource bean-->
  15. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
  16. <property name="driverClass" value="com.mysql.jdbc.Driver" />
  17. <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/noah" />
  18. <property name="user" value="root" />
  19. <property name="password" value="123456" />
  20. </bean>
  21. <!--MyBatis integration with Spring as define sqlSessionFactory -->
  22. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  23. <property name="dataSource" ref="dataSource" />
  24. </bean>
  25. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  26. <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
  27. <property name="basePackage" value="org.denger.mapper"></property>
  28. </bean>
  29. </beans>
  30. package org.denger.mapper;
  31. import org.junit.Assert;
  32. import org.junit.Test;
  33. import org.springframework.beans.factory.annotation.Autowired;
  34. import org.springframework.test.context.ContextConfiguration;
  35. import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
  36. @ContextConfiguration(locations = { "/application-context.xml"})
  37. public class UserMapperTest extends AbstractJUnit4SpringContextTests{
  38. @Autowired
  39. public UserMapper userMapper;
  40. @Test
  41. public void testGetUser(){
  42. Assert.assertNotNull(userMapper.getUser(300L));
  43. }
  44. }
  45. 对于以上极其简单代码看上去并无特殊之处,主要亮点在于 UserMapper 居然不用实现类,而且在调用 getUser 的时候,也是使用直接调用了UserMapper实现类,那么Mybatis是如何去实现 UserMapper的接口的呢?
    可能你马上能想到的实现机制就是通过动态代理方式
  46. 首先在Spring的配置文件中看到下面的Bean:
    Xml代码 复制代码 收藏代码
    1. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    2. <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    3. <property name="basePackage" value="org.denger.mapper"></property>
    4. </bean>

    以上的MapperScannerConfigurer class的注释中描述道:
    从base 包中搜索所有下面所有 interface,并将其注册到 Spring Bean容器中,其注册的class bean是MapperFactoryBean。
    好吧,看看它的注册过程,下面方法来从 MapperScannerConfigurer中的Scanner类中抽取,下面方法在初始化以上application-content.xml文件时就会进行调用。 主要用于是搜索 base packages 下的所有mapper class,并将其注册至 spring 的 benfinitionHolder中。
    Java代码 复制代码 收藏代码
    1. /**
    2. * Calls the parent search that will search and register all the candidates. Then the
    3. * registered objects are post processed to set them as MapperFactoryBeans
    4. */
    5. @Override
    6. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    7. //#1
    8. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    9. if (beanDefinitions.isEmpty()) {
    10. logger.warn("No MyBatis mapper was found in '" + MapperScannerConfigurer.this.basePackage
    11. "' package. Please check your configuration.");
    12. else {
    13. //#2
    14. for (BeanDefinitionHolder holder : beanDefinitions) {
    15. GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
    16. if (logger.isDebugEnabled()) {
    17. logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '"+ definition.getBeanClassName() + "' mapperInterface");
    18. }
    19. //#3
    20. definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
    21. definition.setBeanClass(MapperFactoryBean.class);
    22. }
    23. return beanDefinitions;
    24. }
    #1: 了解Spring初始化Bean过程的人可能都知道,Spring 首先会将需要初始化的所有class先通过BeanDefinitionRegistry进行注册,并且将该Bean的一些属性信息(如scope、className、beanName等)保存至BeanDefinitionHolder中;Mybatis这里首先会调用Spring中的ClassPathBeanDefinitionScanner.doScan方法,将所有Mapper接口的class注册至BeanDefinitionHolder实例中,然后返回一个Set<BeanDefinitionHolder>,其它包含了所有搜索到的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中执行的方法是:
    Java代码 复制代码 收藏代码
    1. /**
    2. * {@inheritDoc}
    3. */
    4. public void afterPropertiesSet() throws Exception {
    5. Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
    6. Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
    7. Configuration configuration = this.sqlSession.getConfiguration();
    8. if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
    9. configuration.addMapper(this.mapperInterface);
    10. }
    11. }
    上面方法中先会检测当前需要创建的 mapperInterface在全局的 Configuration中是否存在,如果不存在则添加。呆会再说他为什么要将 mapperInterface 添加至 Configuration中。该方法调用完成之后,就开始调用 getObject方法来产生mapper实例,看到MapperFactoryBean.getObject的该方法代码如下:
    Java代码 复制代码 收藏代码
    1. /**
    2. * {@inheritDoc}
    3. */
    4. public T getObject() throws Exception {
    5. return this.sqlSession.getMapper(this.mapperInterface);
    6. }
    通过Debug可以看到 sqlSession及mapperInterface对象:
    MyBatis+Spring基于接口编程的原理分析 - George - 乔治博客|GEORGE BLOG
    到目前为止我们的 UserMapper实例实际上还并未产生; 再进入org.mybatis.spring.SqlSessionTemplate中的getMapper方法,该方法将 this及mapper interface class 作为参数传入 org.apache.ibatis.session.Configuration的getMapper() 方法中,代码如下:
    Java代码 复制代码 收藏代码
    1. /**
    2. * {@inheritDoc}
    3. */
    4. public <T> T getMapper(Class<T> type) {
    5. return getConfiguration().getMapper(type, this);
    6. }

    于是,再进入 org.apache.ibatis.session.Configuration.getMapper中代码如下:
    Java代码 复制代码 收藏代码
    1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    2. return mapperRegistry.getMapper(type, sqlSession);
    3. }
    其中 mapperRegistry保存着当前所有的 mapperInterface class. 那么它在什么时候将 mapperinterface class 保存进入的呢?其实就是在上面的 afterPropertiesSet 中通过 configuration.addMapper(this.mapperInterface) 添加进入的。
    再进入 org.apache.ibatis.binding.MapperRegistry.getMapper方法,代码如下:
    Java代码 复制代码 收藏代码
    1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    2.   //首先判断当前knownMappers是否存在mapper interface class.因为前面说到 afterPropertiesSet 中已经将当前的 mapperinterfaceclass 添加进入了。
    3. if (!knownMappers.contains(type))
    4. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    5. try {
    6. return MapperProxy.newMapperProxy(type, sqlSession);
    7. catch (Exception e) {
    8. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    9. }
    10. }

     嗯,没错,看到 MapperProxy.newMapperProxy后可以肯定的是它确实采用的代理模式,再进入一看究竟吧:
      
    Java代码 复制代码 收藏代码
    1. public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) {
    2. ClassLoader classLoader = mapperInterface.getClassLoader();
    3. Class[] interfaces = new Class[]{mapperInterface};
    4. MapperProxy proxy = new MapperProxy(sqlSession);
    5. return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy);
    6. }


    JDK的动态代理就不说了,至此,基本Mybatis已经完成了Mapper实例的整个创建过程,也就是你在具体使用 UserMapper.getUser 时,它实际上调用的是 MapperProxy,因为此时 所返回的 MapperProxy是实现了 UserMapper接口的。只不过 MapperProxy拦截了所有对userMapper中方法的调用,如下:
     
    Java代码 复制代码 收藏代码
    1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    2. try {
    3. //如果调用不是 object 中默认的方法(如equals之类的)
    4. if (!OBJECT_METHODS.contains(method.getName())) {
    5. //因为当前MapperProxy代理了所有 Mapper,所以他需要根据当前拦截到的方法及代理对象获取 MapperInterface class,也就是我这里的 UserMapper.class
    6. final Class declaringInterface = findDeclaringInterface(proxy, method);
    7. //然后再根据UserMapper.class、及当前调用的Method(也就是getUser)及SqlSession构造一个 MapperMethod,在这里面会获取到 getUser方法上的 @Select() 的SQL,然后再通过 sqlSession来进行执行
    8. final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession);
    9. //execute执行数据库操作,并返回结果集
    10. final Object result = mapperMethod.execute(args);
    11. if (result == null && method.getReturnType().isPrimitive()) {
    12. throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type ("+ method.getReturnType() + ").");
    13. }
    14. return result;
    15. }
    16. catch (SQLException e) {
    17. e.printStackTrace();
    18. }
    19. return null;
    20. }

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

分享到:
评论

相关推荐

    spring+mybatis+quartz

    在整合这三个技术时,通常会使用Spring的ApplicationContext来加载配置,包含MyBatis的SqlSessionFactory和Mapper接口,以及Quartz的Scheduler实例。MyBatis的配置文件中,会指定数据源、事务管理器以及Mapper文件的...

    Spring整合Mybatis与SpringBoot整合Mybatis原理分析

    **Spring整合Mybatis原理分析** 在Java Web开发中,Spring框架以其强大的依赖注入和面向切面编程能力,成为了事实上的核心框架。Mybatis则是一个轻量级的持久层框架,它简化了数据库操作,提供了直观的SQL映射。将...

    Spring+SpringMVC+Mybatis资源课件

    9.0 MyBatis基础.pptx可能包含了Mybatis的基本配置、动态SQL、Mapper接口的使用等内容。 描述中提到的“不错的入门学习资源”意味着这些课件适合初学者,它们可能按照逐步学习的方式组织,从基础知识到实际应用,...

    struts+spring+mybatis源代码例子

    在这个模块中,我们可以期待找到与Struts相关的Action类,Spring的Service和DAO接口及其实现,以及MyBatis的Mapper接口和XML配置文件。这些文件会展示如何在实际场景中操作数据库,处理业务逻辑,并通过Struts的...

    spring+mybatis+ligerui

    例如,我们会配置DataSource、SqlSessionFactoryBean以及Mybatis的Mapper接口,让Spring自动管理这些对象的实例化和依赖注入。 Mybatis的配置主要集中在`mybatis-config.xml`文件中,包括数据源配置、事务管理器...

    二年级JAVAEE学生信息管理系统_Spring+SpringMVC+Mybatis+Mysql+Tomcat.zip

    3. Mybatis的配置和使用,包括Mapper接口、XML配置文件和注解方式的SQL映射。 4. MySQL数据库设计,如表结构设计、索引优化、事务处理等。 5. Tomcat的配置和部署,以及日志管理和性能调优。 6. 使用IDEA或Eclipse等...

    SSM框架之员工信息查询系统(Spring mvc + mybatis + mysql + easyui )

    在实际项目中,"TickerDemo20170614_2"可能是项目的源码包或者相关资源文件,包含着SSM框架的配置文件(如spring-servlet.xml、mybatis-config.xml)、实体类(Employee)、Mapper接口与XML映射文件、Controller类...

    Springboot+MyBatis+MySQL实现多功能个人博客系统.zip

    对于学生来说,这个项目不仅能提升Java编程技能,还能了解Springboot的自动配置原理、MyBatis的映射机制,以及数据库的设计和优化。同时,通过实际的开发流程,可以学习到如何从需求出发,进行系统分析、设计、编码...

    Spring MVC+MYBatis企业应用实战

    《Spring MVC+MYBatis企业应用实战》是针对已有一定编程框架基础的学习者设计的一份实战教程,旨在深入解析在企业级项目中如何有效结合Spring、Spring MVC和MyBatis这三个核心组件,构建高效、可维护的Web应用程序。...

    spring-mybatis-spring-2.1.0.zip

    MyBatis-Spring是专门为这两种框架设计的桥梁,它使得MyBatis的SqlSession和Mapper接口可以无缝集成到Spring的管理环境中。通过MyBatis-Spring,我们可以将MyBatis的Mapper配置为Spring的Bean,从而利用Spring的依赖...

    Mybatis-Spring, 这是一个集成了Mybatis分页插件和通用Mapper的示例项目.zip

    6. **源码结构分析**: `Mybatis-Spring-master`可能包含以下几个主要部分:src/main/java(存放Java源代码,包括Service、DAO、Mapper接口等)、src/main/resources(存放配置文件,如mybatis-config.xml、spring...

    基于spring+spring mvc+mybatis的图书管理系统源码

    总的来说,"基于Spring+Spring MVC+Mybatis的图书管理系统源码"是一个典型的Java Web项目,展示了SSM框架在实际开发中的应用,提供了从数据存取、业务处理到用户交互的完整解决方案。通过学习和分析这套源码,开发者...

    毕业设计校园闲置物品交易系统(springboot+mybatis+oracle).zip

    在本项目中,MyBatis通过Mapper接口与XML配置文件,实现了数据操作的声明式编程,使得数据查询和更新更为直观和可控。 Oracle数据库是关系型数据库管理系统中的佼佼者,以其强大的事务处理能力、高可用性和安全性被...

    myeclipse+springmvc+spring+mybatis案例附带mysql数据库

    4. **MyBatis**:了解MyBatis的XML配置、Mapper接口、动态SQL、结果映射等特性,以及如何进行数据库操作。 5. **MySQL数据库**:理解数据库设计,包括表结构设计、SQL语句编写、事务处理等。 6. **项目配置**:分析...

    SSM(Spring+springmvc+mybatis)项目实例.zip

    2. `src/main/resources`:存放配置文件的地方,比如Spring的`applicationContext.xml`和MyBatis的`mybatis-config.xml`以及mapper接口和XML映射文件。 3. `src/main/java`:源代码目录,包含Service、Controller、...

    spring-boot-starter-mybatis-spring-boot-2.0.0.tar.gz

    《Spring Boot与MyBatis深度整合指南》 在现代Java开发中,Spring Boot以其便捷的配置、快速的应用启动以及丰富的生态而备受青睐。与此同时,MyBatis作为一个轻量级的持久层框架,以其灵活的SQL映射和强大的实体...

    mybatis-spring.rar_societyx6y_spring-mybatis

    3. 映射Mapper接口:MyBatis的Mapper接口可以直接在Spring中作为bean使用,通过MapperScannerConfigurer扫描指定包下的接口,实现自动注册。 4. 使用@Autowired注入Mapper接口:在业务逻辑类中,通过Spring的@...

    spring+mybatis开发实战

    《Spring+Mybatis开发实战》是一本专注于Java企业级应用开发的书籍,主要围绕Spring框架与Mybatis持久层框架的整合应用进行深入讲解。Spring以其强大的依赖注入和面向切面编程能力,为开发者提供了便捷的组件管理和...

    SpringMVC+Spring+Mybatis 用户管理系统案例软件源代码.rar

    这是一个基于Java技术栈的用户管理系统案例,主要使用了SpringMVC、Spring和Mybatis三大框架。这个案例旨在帮助开发者理解并掌握这三大框架在实际项目中的整合应用。 首先,SpringMVC是Spring框架的一部分,专注于...

    maven+spring+springmvc+mybatis简称mssm2

    这些源代码可能包括了Maven的配置文件pom.xml,Spring的配置文件如applicationContext.xml,Spring MVC的配置文件如servlet-context.xml,MyBatis的配置文件如mybatis-config.xml,以及相关的实体类、Mapper接口、...

Global site tag (gtag.js) - Google Analytics