1. 程式人生 > >SprignMVC+myBatis整合+mybatis原始碼分析+動態代理實現流程+如何根據mapper介面生成其實現類

SprignMVC+myBatis整合+mybatis原始碼分析+動態代理實現流程+如何根據mapper介面生成其實現類

首先熟悉三個概念:
SqlSessionFactoryBean
–為整合應用提供SqlSession物件資源
MapperFactoryBean
–根據指定的Mapper介面生成Bean例項
MapperScannerConfigurer
–根據指定包批量掃描Mapper介面並生成例項

SqlSessionFactoryBean:
在單獨使用MyBatis時,所有操作都是圍繞SqlSession展開的,SqlSession是通過SqlSessionFactory獲取的,SqlSessionFactory又是通過SqlSessionFactoryBuilder建立生成的。
在SpringMvc+MyBatis整合時,同樣需要SqlSession。SqlSessionFactoryBean這個元件通過原來的SqlSessionFactoryBuilder生成SqlSessionFactory物件,為整合應用提供SqlSession物件。
Java程式碼

<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource">  
    <property name="driverClassName" value="oracle.jdbc.OracleDriver" />  
    <property name="url" value="jdbc:oracle:thin:@localhost:1521:XE" />  
    <property name="username" value="jsd1403" />  
    <property
name="password" value="root" /> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="myDataSource" /> <property name="mapperLocations" value="classpath:com/lydia/entity/*.xml" /> </bean>

MapperFactoryBean:
其作用是根據Mapper介面獲取我們想要的Mapper物件,它封裝了原有的session.getMapper()功能的實現。
在定義MapperFactoryBean時,需要注入一下兩個屬性:
–SqlSessionFactoryBean物件,用於提供SqlSession
–要返回Mapper物件的Mapper介面
MapperFactoryBean配置如下:
Java程式碼

<!-- 方法一:定義mapper -->  
    <bean id="deptMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">  
        <property name="mapperInterface" value="com.lydia.entity.DeptMapper"></property>  
        <!-- 指定SqlSessionFactoryBean物件 -->  
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>  
    </bean>  

MapperScannerConfigurer配置使用:
注意:使用MapperFactoryBean時,當有一個Mapper(可以理解為表對應的對映檔案)就MapperFactoryBean,當mapper少數可以通過applicationContext配置檔案,通過id獲取。
如果大量的mapper,需要使用mybatis-spring.jar通過的MapperScannerConfigurer元件,通過這個元件可以自動掃描指定包下的各個Mapper介面,並註冊對應的MapperFactoryBean物件。

把之前的MapperFactoryBean的配置註釋掉,換成如下配置依然執行通過:
Java程式碼

<!--方法2:  
        可以把掃描到的Mapper介面變成Mapper物件-->  
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
    <!--指定要掃描包: 多個包用逗號隔開 -->  
    <property name="basePackage" value="com.lydia,com.tarena" />  
    <!--指定sqlSessionFactory -->  
    <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>  
</bean>  

注意:上面sqlSessionFactory屬性也可以不用指定,預設會以Autowired方式注入。
如果指定的某個包下並不完全是我們定義的Mapper介面,我們也可以通過自定義註解的方式指定生成MapperFactoryBean物件。
配置如下:

<!--方法3:  
    只要Mapper類前面加上@MyBatisRepository 這個自己指定的註解就OK-->  
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
        <property name="basePackage" value="com.lydia" />  
        <property name="annotationClass" value="com.lydia.annotation.MyBatisRepository" />  
    </bean>  

自定義註解:MyBatisRepository.java
Java程式碼

public @interface MyBatisRepository {  
}  

在DeptMapper介面中使用:

//@Repository("deptMapper")  
@MyBatisRepository  
public interface DeptMapper {  
    void addDept(Dept dept);  
    void deleteDept(Dept dept);  
    void updateDept(Dept dept);  
    ......  
}  

測試:

public class TestCase {  
    @Test  
    public void testFindAll() throws Exception {  
        String conf = "applicationContext.xml";  
        ApplicationContext ac = new ClassPathXmlApplicationContext(conf);  
        //獲取對應的mapper物件,並呼叫mapper介面中對應的方法  
        DeptMapper mapper = ac.getBean("deptMapper", DeptMapper.class);  
        List<Dept> lists = mapper.findAllDept();  
        for (Dept dept : lists) {  
            System.out.println(dept);  
        }  
    }  
}  

