mybatis collection解析以及和association的区别

1.collection标签

说到mybatis的collection标签,我们肯定不陌生,可以通过它解决一对多的映射问题,举个例子一个用户对应多个系统权限,通过对用户表和权限表的关联查询我们可以得到好多条记录,但是用户信息这部分在多条记录中是重复的,只有权限不同,我们需要把这多条权限记录映射到这个用户之中,这个时候可以通过collection标签/association标签来解决(虽然assocation标签一般是解决一对一问题的,但它实际上也能实现我们的需求,可以通过后面的源码看出来)

1.1 相关代码和运行结果

实体类和mapper代码

@Data
public class UserDO {

  private Integer userId;

  private String username;

  private String password;

  private String nickname;

  // 将用户的权限信息映射到用户中
  private List<permitdo> permitDOList;

  public UserDO() {}

  public UserDO(@Param("userId") Integer userId, @Param("username") String username, @Param("password") String password, @Param("nickname") String nickname) {
    this.userId = userId;
    this.username = username;
    this.password = password;
    this.nickname = nickname;
  }
}

@Data
public class PermitDO {

  private Integer id;

  private String code;

  private String name;

  private NodeTypeEnum type;

  private Integer pid;
}

// mybatis&#x4EE3;&#x7801;
public interface UserMapper {

  UserDO getByUserId(@Param("userId") Integer userId);

}

<mapper namespace="org.apache.ibatis.study.mapper.UserMapper">

  <cache readonly="false" flushinterval="5000" size="100" blocking="false">

  </cache>

  <resultmap id="BaseMap" type="org.apache.ibatis.study.entity.UserDO" automapping="true">
     <!--  user_id列用<id>标签,因为对一个用户来说,user_id肯定是唯一的 -->
    <id column="user_id" jdbctype="INTEGER" property="userId">
    <result column="username" jdbctype="VARCHAR" property="username">
    <result column="password" jdbctype="VARCHAR" property="password">
    <result column="nickname" jdbctype="VARCHAR" property="nickname">

    <!-- <collection> 映射多条权限记录 -->
    <collection property="permitDOList" resultmap="PermitBaseMap">

    </collection>
  </result></result></result></id></resultmap>

  <resultmap id="PermitBaseMap" type="org.apache.ibatis.study.entity.PermitDO">
    <id column="id" jdbctype="INTEGER" property="id">
    <result column="code" jdbctype="VARCHAR" property="code">
    <result column="name" jdbctype="VARCHAR" property="name">
    <result column="type" jdbctype="TINYINT" property="type">
    <result column="pid" jdbctype="INTEGER" property="pid">
  </result></result></result></result></id></resultmap>

  <resultmap id="BaseMap1" type="org.apache.ibatis.study.entity.UserDO" automapping="false">
    <constructor>
      <idarg column="user_id" name="userId" jdbctype="INTEGER">
      <arg column="username" name="username" jdbctype="VARCHAR">
      <arg column="password" name="password" jdbctype="VARCHAR">
      <arg column="nickname" name="nickname" jdbctype="VARCHAR">
    </arg></arg></arg></idarg></constructor>
  </resultmap>

  <sql id="BaseFields">
    user_id, username, password, nickname
  </sql>

  <select id="getByUserId" resultmap="BaseMap" resultordered="true">
    select u.*, p.*
    from user u
    inner join user_permit up on u.user_id = up.user_id
    inner join permit p on up.permit_id = p.id
    <trim prefix="where" prefixoverrides="and | or">
      and u.user_id = #{userId, jdbcType=INTEGER}
    </trim>
  </select>

</mapper></permitdo>
public class Test {

  public static void main(String[] args) throws IOException {

    try (InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml")) {
      // &#x6784;&#x5EFA;session&#x5DE5;&#x5382; DefaultSqlSessionFactory
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      SqlSession sqlSession = sqlSessionFactory.openSession();
      UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
      UserDO userDO = userMapper.getByUserId(1);
      System.out.println(userDO);
    }
  }

}

运行结果如下,可以看到权限记录映射到属性permitDOList 的list列表了

mybatis collection解析以及和association的区别

1.2 collection部分源码解析

通过PreparedStatement查询完之后得到ResultSet结果集,之后需要将结果集解析为java的pojo类中,下面通过源码简单讲下是如何解析的

  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    // &#x662F;&#x5426;&#x6709;&#x5D4C;&#x5957;&#x7684;resultMaps
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      // &#x65E0;&#x5D4C;&#x5957;
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

