Mybatis映射器

    使用Mybatis最方便的地方,就是可以把sql的调用,转换成JAVA函数的调用。这样更符合和方便JAVA的编程习惯。Mybatis通过引入映射器实现了这种转换。

映射器的配置使用

映射配置文件

<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--命名空间值与接口全称对应-->
<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper">
    <!--结果集的映射-->
    <resultMap type="org.apache.ibatis.submitted.rounding.User" id="usermap">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="funkyNumber" property="funkyNumber"/>
        <result column="roundingMode" property="roundingMode"/>
    </resultMap>

    <!--id值与接口方法对应-->
    <select id="getUser" resultMap="usermap">
        select * from users
    </select>
    
    <insert id="insert">
        insert into users 
        (id, name, funkyNumber, roundingMode) values
        (#{id}, #{name}, #{funkyNumber}, #{roundingMode})
    </insert>
</mapper>

java接口文件

package org.apache.ibatis.submitted.rounding;

public interface Mapper {
    List<User> getUser();
    void insert(Map map);
}

一般的,一个java的接口文件,对应映射器一个命名空间,接口里面的方法,对应配置文件中的select,update,insert,delete元素,接口方法名称与元素的id对应。Mybatis框架在通过接口调用方法时就是通过(接口名称+方法名称)与(命名空间名称+元素id)的对应关系执行映射器元素里面的sql语句。

Mybatis框架调用代码

public void testMapper throws Exception {
   // 指定全局配置文件路径
   String resource = "mapper.xml";
   // 加载资源文件(全局配置文件和映射文件)
   InputStream inputStream = Resources.getResourceAsStream(resource);
   // 构建者模式,去创建SqlSessionFactory对象
   SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
   SqlSession sqlSession = sqlSessionFactory.openSession();
   //由sqlSession 实例化Mapper接口的实现对象
   Mapper mapper = sqlSession.getMapper(Mapper.class);
   //select * from users 的调用
   List<User> userList = mapper.getUser();
   System.out.println(userList);
   sqlSession.close();
}

映射器把SQL的调用转换成java的接口方法调用,更加符合按对象编程的方式,解耦了调用与配置的过程 。在实现使用中得到了广泛的使用。

映射器分析

Mybatis映射器的使用简单分为两步,第一步读取Mapper.xml配置文件,第二步生成接口的代理类,调用

配置文件读取

首先需要告诉Mybatis配置文件的位置,官方文档列举的不同方式:

<!-- 使用相对于类路径的资源引用;我们举例的方式,也是最常用的方式-->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

直接找到读取配置文件的代码: XMLConfigBuilder#mapperElement

//parent 为<mapper>元素
//XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
        //对应package的方式 <package name="org.mybatis.builder"/>
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
        configuration.addMappers(mapperPackage);
      } else {
         // <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
        String resource = child.getStringAttribute("resource");
        // <mapper url="file:///var/mappers/AuthorMapper.xml"/>  
        String url = child.getStringAttribute("url");
        // <mapper class="org.mybatis.builder.PostMapper"/>
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          //配置文件解析具体过程
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

针对不同的配置方式,解析成统一的配置,对mapperParser.parse()的过程继续分析:XMLMapperBuilder#parse

//XMLMapperBuilder#parse
public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    //对配置文件进行读取配置,为具体的解析过程
    configurationElement(parser.evalNode("*[local-name()='mapper']"));
    //解析完成,放入已加载的缓存中
    configuration.addLoadedResource(resource);
    //映射器绑定到命名空间
    bindMapperForNamespace();
  }
  
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

具体读取的过程:XMLMapperBuilder#configurationElement(XNode context)

