Mybatis如何从DAO到SQL


1、Mybatis简介


MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。在项目中通过配置不同的mapper和xml,然后即可方便的对数据库进行操作。对于Mybatis的使用方法大家应该都很熟悉,本文不再赘述,本文重点叙述Mybatis的原理,详细分析Mybatis如何完成从mapper到sql的转化过程。下文会重点叙述两个方面:

  1. 启动时候Mybatis如何扫描并解析Mapper
  2. 运行时如何完成从对Mapper方法的调用转化成Sql

2、Mybatis扫描Mapper过程


2.1、Mapper扫描过程源码分析

对于dataSource和SqlSessionFactory大家应该都比较了解,我们不难猜测MapperScannerConfigurer主要完成对Mapper的扫描和解析。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName">
            <value>${jdbc.driverClassName}</value>
        </property>
        <property name="url">
            <value>${jdbc.url}</value>
        </property>
        <property name="username">
            <value>${jdbc.username}</value>
        </property>
        <property name="password">
            <value>${jdbc.password}</value>
        </property>
    </bean>

    //负责加载解析mapper对应的xml配置,将mapper和mappedStatements等配置信息保存到DefaultSqlSessionFactory的configuration属性
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="mapperLocations" value="classpath*:*.xml" />
	</bean>

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

	<!--事务管理器-->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="sourceDataSource"></property>
	</bean>

MapperScannerConfigurer实现BeanDefinitionRegistryPostProcessor接口,在对Bean实例化之前,通过回调postProcessBeanDefinitionRegistry方法register Mapper对应的BeanDefinition。最终将Mapper对应的Bean包装成MapperFactoryBean,并指定调用父类SqlSessionDaoSupport的setSqlSessionFactory方法给子类MapperFactoryBean添加对应的sqlSessionFactory属性。上述过程最终将Mapper转化成了MapperFactoryBean,如果对FactoryBean比较了解的话,应该知道后续我们通过@Autowired注解注入Mapper时候,会调用MapperFactoryBean的getObject()方法构造Mapper的实例。

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
    @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    //扫描指定的mapper
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
}

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //调用父类方法扫描xml配置的mapper路径,返回对应的beanDefinitions
    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 {
      //上述beanDefinitions 包装成MapperFactoryBean
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }
  
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      // 将mapper对应的beanDefinitions包装成MapperFactoryBean
      MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());

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

      boolean explicitFactoryUsed = false;
      //如果xml配置了SqlSessionFactory调用父类SqlSessionDaoSupport的setSqlSessionFactory方法给子类MapperFactoryBean添加对应的sqlSession属性,最终每个Mapper都会创建一个SqlSessionTemplate
      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;
      }

      //如果xml配置了SqlSessionTemplate,调用父类SqlSessionDaoSupport的setSqlSessionTemplate方法给子类MapperFactoryBean添加对应的sqlSession属性,此时所有Mapper共享一个SqlSessionTemplate(线程安全)
      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);
      }
    }
  }
}


public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }
  
  public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
    this.sqlSession = sqlSessionTemplate;
    this.externalSqlSession = true;
  }
}

2.2、MapperFactoryBean构造真实Mapper

上文已经叙述过Spring依赖注入Mapper时候,会调用getObject方法来返回真实的实例,然后经由一系列过程调用MapperRegistry的getMapper方法

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
  @Override
  public T getObject() throws Exception {
    //上文已经知道getSqlSession()会返回SqlSessionTemplate的一个实例
    return getSqlSession().getMapper(this.mapperInterface);
  }
}

public abstract class SqlSessionDaoSupport extends DaoSupport {

  private SqlSession sqlSession;

  private boolean externalSqlSession;

  public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
    if (!this.externalSqlSession) {
      this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
    }
  }
}

public class SqlSessionTemplate implements SqlSession, DisposableBean {
  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;
    //又是使用动态代理,对sqlSessionProxy方法的调用最终都会调用SqlSessionInterceptor的invoke方法
    this.sqlSessionProxy = (SqlSession) newProxyInstance(
        SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class },
        new SqlSessionInterceptor());
  }
@Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }
  
  //返回sqlSessionFactory对应的Configuration
  public Configuration getConfiguration() {
    return this.sqlSessionFactory.getConfiguration();
  }
}

