mybatis與spring集成原理

參考mybatis進(jìn)階4——與Spring的集成

1.SqlSessionFactoryBean

    <!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="typeAliasesPackage" value="com.enjoylearning.mybatis.entity" />
        <property name="mapperLocations" value="classpath:sqlmapper/*.xml" />
    </bean>
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

實(shí)現(xiàn)FactoryBean接口的getObject方法:

 /**
   * 
   * 將SqlSessionFactory對(duì)象注入spring容器
   * {@inheritDoc}
   */
  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }

    return this.sqlSessionFactory;
  }

SqlSessionFactoryBean實(shí)現(xiàn)InitializingBean接口砌创,需要實(shí)現(xiàn)其afterPropertiesSet():

 @Override
  //在spring容器中創(chuàng)建全局唯一的sqlSessionFactory
  public void afterPropertiesSet() throws Exception {
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
              "Property 'configuration' and 'configLocation' can not specified with together");

    this.sqlSessionFactory = buildSqlSessionFactory();
  }

核心是buildSqlSessionFactory:

  protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {//如果configuration不為空事甜,則使用該對(duì)象碑韵,并對(duì)其進(jìn)行配置
      configuration = this.configuration;
      if (configuration.getVariables() == null) {
        configuration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        configuration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {//創(chuàng)建xmlConfigBuilder留晚,讀取mybatis的核心配置文件
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    } else {//如果configuration為空,實(shí)例化一個(gè)configuration對(duì)象
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      }
      configuration = new Configuration();
      if (this.configurationProperties != null) {
        configuration.setVariables(this.configurationProperties);
      }
    }
    //設(shè)置objectFactory
    if (this.objectFactory != null) {
      configuration.setObjectFactory(this.objectFactory);
    }
    //設(shè)置objectWrapperFactory
    if (this.objectWrapperFactory != null) {
      configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }
  //設(shè)置vfs
    if (this.vfs != null) {
      configuration.setVfsImpl(this.vfs);
    }
  //掃描指定的包typeAliasesPackage,注冊(cè)別名
    if (hasLength(this.typeAliasesPackage)) {
      String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeAliasPackageArray) {
        configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
        }
      }
    }
  //為typeAliases指定的類注冊(cè)別名
    if (!isEmpty(this.typeAliases)) {
      for (Class<?> typeAlias : this.typeAliases) {
        configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type alias: '" + typeAlias + "'");
        }
      }
    }
    //注冊(cè)插件
    if (!isEmpty(this.plugins)) {
      for (Interceptor plugin : this.plugins) {
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '" + plugin + "'");
        }
      }
    }
    //掃描指定的包typeHandlersPackage箍镜,注冊(cè)類型解析器
    if (hasLength(this.typeHandlersPackage)) {
      String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      for (String packageToScan : typeHandlersPackageArray) {
        configuration.getTypeHandlerRegistry().register(packageToScan);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
        }
      }
    }
  //為typeHandlers指定的類注冊(cè)類型解析器
    if (!isEmpty(this.typeHandlers)) {
      for (TypeHandler<?> typeHandler : this.typeHandlers) {
        configuration.getTypeHandlerRegistry().register(typeHandler);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered type handler: '" + typeHandler + "'");
        }
      }
    }
    //配置databaseIdProvider
    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
      try {
        configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }
  //配置緩存
    if (this.cache != null) {
      configuration.addCache(this.cache);
    }
     //使用xmlConfigBuilder讀取mybatis的核心配置文件
    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
        }
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }
    //默認(rèn)使用SpringManagedTransactionFactory作為事務(wù)管理器
    if (this.transactionFactory == null) {
      this.transactionFactory = new SpringManagedTransactionFactory();
    }
   //設(shè)置Environment
    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

  //根據(jù)mapperLocations的配置,處理映射配置文件以及相應(yīng)的mapper接口
    if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
    }
    //最終使用sqlSessionFactoryBuilder創(chuàng)建sqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(configuration);
  }

可知SqlSessionFactoryBean主要通過對(duì)applicationContext.xml解析完成時(shí)Configuration的實(shí)例化以及對(duì)完成對(duì)映射配置文件mapper*.xml的解析渔欢。
因?yàn)檫@個(gè)方法內(nèi)有兩個(gè)關(guān)鍵類:
*XMLConfigBuilder:在mybatis中主要負(fù)責(zé)解釋mybatis-config.xml
XMLMapperBuilder:負(fù)責(zé)解析映射配置文件