根据有无嵌套分成两层逻辑,有嵌套resultMaps就是指

  private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    final DefaultResultContext<object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
    // &#x8DF3;&#x8FC7;offset&#x884C;
    skipRows(resultSet, rowBounds);
    // &#x4E0A;&#x4E00;&#x6B21;&#x83B7;&#x53D6;&#x7684;&#x6570;&#x636E;
    Object rowValue = previousRowValue;
    // &#x5DF2;&#x83B7;&#x53D6;&#x8BB0;&#x5F55;&#x6570;&#x91CF;&#x5C0F;&#x4E8E;limit
    while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
      // &#x9274;&#x522B;&#x5668;&#x89E3;&#x6790;
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      // &#x521B;&#x5EFA;&#x7F13;&#x5B58;key resultMapId + &#xFF08;columnName + columnValue&#xFF09;....

      final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
      // &#x90E8;&#x5206;&#x5BF9;&#x8C61;&#xFF08;&#x53EF;&#x80FD;&#x5B58;&#x5728;&#x5BF9;&#x8C61;&#x5185;&#x5BB9;&#x7F3A;&#x5931;&#x672A;&#x5B8C;&#x5168;&#x5408;&#x5E76;&#xFF09;
      Object partialObject = nestedResultObjects.get(rowKey);
      // issue #577 && #542
      // &#x5173;&#x4E8E;resultOrdered&#x7684;&#x7406;&#x89E3;&#xFF0C;&#x4E3E;&#x4F8B;&#x82E5;&#x67E5;&#x8BE2;&#x5F97;&#x5230;&#x56DB;&#x6761;&#x8BB0;&#x5F55;a,a,b,a , &#x76F8;&#x540C;&#x53EF;&#x4EE5;&#x5408;&#x5E76;&#x3002;
      // &#x90A3;&#x4E48;&#x5F53;resultOrdered=true&#x65F6;&#xFF0C;&#x6700;&#x540E;&#x53EF;&#x4EE5;&#x5F97;&#x5230;&#x4E09;&#x6761;&#x8BB0;&#x5F55;&#xFF0C;&#x7B2C;&#x4E00;&#x6761;&#x548C;&#x7B2C;&#x4E8C;&#x6761;&#x5408;&#x5E76;&#x6210;&#x4E00;&#x6761;&#x3001;&#x7B2C;&#x4E09;&#x6761;&#x5355;&#x72EC;&#x4E00;&#x6761;&#x3001;&#x7B2C;&#x56DB;&#x6761;&#x4E5F;&#x662F;&#x5355;&#x72EC;&#x4E00;&#x6761;&#x8BB0;&#x5F55;
      // resultOrdered=false&#x65F6;&#xFF0C;&#x6700;&#x540E;&#x53EF;&#x4EE5;&#x5F97;&#x5230;&#x4E24;&#x6761;&#x8BB0;&#x5F55;&#xFF0C;&#x7B2C;&#x4E00;&#x6761;&#x3001;&#x7B2C;&#x4E8C;&#x6761;&#x548C;&#x7B2C;&#x56DB;&#x6761;&#x4F1A;&#x5408;&#x5E76;&#x6210;&#x4E00;&#x6761;&#xFF0C;&#x7B2C;&#x4E09;&#x6761;&#x5355;&#x72EC;&#x4E00;&#x6761;&#x8BB0;&#x5F55;
      // &#x53E6;&#x5916;&#x5B58;&#x50A8;&#x5230;resultHandler&#x7684;&#x65F6;&#x673A;&#x4E5F;&#x4E0D;&#x4E00;&#x6837;&#xFF0C;resultOrdered=true&#x662F;&#x7B49;&#x9047;&#x5230;&#x4E0D;&#x53EF;&#x5408;&#x5E76;&#x7684;&#x8BB0;&#x5F55;&#x7684;&#x65F6;&#x5019;&#x624D;&#x628A;&#x4E4B;&#x524D;&#x5DF2;&#x7ECF;&#x5408;&#x5E76;&#x7684;&#x8BB0;&#x5F55;&#x5B58;&#x50A8;&#xFF0C;
      // &#x800C;resultOrdered=false&#x662F;&#x76F4;&#x63A5;&#x5B58;&#x50A8;&#x7684;&#x540E;&#x7EED;&#x6709;&#x5408;&#x5E76;&#x7684;&#x8BB0;&#x5F55;&#x518D;&#x5904;&#x7406;&#x6DFB;&#x52A0;&#x5230;&#x96C6;&#x5408;&#x5C5E;&#x6027;&#x4E2D;
      if (mappedStatement.isResultOrdered()) {
        // partialObject&#x4E3A;null&#xFF0C;&#x8BF4;&#x660E;&#x8FD9;&#x4E00;&#x6761;&#x8BB0;&#x5F55;&#x4E0D;&#x53EF;&#x4E0E;&#x4E0A;&#x4E00;&#x6761;&#x8BB0;&#x5F55;&#x8FDB;&#x884C;&#x5408;&#x5E76;&#x4E86;&#xFF0C;&#x90A3;&#x4E48;&#x6E05;&#x7A7A;nestedResultObjects&#x9632;&#x6B62;&#x4E4B;&#x540E;&#x51FA;&#x73B0;&#x6709;&#x53EF;&#x5408;&#x5E76;&#x7684;&#x8BB0;&#x5F55;&#x7684;&#x65F6;&#x5019;&#x7EE7;&#x7EED;&#x5408;&#x5E76;
        // &#x7136;&#x540E;&#x5C06;&#x8BB0;&#x5F55;&#x5B58;&#x50A8;&#x5230;resultHandler&#x91CC;&#x9762;
        if (partialObject == null && rowValue != null) {
          nestedResultObjects.clear();
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
      } else {
        // &#x5904;&#x7406;resultSet&#x7684;&#x5F53;&#x524D;&#x8FD9;&#x4E00;&#x6761;&#x8BB0;&#x5F55;
        rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
        if (partialObject == null) {
          // &#x5C06;&#x8BB0;&#x5F55;&#x5B58;&#x50A8;&#x5230;resultHandler&#x91CC;&#x9762;
          storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
        }
      }
    }</object>