MapperRegistry的getMapper方法会调用MapperProxyFactory的newInstance方法,该方法通过jdk动态代理来代理Mapper,后续对该Mapper所有方法的调用都会调用MapperProxy的invoke方法,至此完成了启动时Spring对Mapper的加载过程。

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

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

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

}

3、Mapper到sql的转化过程


通过上文我们知道在调用Mapper.insert(其他方法类似)时,会通过动态代理调用MapperProxy.invoke方法,该方法从缓存中获取MapperMethod,如果不存在就创建一个MapperMethod并缓存,然后调用MapperMethod.execute方法

@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);
    }
    //从缓存中获取MapperMethod,如果不存在就创建一个MapperMethod并缓存
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

MapperMethod的execute方法又会调用SqlSessionTemplate的同名方法,上文提到由于sqlSessionProxy是代理对象,对其任何方法调用,会调用SqlSessionInterceptor的invoke方法

public class MapperMethod {
    public Object execute(SqlSession sqlSession, Object[] args) {
        Object result;
        switch (command.getType()) {
          case INSERT: {
          Object param = method.convertArgsToSqlCommandParam(args);
          //此处sqlSession为启动过程创建的SqlSessionTemplate
            result = rowCountResult(sqlSession.insert(command.getName(), param));
            break;
          }
          case UPDATE: {
            Object param = method.convertArgsToSqlCommandParam(args);
            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);
            }
            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;
    }
}


public class SqlSessionTemplate implements SqlSession, DisposableBean {
    @Override
  public int insert(String statement, Object parameter) {
    return this.sqlSessionProxy.insert(statement, parameter);
  }
}

SqlSessionInterceptor的invoke方法首先获取一个SqlSession(DefaultSqlSession),然后通过反射调用DefaultSqlSession的对应方法,如果当前不存在事务,则直接commit。

private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      //getSqlSession方法是后续Spring处理事务的核心,这里叙述下其过程:如果当前不存在事务,则每次都重新创建一个SqlSession;如果当前存在事务,则从Spring Transaction Manager获取,并将获取的SqlSession缓存到当前线程。这里判断是否存在、获取或者缓存SqlSession都是基于ThreadLocal的,后续在Spring事务博客中会详细介绍
      SqlSession sqlSession = SqlSessionUtils.getSqlSession(
          SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType,
          SqlSessionTemplate.this.exceptionTranslator);
      try {
        //反射调用DefaultSqlSession的同名方法
        Object result = method.invoke(sqlSession, args);
        //如果当前不存在事务,则直接commit
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          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);
        }
      }
    }
  }

上文已经提到,启动过程中在构造sqlSessionFactory时候,会解析mapper对应的xml,并将配置信息缓存到configuration。我们知道在每个xml文件中需要配置一个namespace,然后还有很多sql,每个sql都对应的id,上述信息都会缓存到configuration的mappedStatements中。其中key=namespace+id,value是每条sql详细的配置信息。在获取到我们对应的MappedStatement后,然后调用executor.update方法

public class DefaultSqlSession implements SqlSession {
    @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      //这里的statement就是xml配置的[namespace+id]从configuration获取指定的MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
}

SimpleExecutor的doUpdate方法构造StatementHandler和prepareStatement,然后调用andler.update(stmt),最终会调用SimpleStatementHandler的update方法。到这个方法我们终于看到具体的sql了,通过标准的Jdbc接口操作数据库。

public class SimpleExecutor extends BaseExecutor {
  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }
}

@Override
  public int update(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      statement.execute(sql);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }

文章作者: 叶明
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 叶明 !
评论
 上一篇
IDEA调试Elasticsearch源码 IDEA调试Elasticsearch源码
本文首先会介绍在Mac上如何通过docker来安装es单机版本和集群版本,然后重点叙述如何使用IDEA编译Elasticsearch源码并进行调试
2021-04-22
下一篇 
Postman和Java Client访问K8s Postman和Java Client访问K8s
K8s的所有操作基本都是通过调用kube-apiserver这个组件进行的,它提供了restful api供外部系统访问,当然为了保证整个k8s集群的安全性,k8s提供了多种认证方式来保证集群的安全性:比如客户端证书、静态token、静态密
2018-11-11
  目录