private void configurationElement(XNode context) {
  try {
    //读取命名空间
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    //cache-ref 元素解析
    cacheRefElement(context.evalNode("*[local-name()='cache-ref']"));
    //cache元素解析
    cacheElement(context.evalNode("*[local-name()='cache']"));
    //parameterMap 元素解析
    parameterMapElement(context.evalNodes("*[local-name()='parameterMap']"));
    //resultMap 元素解析
    resultMapElements(context.evalNodes("*[local-name()='resultMap']"));
    //sql 元素解析
    sqlElement(context.evalNodes("*[local-name()='sql']"));
    // select,update,delete,insert元素解析
    buildStatementFromContext(context.evalNodes("*[local-name()='select' or local-name()='insert' or local-name()='update' or local-name()='delete']"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

由以前的分析知道,select,update,delete,insert元素最终会被解析成MappedStatement对象,并把这些对象放入到Configuration的缓存中,从buildStatementFromContext 开始的大致代码过程:


//XMLMapperBuilder#buildStatementFromContext
//list: select,update,delete,insert元素的元素列表
private void buildStatementFromContext(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  //创建MappedStatement
  buildStatementFromContext(list, null);
}


//#XMLMapperBuilderbuild#StatementFromContext 
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    //循环解析
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        //解析成MappedStatement,由XMLStatementBuilder完成
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }


//XMLStatementBuilder#parseStatementNode
 public void parseStatementNode() {
     //省略读取过程
     //...

    //<select id="getUser" resultMap="usermap">
    //  select * from users
    //</select>
    //解析select的各个元素,并创建MappedStatement对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

//MapperBuilderAssistant#addMappedStatement
public MappedStatement addMappedStatement(/*省略参数*/) {
    
    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }
    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(/*省略build过程*/);

    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
    //创建出最终的mappedStatement对象
    MappedStatement statement = statementBuilder.build();
    //mappedStatement对象放入configuration的缓存
    configuration.addMappedStatement(statement);
    return statement;
  }

//configuration#addMappedStatement
  public void addMappedStatement(MappedStatement ms) {
      // Map<String, MappedStatement> mappedStatements
      //mappedStatements 是一个map缓存,getId()=(namespace+元素id)
    mappedStatements.put(ms.getId(), ms);
  }

完成MappedStatement的解析和绑定以后,Mybatis继续把命名空间对应的接口进行缓存,方便调用时获取。

//XMLMapperBuilder#bindMapperForNamespace
private void bindMapperForNamespace() {
    //获取命名空间
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      //反射得到命名空间对应的class对象
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
    }
    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的缓存
        configuration.addMapper(boundType);
      }
    }
  }
}

//configuration#addMapper 
 public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
//映射器库结构类
public class MapperRegistry {
  private final Configuration config;
  //map结构:key:接口类;value:映射器代理工厂,由映射器工厂生产对应的动态代理实例
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
  
  //configuration.addMapper最终调用到这里:
  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);
        }
      }
    }
  }  
}

配置文件的读取缓存过程比较长,总体调用流程链比较简单。总结这里的工作就简化成两步

  1. 解析映射文件把,select,update,delete,insert转换成MappedStatement对象,放入Configuration 缓存

  2. 把mapper的命名空间与java的接口类进行绑定。并放入到Configuration 缓存

image-20210121162930484.png

接口代理类

在配置文件读取中会把命名空间对应的java接口类放入Configuration的缓存,里面放入的不是java接口的代理对象,而是生产代理对象的工厂,由这个工厂在调用时动态生成对象。分析这个工厂类

//映射器代理工厂类
public class MapperProxyFactory<T> {
//映射器对应的java接口
  private final Class<T> mapperInterface;
    
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  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) {
     //jdk的动态代理对象生成
     //mapperInterface:java接口
     //mapperProxy 实际的实体类
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    //MapperProxy对象来代理执行
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(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;
  }

   //java动态代理执行方法
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    //获取缓存的方法
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

    //缓存接口调用的方法
  private MapperMethod cachedMapperMethod(Method method) {
    return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
  }
}
//MapperMethod #execute
//最终执行的代码,看到最终执行由sqlSession来执行
public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: {
   Object param = method.convertArgsToSqlCommandParam(args);
      //sqlSession.insert
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      //sqlSession.update
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT:
      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 if (method.returnsCursor()) {
        result = executeForCursor(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
        if (method.returnsOptional() &&
            (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements();
      break;
    default:
      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;
}

回看获取映射器的调用代码

Mapper mapper = sqlSession.getMapper(Mapper.class);
//DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
  return configuration.<T>getMapper(type, this);
}
//configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}
//MapperRegistry#getMapper
//由mapperProxyFactory 创建出接口的代理对象
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);
  }
}

sqlSession实际就是从configuration的缓存中获取到动态代理对象,动态代理对象实际执行者为sqlSession,从新返回给sqlSession执行。

核心就生成java接口的动态代理对象,由动态代理对象根据调用的方法名和mapper配置文件里面的id进行对应,最终调用sqlSession的方法执行。

spring结合

spring 作为java里面使用最广泛的IOC和AOP框架,Mybatis和Spring实现了无缝的对接功能。

再次看下mybatis的调用代码

Mapper mapper = sqlSession.getMapper(Mapper.class);

我们知道sqlSession执行操作后需要close,释放资源;那时再次用获取的代理对象进行调用的话,因为资源已经被回收,肯定调用不通,会报错,因此按照mybatis的调用方式,每次sqlSession释放资源后都需要重新获取代理对象。

    而我们在使用spring框架时,会把获取的代理对象注入到spring容器中,spring把注入代理对象作为单例管理,可以一直使用,不需要每次调用前再去生成。

    mybatis提供了与spring结合的jar包:mybatis-srping-xxxx.jar,引入工程。以xml的配置使用为例
 <!-- 配置SqlSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 注入数据库连接池 -->
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="mybatis-spring-config.xml"/>
</bean>
<!--<bean id="sqlsessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">-->
 <!--<constructor-arg index="0" ref="sqlSessionFactory" />-->