这段代码主要是创建了一个缓存key,主要是根据resultMapId和

  private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {
    final String resultMapId = resultMap.getId();
    Object rowValue = partialObject;
    // rowValue&#x4E0D;&#x7B49;&#x4E8E;null&#x65F6;&#xFF0C;&#x8BF4;&#x660E;&#x6B64;&#x6761;&#x8BB0;&#x5F55;&#x53EF;&#x5408;&#x5E76;
    if (rowValue != null) {
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      putAncestor(rowValue, resultMapId);
      applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
      ancestorObjects.remove(resultMapId);
    } else {
      final ResultLoaderMap lazyLoader = new ResultLoaderMap();
      // &#x521B;&#x5EFA;result&#x63A5;&#x6536;&#x5BF9;&#x8C61;&#xFF0C;&#x672C;&#x4F8B;&#x4E2D;&#x662F;UserDO&#x5BF9;&#x8C61;
      rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
      if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
        final MetaObject metaObject = configuration.newMetaObject(rowValue);
        boolean foundValues = this.useConstructorMappings;
        // &#x662F;&#x5426;&#x5C06;&#x67E5;&#x8BE2;&#x51FA;&#x6765;&#x7684;&#x5B57;&#x6BB5;&#x5168;&#x90E8;&#x6620;&#x5C04; &#x9ED8;&#x8BA4;false
        if (shouldApplyAutomaticMappings(resultMap, true)) {
          foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
        }
        // &#x8BBE;&#x7F6E;&#x9700;&#x8981;&#x6620;&#x5C04;&#x7684;&#x5C5E;&#x6027;&#x503C;&#xFF0C;&#x4E0D;&#x7BA1;&#x6709;&#x5D4C;&#x5957;ResultMap&#x7684;
        foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
        // &#x5B58;&#x653E;&#x7B2C;&#x4E00;&#x6761;&#x6570;&#x636E;
        putAncestor(rowValue, resultMapId);
        // &#x5904;&#x7406;&#x6709;&#x5D4C;&#x5957;&#x7684;resultMapping
        foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
        ancestorObjects.remove(resultMapId);
        foundValues = lazyLoader.size() > 0 || foundValues;
        rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
      }
      // &#x5C06;&#x6700;&#x7EC8;&#x7ED3;&#x679C;&#x653E;&#x5165;&#x5230;nestedResultObjects&#x4E2D;
      if (combinedKey != CacheKey.NULL_CACHE_KEY) {
        nestedResultObjects.put(combinedKey, rowValue);
      }
    }
    return rowValue;
  }