2.MapperFactoryBean

2.1 MapperFactoryBean測(cè)試

applicationContext.xml配置墓塌,注入MapperFactoryBean:

    <bean id="tUserMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
        <property name="mapperInterface" value="com.enjoylearning.mybatis.mapper.TUserMapper"></property>
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
    </bean>

UserServiceImpl.java中按名字注入tUserMapper:

@Service
public class UserServiceImpl  implements UserService{
    @Resource(name="tUserMapper")
    private TUserMapper userMapper;

    @Override
    public TUser getUserById(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }
}

測(cè)試代碼:

public class MybatisSpringTest {
    @Resource
    private UserService us;

    @Test
    public void TestSpringMyBatis(){
        System.out.println(us.getUserById(1).toString());
    }

測(cè)試結(jié)果:

TUser [id=1, userName=lison, realName=李小宇, sex=1, mobile=186995587422, email=lison@qq.com, note=lison的備注, positionId=1]

2.2 MapperFactoryBean源碼解析

實(shí)現(xiàn)FactoryBean接口的getObject方法:

  /**
   * 通過在容器中的mapperRegistry,返回當(dāng)前mapper接口的動(dòng)態(tài)代理
   * 
   * {@inheritDoc}
   */
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

調(diào)用SqlSessionTemplate的getMapper:

  @Override
  public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
  }

調(diào)用Configuration的getMapper:

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

調(diào)用MapperRegistry的getMapper:

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

調(diào)用MapperProxyFactory的newInstance:

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

2.2.1 這里解決了一個(gè)重要問題

    @Test
    // 測(cè)試自動(dòng)映射以及下劃線自動(dòng)轉(zhuǎn)化駝峰
    public void quickStart() throws IOException {
        // 2.獲取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 3.獲取對(duì)應(yīng)mapper
        TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
        // 4.執(zhí)行查詢語句并返回結(jié)果
        TUser user = mapper.selectByPrimaryKey(1);
        System.out.println(user.toString());

    }

TUserMapper是方法級(jí)別的,而注入到Spring的容器中顯然不是苫幢。

@Service
public class UserServiceImpl  implements UserService{
    @Resource(name="tUserMapper")
    private TUserMapper userMapper;

    @Override
    public TUser getUserById(Integer id) {
        return userMapper.selectByPrimaryKey(id);
    }
}

MyBatis-Spring是通過FactoryBean來解決的访诱,F(xiàn)actoryBean每次通過getObject來生成新的動(dòng)態(tài)代理對(duì)象來解決這個(gè)問題,本質(zhì)上與MyBatis中沒有區(qū)別韩肝!

3.一種更方便的方法——MapperScannerConfigurer

MapperScannerConfigurer會(huì)將指定basePackage下面的所有接口都以MapperFactoryBean形式注冊(cè)到Spring容器中触菜。

    <!-- DAO接口所在包名,Spring會(huì)自動(dòng)查找其下的類 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.enjoylearning.mybatis.mapper" />
    </bean>
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {

核心實(shí)現(xiàn)了BeanDefinitionRegistry后置處理器BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry哀峻,主要是對(duì)bean的定義信息進(jìn)行修改增強(qiáng)涡相,例如對(duì)bean的類型進(jìn)行轉(zhuǎn)換:

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {//占位符處理
      processPropertyPlaceHolders();
    }
    //實(shí)例化ClassPathMapperScanner,并對(duì)scanner相關(guān)屬性進(jìn)行配置
    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();//根據(jù)上述配置剩蟀,生成過濾器催蝗,只掃描合條件的class
    //掃描指定的包以及其子包
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

調(diào)用ClassPathBeanDefinitionScanner:

    public int scan(String... basePackages) {
        int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

        doScan(basePackages);

        // Register annotation config processors, if necessary.
        if (this.includeAnnotationConfig) {
            AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
        }

        return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
    }

調(diào)用ClassPathMapperScanner的doScan:

  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //通過父類的掃描,獲取所有復(fù)合條件的BeanDefinitionHolder對(duì)象
    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 {
      //處理掃描得到的BeanDefinitionHolder集合育特,將集合中的每一個(gè)mapper接口轉(zhuǎn)換成MapperFactoryBean后丙号,注冊(cè)至spring容器
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
  }

轉(zhuǎn)換成MapperFactoryBean:

  //處理掃描得到的BeanDefinitionHolder集合,將集合中的每一個(gè)mapper接口轉(zhuǎn)換成MapperFactoryBean后缰冤,注冊(cè)至spring容器
  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    //遍歷集合
    for (BeanDefinitionHolder holder : beanDefinitions) {
      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
      //將添加掃描到的接口類型作為構(gòu)造函數(shù)的入?yún)?      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      //講bean的類型轉(zhuǎn)換成mapperFactoryBean
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      //增加addToConfig屬性
      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      //增加sqlSessionFactory屬性
      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;
      }
      //增加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;
      }

      //修改自動(dòng)注入的方式 bytype
      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