MapperScannerConfigurer處理過程原始碼分析

本文將分析mybatis與spring整合的MapperScannerConfigurer的底層原理,之前已經分析過java中實現動態,可以使用jdk自帶api和cglib第三方庫生成動態代理。本文分析的mybatis版本3.2.7,mybatis-spring版本1.2.2。
MapperScannerConfigurer介紹
MapperScannerConfigurer是spring和mybatis整合的mybatis-spring jar包中提供的一個類。

想要了解該類的作用,就得先了解MapperFactoryBean。

MapperFactoryBean的出現為了代替手工使用SqlSessionDaoSupport或SqlSessionTemplate編寫資料訪問物件(DAO)的程式碼,使用動態代理實現。

比如下面這個官方文件中的配置:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

org.mybatis.spring.sample.mapper.UserMapper是一個介面,我們建立一個MapperFactoryBean例項,然後注入這個介面和sqlSessionFactory(mybatis中提供的SqlSessionFactory介面,MapperFactoryBean會使用SqlSessionFactory建立SqlSession)這兩個屬性。

之後想使用這個UserMapper介面的話,直接通過spring注入這個bean,然後就可以直接使用了,spring內部會建立一個這個介面的動態代理。

當發現要使用多個MapperFactoryBean的時候,一個一個定義肯定非常麻煩,於是mybatis-spring提供了MapperScannerConfigurer這個類,它將會查詢類路徑下的對映器並自動將它們建立成MapperFactoryBean。

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

這段配置會掃描org.mybatis.spring.sample.mapper下的所有介面,然後建立各自介面的動態代理類。
MapperScannerConfigurer底層程式碼分析
以以下程式碼為示例進行講解(部分程式碼,其他程式碼及配置省略):

package org.format.dynamicproxy.mybatis.dao;
public interface UserDao {
    public User getById(int id);
    public int add(User user);    
    public int update(User user);    
    public int delete(User user);    
    public List<User> getAll();    
}

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="org.format.dynamicproxy.mybatis.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

我們先通過測試用例debug檢視userDao的實現類到底是什麼。
這裡寫圖片描述
我們可以看到,userDao是1個MapperProxy類的例項。
看下MapperProxy的原始碼,沒錯,實現了InvocationHandler,說明使用了jdk自帶的動態代理。

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }

}

下面開始分析MapperScannerConfigurer的原始碼
MapperScannerConfigurer實現了BeanDefinitionRegistryPostProcessor介面,BeanDefinitionRegistryPostProcessor介面是一個可以修改spring工長中已定義的bean的介面,該介面有個postProcessBeanDefinitionRegistry方法。
這裡寫圖片描述
然後我們看下ClassPathMapperScanner中的關鍵是如何掃描對應package下的介面的。
這裡寫圖片描述
其實MapperScannerConfigurer的作用也就是將對應的介面的型別改造為MapperFactoryBean,而這個MapperFactoryBean的屬性mapperInterface是原型別。MapperFactoryBean本文開頭已分析過。

所以最終我們還是要分析MapperFactoryBean的實現原理!

MapperFactoryBean繼承了SqlSessionDaoSupport類,SqlSessionDaoSupport類繼承DaoSupport抽象類,DaoSupport抽象類實現了InitializingBean介面,因此例項個MapperFactoryBean的時候,都會呼叫InitializingBean介面的afterPropertiesSet方法。

DaoSupport的afterPropertiesSet方法:
這裡寫圖片描述
MapperFactoryBean重寫了checkDaoConfig方法:
這裡寫圖片描述
然後通過spring工廠拿對應的bean的時候:
這裡寫圖片描述
這裡的SqlSession是SqlSessionTemplate,SqlSessionTemplate的getMapper方法:
這裡寫圖片描述
Configuration的getMapper方法,會使用MapperRegistry的getMapper方法:
這裡寫圖片描述
MapperRegistry的getMapper方法:
這裡寫圖片描述
MapperProxyFactory構造MapperProxy:
這裡寫圖片描述
沒錯! MapperProxyFactory就是使用了jdk組帶的Proxy完成動態代理。
MapperProxy本來一開始已經提到。MapperProxy內部使用了MapperMethod類完成方法的呼叫:
這裡寫圖片描述
下面,我們以UserDao的getById方法來debug看看MapperMethod的execute方法是如何走的。