getRowValue方法主要是将ResultSet解析为实体类对象,applyPropertyMappings填充

  private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
    boolean foundValues = false;
    for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
      // &#x5D4C;&#x5957;id
      final String nestedResultMapId = resultMapping.getNestedResultMapId();
      // resultMapping&#x6709;&#x5D4C;&#x5957;&#x7684;map&#x624D;&#x7EE7;&#x7EED; <association> <collection>
      if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
        try {
          final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
          // &#x83B7;&#x53D6;&#x5D4C;&#x5957;&#xFF08;&#x7ECF;&#x8FC7;&#x4E00;&#x6B21;&#x9274;&#x6743;&#xFF09;&#x7684;ResultMap
          final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
          if (resultMapping.getColumnPrefix() == null) {
            // try to fill circular reference only when columnPrefix
            // is not specified for the nested result map (issue #215)
            Object ancestorObject = ancestorObjects.get(nestedResultMapId);
            if (ancestorObject != null) {
              if (newObject) {
                linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
              }
              continue;
            }
          }
          // &#x6784;&#x5EFA;&#x5D4C;&#x5957;map&#x7684;key
          final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
          // &#x5408;&#x5E76;cacheKey
          final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
          // &#x5C1D;&#x8BD5;&#x83B7;&#x53D6;&#x4E4B;&#x524D;&#x662F;&#x5426;&#x5DF2;&#x7ECF;&#x521B;&#x5EFA;&#x8FC7;
          Object rowValue = nestedResultObjects.get(combinedKey);
          boolean knownValue = rowValue != null;
          // &#x5B9E;&#x4F8B;&#x5316;&#x96C6;&#x5408;&#x5C5E;&#x6027; list&#x590D;&#x5236;&#x4E3A;&#x7A7A;&#x5217;&#x8868;
          instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
          // &#x5B58;&#x5728;&#x6307;&#x5B9A;&#x7684;&#x975E;&#x7A7A;&#x5217;&#x5B58;&#x5728;&#x7A7A;&#x503C;&#x5219;&#x8FD4;&#x56DE;false
          if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
            rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
            if (rowValue != null && !knownValue) {
              // &#x5408;&#x5E76;&#x8BB0;&#x5F55;&#xFF0C;&#x8BBE;&#x7F6E;&#x5BF9;&#x8C61;-association&#x6216;&#x5C06;&#x5BF9;&#x8C61;&#x6DFB;&#x52A0;&#x5230;&#x96C6;&#x5408;&#x5C5E;&#x6027;&#x4E2D;-collection
              linkObjects(metaObject, resultMapping, rowValue);
              foundValues = true;
            }
          }
        } catch (SQLException e) {
          throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
        }
      }
    }
    return foundValues;
  }</collection></association>

处理嵌套的结果映射,其实就是

private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
    final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
    // &#x5C5E;&#x6027;&#x662F;&#x96C6;&#x5408;&#x8FDB;&#x884C;&#x6DFB;&#x52A0; <collection>
    if (collectionProperty != null) {
      final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
      targetMetaObject.add(rowValue);
    } else {
      // &#x5426;&#x5219;&#x662F;&#x5BF9;&#x8C61; &#x76F4;&#x63A5;&#x8FDB;&#x884C;setter&#x8BBE;&#x7F6E; <association>
      metaObject.setValue(resultMapping.getProperty(), rowValue);
    }
  }</association></collection>

最后就把能合并的记录都合并在一起了,不同的权限映射到permitDOList这个集合中了

1.3

从上面的代码看来,关于

mybatis collection解析以及和association的区别

从上面的图中我们可以看到

另外在使用习惯上因为我们能确认表和表之间的关系是一对一还是一对多的,能够确认pojo类中的属性javaType是使用list还是普通对象,所以一般情况下一对一使用

最后

如果说的有问题欢迎提出指正讨论,代码提交在gitee上,感兴趣的同学可以下载看看