4.總結(jié)

MyBatis集成到Spring后犬缨,只有兩件事稍微有不同:

  • 1)Cofiguration的實(shí)例化是讀取applicationContext.xml,而不是mybatis-config.xml
  • 2)mapper對(duì)象是方法級(jí)別的棉浸,Spring通過FactoryBean進(jìn)行增強(qiáng)巧妙地解決了這一問題

參考

  • 1)享學(xué)課堂Lison老師筆記
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怀薛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子涮拗,更是在濱河造成了極大的恐慌乾戏,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件三热,死亡現(xiàn)場(chǎng)離奇詭異鼓择,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)就漾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門呐能,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抑堡,你說我怎么就攤上這事摆出。” “怎么了首妖?”我有些...
    開封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵偎漫,是天一觀的道長。 經(jīng)常有香客問我有缆,道長象踊,這世上最難降的妖魔是什么温亲? 我笑而不...
    開封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮杯矩,結(jié)果婚禮上栈虚,老公的妹妹穿的比我還像新娘。我一直安慰自己史隆,他們只是感情好魂务,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泌射,像睡著了一般粘姜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上魄幕,一...
    開封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天相艇,我揣著相機(jī)與錄音,去河邊找鬼纯陨。 笑死坛芽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的翼抠。 我是一名探鬼主播咙轩,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼阴颖!你這毒婦竟也來了活喊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤量愧,失蹤者是張志新(化名)和其女友劉穎钾菊,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體偎肃,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煞烫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了累颂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滞详。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖紊馏,靈堂內(nèi)的尸體忽然破棺而出料饥,到底是詐尸還是另有隱情,我是刑警寧澤朱监,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布岸啡,位于F島的核電站,受9級(jí)特大地震影響赫编,放射性物質(zhì)發(fā)生泄漏凰狞。R本人自食惡果不足惜篇裁,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赡若。 院中可真熱鬧,春花似錦团甲、人聲如沸逾冬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽身腻。三九已至,卻和暖如春匹厘,著一層夾襖步出監(jiān)牢的瞬間嘀趟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來泰國打工愈诚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留她按,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓炕柔,卻偏偏與公主長得像酌泰,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子匕累,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 單獨(dú)使用mybatis是有很多限制的(比如無法實(shí)現(xiàn)跨越多個(gè)session的事務(wù))陵刹,而且很多業(yè)務(wù)系統(tǒng)本來就是使用sp...
    七寸知架構(gòu)閱讀 3,431評(píng)論 0 53
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)欢嘿,斷路器衰琐,智...
    卡卡羅2017閱讀 134,601評(píng)論 18 139
  • # 前言 在前兩篇文章我們?cè)?mybatis 源碼中探究了他的運(yùn)行原理,但在實(shí)際使用中炼蹦,我們需要將其和Spring...
    莫那一魯?shù)?/span>閱讀 3,456評(píng)論 0 4
  • 本文是我自己在秋招復(fù)習(xí)時(shí)的讀書筆記羡宙,整理的知識(shí)點(diǎn),也是為了防止忘記框弛,尊重勞動(dòng)成果辛辨,轉(zhuǎn)載注明出處哦!如果你也喜歡瑟枫,那...
    波波波先森閱讀 12,276評(píng)論 6 86
  • 2017.11.16日 陰 星期四 中午接你回來的路上斗搞,你說:媽媽老...
    珍萍閱讀 167評(píng)論 0 0