@Test
public void testGet() {
    int id = 1;
    System.out.println(userDao.getById(id));
}
<select id="getById" parameterType="int" resultType="org.format.dynamicproxy.mybatis.bean.User">
    SELECT * FROM users WHERE id = #{id}
</select>

這裡寫圖片描述
這裡寫圖片描述

java動態代理淺析

最近在公司看到了mybatis與spring整合中MapperScannerConfigurer的使用,該類通過反向代理自動生成基於介面的動態代理類。

於是想起了java的動態代理,然後就有了這篇文章。

本文使用動態代理模擬處理事務的攔截器。

介面:

public interface UserService {
    public void addUser();
    public void removeUser();
    public void searchUser();
}

實現類:

public class UserServiceImpl implements UserService {
    public void addUser() {
        System.out.println("add user");
    }
    public void removeUser() {
        System.out.println("remove user");
    }
    public void searchUser() {
        System.out.println("search user");
    }
}

java動態代理的實現有2種方式
1.jdk自帶的動態代理
使用jdk自帶的動態代理需要了解InvocationHandler介面和Proxy類,他們都是在java.lang.reflect包下。

InvocationHandler介紹:

InvocationHandler是代理例項的呼叫處理程式實現的介面。

每個代理例項都具有一個關聯的InvocationHandler。對代理例項呼叫方法時,這個方法會呼叫InvocationHandler的invoke方法。

Proxy介紹:

Proxy 提供靜態方法用於建立動態代理類和例項。

例項(模擬AOP處理事務):

public class TransactionInterceptor implements InvocationHandler {

    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("start Transaction");
        method.invoke(target, args);
        System.out.println("end Transaction");
        return null;
    }

}

測試程式碼:

public class TestDynamicProxy {

    @Test
    public void testJDK() {
        TransactionInterceptor transactionInterceptor = new TransactionInterceptor();
        UserService userService = new UserServiceImpl();
        transactionInterceptor.setTarget(userService);
        UserService userServiceProxy =
                (UserService) Proxy.newProxyInstance(
                        userService.getClass().getClassLoader(),
                        userService.getClass().getInterfaces(),
                        transactionInterceptor);
        userServiceProxy.addUser();
    }

}

測試結果:

start Transaction
add user
end Transaction

很明顯,我們通過userServiceProxy這個代理類進行方法呼叫的時候,會在方法呼叫前後進行事務的開啟和關閉。

2. 第三方庫cglib

CGLIB是一個功能強大的,高效能、高質量的程式碼生成庫,用於在執行期擴充套件Java類和實現Java介面。

它與JDK的動態代理的之間最大的區別就是:

JDK動態代理是針對介面的,而cglib是針對類來實現代理的,cglib的原理是對指定的目標類生成一個子類,並覆蓋其中方法實現增強,但因為採用的是繼承,所以不能對final修飾的類進行代理。

例項:

public class UserServiceCallBack implements MethodInterceptor {

    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("start Transaction by cglib");
        methodProxy.invokeSuper(o, args);
        System.out.println("end Transaction by cglib");
        return null;
    }

}

測試程式碼:

public class TestDynamicProxy {

    @Test
    public void testCGLIB() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserServiceImpl.class);
        enhancer.setCallback(new UserServiceCallBack());
        UserServiceImpl proxy = (UserServiceImpl)enhancer.create();
        proxy.addUser();
    }

}

測試結果:

start Transaction by cglib
add user
end Transaction by cglib

mybatis如何根據mapper介面生成其實現類

mybatis裡頭給sqlSession指定執行哪條sql的時候,有兩種方式,一種是寫mapper的xml的namespace+statementId,如下:

public Student findStudentById(Integer studId) {
logger.debug(“Select Student By ID :{}”, studId);
SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
try {
return sqlSession.selectOne(“com.mybatis3.StudentMapper.findStudentById”, studId);
} finally {
sqlSession.close();
}
}
另外一種方法是指定mapper的介面:

public Student findStudentById(Integer studId) {
logger.debug(“Select Student By ID :{}”, studId);
SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
return studentMapper.findStudentById(studId);
} finally {
sqlSession.close();
}
}
一般的話,比較推薦第二種方法,因為手工寫namespace和statementId極大增加了犯錯誤的概率,而且也降低了開發的效率。

問題
mapper的實現類如何生成