Original: https://www.cnblogs.com/monianxd/p/16477331.html
Author: 默念x
Title: mybatis collection解析以及和association的区别

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/620882/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Redis 串行生成顺序编码

    场景:针对于分布式并发环境,易出现编码生成重复问题方案特点:串行操作可避免阻塞加锁,处理效率更高 具体解决方案 private final static String ENTERP…

    数据库 2023年6月6日
    071
  • 局域网内访问子网服务(访问电脑虚拟机中的服务)

    局域网内访问子网服务 问题描述: 同一个路由器(172.18.0.0)下面有两台电脑A(172.18.40.45)和B (172.18.44.173) ,在B电脑上安装虚拟机 ,使…

    数据库 2023年6月9日
    082
  • MyRocks DDL原理

    最近一个日常实例在做DDL过程中,直接把数据库给干趴下了,问题还是比较严重的,于是赶紧排查问题,撸了下crash堆栈和alert日志,发现是在去除唯一约束的场景下,MyRocks存…

    数据库 2023年6月9日
    087
  • kafka学习

    Kafka概述 Kafka是分布式(点对点模式)(发布-订阅模式)消息系统,由Scala 写成, 它主要用于处理流式数据。本质是基于消息队列缓存数据. Kafka对消息保存时根据T…

    数据库 2023年6月16日
    066
  • 【运维】– Docker基础必知必会(1)

    1.Docker简介 Docker的出现简单地说就是为了解决运行环境和软件配置相关的不一致性问题,采用Docker镜像的方式将软件所需要的运行环境打包。通过Docker build…

    数据库 2023年6月6日
    093
  • springboot~手动加载thymeleaf模版

    thymeleaf在spring-mvc时代很是盛行,与freemaker组成了两大模版引擎,而进行springboot之后,很多项目都采用前后分离的模式,这使得模板引擎关注度少了…

    数据库 2023年6月6日
    0105
  • 慢查询SQL排查

    转载请注明出处❤️ 作者:测试蔡坨坨 原文链接:caituotuo.top/c56bd0c5.html 你好,我是测试蔡坨坨。 在往期文章中,我们聊过数据库基础知识,可参考「数据库…

    数据库 2023年5月24日
    082
  • java通过内存流去掉多行文本中的空行

    对于多行文本,你直接通过replace,replaceAll是不能将空行删除的,你需要遍历这些行,对每行文本进行操作,最后把返回新的文本才行。 public static Stri…

    数据库 2023年6月6日
    076
  • Debezium的基本使用(以MySQL为例)

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源。 GreatSQL是MySQL的国产分支版本,使用上与MySQL一致。 一、Debezium介绍 摘自官…

    数据库 2023年5月24日
    089
  • Java 使用Stream处理List对象去重

    // 根据name去重 List unique = persons.stream().collect( Collectors.collectingAndThen( Collecto…

    数据库 2023年6月16日
    064
  • K8S的安装部署以及基础知识

    Kubernetes(K8S)概述 Kubernetes又称作k8s,是 Google在2014年发布的一个开源项目。 最初Google开发了一个叫 Borg的系统(现在命名为Om…

    数据库 2023年6月6日
    082
  • Redis限制一键登录次数

    一、产生背景 之前的随笔提到过项目中写了一键登录功能、上线后除了有时候网络波动会导致登陆失败,其他情况一直稳如老狗 しかし,邮件看到有人恶意刷一键登录,这年头闲的人可真闲啊,只能思…

    数据库 2023年6月6日
    084
  • MyBatis-Plus修改数据,会不会把其他字段置为null

    前两天在用MyBatis-Plus写了一张单表的增删改查,在写到修改的时候,就突然蹦出一个奇怪的想法。 MyBatis-Plus的BaseMapper中有两个关于修改的方法。如下:…

    数据库 2023年6月11日
    099
  • java使用EasyExcel导入导出excel

    使用alibab的EasyExce完成导入导出excel 一、准备工作 1、导包 org.apache.poi poi 3.17 org.apache.poi poi-ooxml-…

    数据库 2023年6月6日
    075
  • IDEA中Git的使用

    Git在IDEA中的使用 JAVA技术交流群:737698533 创建和导入 创建一个新项目到Gitee上 首先创建一个仓库,勾选上初始化 获取新创建仓库的路径 然后随便在一个文件…

    数据库 2023年6月16日
    073
  • FastDFS安装和简介详细总结

    1、fastDFS简介 1 FastDFS是用c语言编写的一款开源的分布式文件系统。 2 FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用…

    数据库 2023年6月14日
    0104
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球