springboot 加载mybatis的流程

一、mybatics的配置步骤

1.POM依赖包加载

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>

2.自定义MYBATICS配置对象。

/**
 *  mybatics的数据工厂配置。
 */
@Configuration
@MapperScan(basePackages = "com.tpw.summaryday.dao", sqlSessionFactoryRef = "test2_sqlSessionFactory")
public class MybaticsSessionConf {

    //配置mybatis的分页插件pageHelper
    @Bean
    public PageHelper pageHelper() {
        PageHelper pageHelper = new PageHelper();
        Properties props = new Properties();
        props.setProperty("dialect", "mysql");
        // 表示支持从接口中读取pageNum和pageSize
        props.setProperty("supportMethodsArguments", "true");
        pageHelper.setProperties(props);
        return pageHelper;
    }


    @Bean(name = "test2_dataSource")
    @ConfigurationProperties(prefix = "spring.datasource.test2")
    public DataSource getDateSource1() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "test2_sqlSessionFactory")
    public SqlSessionFactory getSqlSessionFactory(@Qualifier("test2_dataSource") DataSource test2DataSource)
            throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(test2DataSource);
        bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));
//        bean.setMapperLocations(
//                // 设置mybatis的xml所在位置
//                new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/localdb/*.xml"));
        return bean.getObject();
    }

    @Bean("test2_sqlSessionTemplate")
    public SqlSessionTemplate getSqlsessiontemplate(@Qualifier("test2_sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        try {
            return new SqlSessionTemplate(sqlSessionFactory);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}

3.环境配置文件加载pageHelper配置

pagehelper.helperDialect = mysql
pagehelper.reasonable= true
pagehelper.supportMethodsArguments=  true
pagehelper.params= count=countSql

4.mybatis-generator.xml配置,用来生成DAO,MAPPER,ENTITY

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>

    <context id="DB2Tables"    targetRuntime="MyBatis3">
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--数据库链接地址账号密码-->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://rm-wz96s5izji5099uz13o.mysql.rds.aliyuncs.com:3306/test2?"
                        userId="" password="">
        </jdbcConnection>
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!--生成Model类存放位置-->
        <javaModelGenerator targetPackage="com.tpw.summaryday.entity" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!--生成映射文件存放位置-->
        <sqlMapGenerator targetPackage="com.tpw.summaryday.mapper" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!--生成Dao类存放位置-->
        <!-- 客户端代码,生成易于使用的针对Model对象和XML配置文件 的代码
                type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象
                type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象
                type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口
        -->
        <javaClientGenerator type="ANNOTATEDMAPPER" targetPackage="com.tpw.summaryday.dao" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <!--生成对应表及类名-->
        <table tableName="fanli_directional_goods" domainObjectName="DirectionalGoods"
               enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"
               enableSelectByExample="false" selectByExampleQueryId="false"></table>

    </context>
</generatorConfiguration>

5.mybatis-config.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="logImpl" value="STDOUT_LOGGING"/>
        <!--这个配置使全局的映射器(二级缓存)启用或禁用缓存-->
        <setting name="cacheEnabled" value="true" />
    </settings>
</configuration>

6.DAO层添加二级缓存配置


@CacheNamespace(implementation = RedisCache.class,flushInterval = 60L)
public interface DirectionalGoodsMapper {
    @Delete({
        "delete from fanli_directional_goods",
        "where id = #{id,jdbcType=INTEGER}"
    })
    int deleteByPrimaryKey(Integer id);

 

    @Select("select g.*,a.img,a.price from fanli_directional_goods g,fanli_akbgoods a where g.goods_id = a.data_id and g.goods_id = #{goodsId} limit 1")
    DirectGoodsDto selectByGoodsId(String goodsId);

    @Select("select g.*,a.img,a.price from fanli_directional_goods g,fanli_akbgoods a where g.goods_id = a.data_id and g.goods_id = #{goodsId}")
    List<DirectGoodsDto> listByGoodsId(String goodsId);


    @Select("select * from fanli_directional_goods  where  stime >= #{stime} and etime <= #{etime}")
    List<DirectionalGoods> pageByStimeAndEtime(String stime, String etime/*, @Param("pageNum")int startIndex, @Param("pageSize")int pageNumIn*/);
}

二、mybatics的DAO层对象加载流程

1.在CONFIG类中加入了@MapperScan(basePackages = "com.tpw.summaryday.dao", sqlSessionFactoryRef = "test2_sqlSessionFactory"),mapperScan对象引入了@Import(MapperScannerRegistrar.class)

org.mybatis.spring.annotation.MapperScannerRegistrar

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));

    List<String> basePackages = new ArrayList<String>();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

 2.然后跳到org.mybatis.spring.mapper.ClassPathMapperScanner.doScan 方法,去搜索DAO目录下所有的MAPPER对象。

 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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

这里生成的BEANDEFINERD中的beanClass就是MapFactoryBean工厂类。

 3.初始化数据源,sqlSessionFactory,sqlSessionTemplate对象。

 4.开始创建DAO层代理对象。是根据 org.mybatis.spring.mapper.MapperFactoryBean.getObject来通过工厂类BEAN创建对象。由于是工厂BEAN创建对象,首先要创建工厂对象。