如果使用mapper介面的方式,問題來了,這個是個介面,通過sqlSession物件get出來的一定是個實現類,問題是,我們並沒有手工去寫 實現類,那麼誰去幹了這件事情呢?答案是mybatis通過JDK的動態代理方式,在啟動載入配置檔案時,根據配置mapper的xml去生成。

mybatis-spring幫忙做了什麼

自動open和close session

一、mapper代理類是如何生成的
啟動時載入解析mapper的xml

如果不是整合spring的,會去讀取節點,去載入mapper的xml配置

<?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>
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="multipleResultSetsEnabled" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <setting name="defaultStatementTimeout" value="2"/>
    </settings>
    <typeAliases>
        <typeAlias alias="CommentInfo" type="com.xixicat.domain.CommentInfo"/>
    </typeAliases>
    <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/demo"/>
                <property name="username" value="root"/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/xixicat/dao/CommentMapper.xml"/>
    </mappers>
</configuration>

如果是整合spring的,會去讀spring的sqlSessionFactory的xml配置中的mapperLocations,然後去解析mapper的xml

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- 配置mybatis配置檔案的位置 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="typeAliasesPackage" value="com.xixicat.domain"/>
        <!-- 配置掃描Mapper XML的位置 -->
        <property name="mapperLocations" value="classpath:com/xixicat/dao/*.xml"/>
    </bean>

然後繫結namespace(XMLMapperBuilder)

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          configuration.addMapper(boundType);
        }
      }
    }
  }

這裡先去判斷該namespace能不能找到對應的class,若可以則呼叫

configuration.addMapper(boundType);
configuration委託給MapperRegistry:

public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }

生成該mapper的代理工廠(MapperRegistry)

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

這裡的重點就是MapperProxyFactory類:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

getMapper的時候生成mapper代理類

@SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

new出來MapperProxy

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
@SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }
這裡給代理類注入了sqlSession

MapperProxy實現InvocationHandler介面進行攔截代理

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
}

這裡的代理攔截,主要是尋找到MapperMethod,通過它去執行SQL。
MapperMethod委託給SqlSession去執行sql

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName() 
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

其實這裡就回到了第一種模式,該模式是直接指定了statement的Id(這裡是command.getName()),而通過mapper的介面方式,則多了這麼步驟,最後通過MapperMethod,給sqlSession傳入statement的id。

sqlSession其實自己也不執行sql,它只是mybatis對外公佈的一個api入口,具體它委託給了executor去執行sql。

什麼時候去getMapper

  • 手工get,比如

    public void createStudent(Student student) {
    SqlSession sqlSession = MyBatisSqlSessionFactory.getSqlSession();
    try {
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    studentMapper.insertStudent(student);
    sqlSession.commit();
    } finally {
    sqlSession.close();
    }
    }

  • 整合spring的話
    在spring容器給指定的bean注入mapper的時候get出來(見MapperFactoryBean的getObject方法)
    二、mybatis-spring幫忙做了什麼
    通過MapperScannerConfigurer將mapper適配成spring bean
<!-- 配置掃描Mapper介面的包路徑 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="com.xixicat.dao"/>
    </bean>

這裡使用 MapperFactoryBean將Mapper介面配置成 Spring bean 實體同時注入sqlSessionFactory。
MapperScannerConfigurer給每個mapper生成對應的MapperFactoryBean

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));
  }

委託給ClassPathMapperScanner去scan

public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

    if (beanDefinitions.isEmpty()) {
      logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
    } else {
      for (BeanDefinitionHolder holder : beanDefinitions) {
        GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();

        if (logger.isDebugEnabled()) {
          logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
              + "' and '" + definition.getBeanClassName() + "' mapperInterface");
        }

        // the mapper interface is the original class of the bean
        // but, the actual class of the bean is MapperFactoryBean
        definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
        definition.setBeanClass(MapperFactoryBean.class);

        definition.getPropertyValues().add("addToConfig", this.addToConfig);

        boolean explicitFactoryUsed = false;
        if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
          definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionFactory != null) {
          definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
          explicitFactoryUsed = true;
        }

        if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
          if (explicitFactoryUsed) {
            logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
          }
          definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
          explicitFactoryUsed = true;
        } else if (this.sqlSessionTemplate != null) {
          if (explicitFactoryUsed) {
            logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
          }
          definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
          explicitFactoryUsed = true;
        }

        if (!explicitFactoryUsed) {
          if (logger.isDebugEnabled()) {
            logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
          }
          definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
      }
    }

    return beanDefi