<!--</bean>-->
<!--配置userMapper对象-->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="dao.UserMapper"/>
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

SqlSessionFactoryBean 这个类的作用就是生成mybatis的SqlSessionFactory类,id=userMapper对应的MapperFactoryBean才是需要使用的映射器接口类。

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> 

MapperFactoryBean 实现了FactoryBean,因此id=userMapper对应的实例对象由方法MapperFactoryBean.getObject()生成

//MapperFactoryBean#getObject()
@Override
public T getObject() throws Exception {
  return getSqlSession().getMapper(this.mapperInterface);
}

MapperFactoryBean 的sqlSessionFactory属性在注入时调用的set方法,封装成了SqlSessionTemplate类

//MapperFactoryBean#setSqlSessionFactory
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
  if (!this.externalSqlSession) {
     //封装成SqlSessionTemplate对象
    this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
  }
}

因此MapperFactoryBean#getObject()中的getSqlSession()获取到了SqlSessionTemplate对象,再由SqlSessionTemplate调用getMapper()方法。

//SqlSessionTemplate 部分源码
public class SqlSessionTemplate implements SqlSession,DisposableBean{
   //mybatis的sqlSessionFactory对象 
  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;
//SqlSession 的一个动态代理对象,由它来代理执行sql
  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;
  
  //构造函数 :SqlSessionTemplate(sqlSessionFactory)  最终会调用此构造函数实例SqlSessionTemplate对象
  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {

    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    //生成SqlSession的代理类
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        //实现InvocationHandler接口,sqlsession的代理者,有SqlSessionInterceptor来执行SqlSession的操作
        new SqlSessionInterceptor());
  }
    
  
  //获取映射接口的代理对象
  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
  
    
      @Override
  public Configuration getConfiguration() {
    return this.sqlSessionFactory.getConfiguration();
  }
  
    //sqlSession接口的select方法实现
     @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
      //由代理者执行select调用,其他update,delete,insert方法也类似调用
      this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
  }
    
   //...忽略其他代码
    
//InvocationHandler  接口的实现,jdk动态代理的实现类
    private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //获取mybatis的sqlSession对象
      SqlSession sqlSession = getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //反射执行方法
        Object result = method.invoke(sqlSession, args);
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) 
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        //关闭对象
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }
}

结合代码分析获取代理对象的过程:

  1. MapperFactoryBean.getObject---->getSqlSession().getMapper(this.mapperInterface);从MapperFactoryBean中获取的对象的方法与mybatis中的代码一样,还是通过sqlSession来获取。

  2. MapperFactoryBean中的SqlSession经过了封装 new SqlSessionTemplate(sqlSessionFactory),实际调用的是SqlSessionTemplate的getSqlSession方法。

    • SqlSessionTemplate implements SqlSession:SqlSessionTemplate 实现了SqlSession方法
    • SqlSessionTemplate 有自己的SqlSession对象
    • SqlSessionTemplate 的SqlSession对象 在构造函数中初始化,实例为SqlSession的动态代理对象,代理类的实现为SqlSessionInterceptor类
  3. SqlSessionTemplate 的getMapper(Class<T> type)方法-> return getConfiguration().getMapper(type, this),就是mybatis的获取代理对象的过程,获取的代理对象执行最后方法由sqlSession对象来执行,这里传进去this,即还是转到SqlSessionTemplate(实现了sqlSession接口) 中的方法。

  4. SqlSessionTemplate的select,delete,update,insert方法由内部的sqlSession代理对象调用:this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);

  5. sqlSessionProxy代理对象的实现为3中的SqlSessionInterceptor类,其实现的invoke方法,一开始就去获取真正执行的sqlsession对象。

  6. invoke方法执行完成后,最后关闭了sqlsession对象

从分析中得知,获取了映射器接口的代理对象后,代理对象中的执行sql的sqlSession对象的实例为SqlSessionTemplate,而SqlSessionTemplate对象在执行方法的时候又调用了sqlSession的另一个动态代理对象SqlSessionInterceptor,SqlSessionInterceptor的invoke方法每次执行都会通过sqlSessionFactory新获取一个sqlsession对象,并由这个sqlsession对象来进行sql的执行。
因此注入到spring的映射器接口代理对象可以重复使用,他把从sqlSessionFactory获取sqlsession对象的过程放在代理的方法中执行。

总结

mybatis与spring的结合中核心使用了动态代理模式,把sqlSessionFactory获取sqlsession对象的过程交由代理对象来实现。解决了开始提出的问题,方便使用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容