这时在进行工厂对象的实例化和初始化操作。

 5.上面工厂对象的afterPropertiesSet方法中,接着会调用到org.apache.ibatis.binding.MapperRegistry中的addMapper方法,将mapper映射保存起来。

上面类中有这样一个MAPPER映射哈希表。为一个****Mapper对象一个MAPPER代理工厂对象。

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
MapperRegistry为org.apache.ibatis.session.Configuration下面的一个成员变量。Configuration也为sqlSession下一个成员。
Configuration configuration = getSqlSession().getConfiguration();
public Configuration getConfiguration() {
  return this.sqlSessionFactory.getConfiguration();
}

从上面可以看出,configuation,MapperRegistry为一个sqlSessionFactory全局共享。

 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));
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

 6.这个为 org.mybatis.spring.mapper.MapperFactoryBean.getObject方法,就是通过sqlSession去创建一个mapper对象,所以我们在外面也可以通过sqlSession获取任意的mybatics的MAPPER对象。

  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

 

7.这里面的sqlSession其实为sqlSessionTemplate对象,然后会调用MapperRegistry的

getMapper方法来创建对象。
  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);
    }
  }
  

最终也就是调用org.apache.ibatis.binding.MapperProxyFactory.newInstance来创建代理对象。

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

 8.我们可以看到,最终就是生成一个JDK的动态代理对象,真正的代理实现类为MapperProxy

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

三、mybatics的DAO层接口调用流程

1.上面说过DAO层接口对象实际上是JDK生成的动态代理对象,dao层的任何接口都会走向代理对象的invoke方法。

org.apache.ibatis.binding.MapperProxy
 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);
  }

2.首先创建mapperMethod对象,传入sqlSession.getConfiguration()对象。

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

3.然后会调用到MapperMethod.execute方法,从这里可以看到,都是通过sqlSession的接口转发。

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
      Object param = method.convertArgsToSqlCommandParam(args);
        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());
    }
    return result;
  }

4.由于我们执行的是返回一个list,所以执行的是executeForMany方法

@Select("select g.*,a.img,a.price from fanli_directional_goods g,fanli_goods a where g.goods_id = a.data_id and g.goods_id = #{goodsId}")
List<DirectGoodsDto> listByGoodsId(String goodsId);
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

5.这里面的sqlSession为sqlSessionTemplate对象,继而调用此类的selectList方法

  public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.<E> selectList(statement, parameter);
  }
这里的sqlSessionProxy又是一个JDK动态代理对象,实际的实现类为SqlSessionInterceptor
this.sqlSessionProxy = (SqlSession) newProxyInstance(
    SqlSessionFactory.class.getClassLoader(),
    new Class[] { SqlSession.class },
    new SqlSessionInterceptor());

 6.接着就会调用SqlSessionInterceptor.invoke方法,进行反射调用。这里面会先获取一个sqlSession(同一个事务就会共用,否则每次都会创建一个新的)

  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      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)) {
          // 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) {
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

7.创建session为org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource,注意这里会先根据mybatics的config类以及数据源开启一个JDBC的事务,并且生成一个带缓存的执行器,然后会再生成一个SQL会话,传入执行器,配置类,默认自动提交为FALSE。

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

 8.接着反射调用到org.apache.ibatis.session.defaults.DefaultSqlSession.selectList

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

9.这里面的executor为带缓存的执行器,然后由于我们加入了分页插件,所以加入了

com.github.pagehelper.pageInterceptor,

 

   public Object intercept(Invocation invocation) throws Throwable {
        Object var16;
        try {
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement)args[0];
            Object parameter = args[1];
            RowBounds rowBounds = (RowBounds)args[2];
            ResultHandler resultHandler = (ResultHandler)args[3];
            Executor executor = (Executor)invocation.getTarget();
            CacheKey cacheKey;
            BoundSql boundSql;
            this.checkDialectExists();
            List resultList;
            if (!this.dialect.skip(ms, parameter, rowBounds)) {

                resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
            } else {
                resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }

            var16 = this.dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            this.dialect.afterAll();
        }

        return var16;
    }

10.由于我们是普通查询,没有分页,所以执行executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);

执行到org.apache.ibatis.executor.BaseExecutor.query方法

这里的cache只是在同一个sqlSession中有效,由于spring的特性,每次都会开启一个新的会话,所以一级缓存无效。这里查询的是数据库。

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    return list;
  }

11.然后会执行到org.apache.ibatis.executor.SimpleExecutor.doQuery,这里最终会去创建数据源连接,然后生成一个预处理的语句,然后进行JDBC的数据源查询 。

 

  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

12.执行完查询结果后,就会返回到SqlSessionInterceptor.invoke方法,最终发现本方法没有开启事务,进行自动提交,返回结果,然后关闭会话。

 13.至此查询流程完成。

四、mybatics的一级查询缓存在SPRING中无效,上面已经讲过,二级缓存可以开启,在同 一个命名空间中生效。

二级缓存,实现Cache接口,然后底层实现可以使用REDIS等各种分布式缓存。

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值