Mybatis 源码 ∞ :杂七杂八

news2024/11/27 3:41:00

文章目录

  • 一、前言
  • 二、TypeHandler
  • 三、KeyGenerator
  • 四、Plugin
    • 1 Interceptor
    • 2 org.apache.ibatis.plugin.Plugin
    • 3. 调用场景
  • 五、Mybatis 嵌套映射 BUG
    • 1. 示例
    • 2. 原因
    • 3. 解决方案
  • 六、discriminator 标签
  • 七、其他
    • 1. RowBounds
    • 2. ResultHandler
    • 3. @MapKey

一、前言

Mybatis 官网 以及 本系列文章地址:

  1. Mybatis 源码 ① :开篇
  2. Mybatis 源码 ② :流程分析
  3. Mybatis 源码 ③ :SqlSession
  4. Mybatis 源码 ④ :TypeHandler
  5. Mybatis 源码 ∞ :杂七杂八

主要是 Mybatis 的一些杂七杂八的内容,用于自己可以快速定位一些问题,所以部分内容写比较随性

二、TypeHandler

关于 TypeHandler 的使用,各处都是文章,这里就不再贴出完整的项目,仅对关键内容进行说明。


  1. 注册或声明 TypeHandler :
    • 通过 mybatis.type-handlers-package 直接指定包路径 :该路径下的 TypeHandler 实现类都会被自动注册,并且只要是符合转换类型无论是入参还是出参都会经过转换

      mybatis.type-handlers-package=com.kingfish.config.handler
      
    • Xml 中 通过如下标签注册,可以指定注册哪些 TypeHandler,并且只要是符合转换类型无论是入参还是出参都会经过转换。

      <configuration>
          <typeHandlers>
              <typeHandler handler="com.kingfish.config.handler.PwdTypeHandler"/>
          </typeHandlers>
      </configuration>
      
    • <result> 标签 通过 typeHandler 属性指定,指定某个属性使用 TypeHandler 查询,需要注意的是,仅仅是返回类型是当前 ResultMap 时才会进行类型转换:

      <resultMap id="BaseResultMap" type="com.kingfish.entity.SysUser">
          <result property="password" column="password" jdbcType="VARCHAR" typeHandler="com.kingfish.config.handler.PwdTypeHandler"/>
      </resultMap>
      

  1. 定义密码加解密类型转换器 : PwdTypeHandler。密码不能明文存储在库中,所以当我们需要对DB 中的密码进行加密处理。这里便可以通过 TypeHandler 来实现(在新增、更新、删除时自动加密,在查询时自动解密)

    public class PwdTypeHandler extends BaseTypeHandler<String> {
        private static final SymmetricCrypto AES = new SymmetricCrypto(
                SymmetricAlgorithm.AES, "1234567890123456".getBytes());
    
        @Override
        public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
            ps.setString(i, AES.encryptBase64(parameter));
        }
    
        @Override
        public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
            return AES.decryptStr(rs.getString(columnName));
        }
    
        @Override
        public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
            return AES.decryptStr(rs.getString(columnIndex));
        }
    
        @Override
        public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
            return AES.decryptStr(cs.getString(columnIndex));
        }
    }
    

需要注意的是

  1. 如果以注册的方式(mybatis.type-handlers-package 或者 <typeHandlers> 标签)注册该 TypeHandler。只要是符合其类型转换的情况都会使用该处理器转化,如上面 PwdTypeHandler 转换类型是 String,即只要字段类型是 String,都会被该处理器处理,比如 user_name 也是 String 类型,入库后也会被加密。这种情况并非我们想要的。所以我们可以通过自定义复杂类型的方式来避免将其他类型转换,或者通过下面 标签属性 的方式来转换。

  2. 如果是通过 标签的 typeHandler 属性指定,则只会在查询返回结果时对指定结果集中的指定字段进行处理。

    	<!-- 返回转换(忽略了其他字段) -->
        <resultMap id="BaseResultMap" type="com.kingfish.entity.SysUser">
            <result property="password" column="password" jdbcType="VARCHAR" typeHandler="com.kingfish.config.handler.PwdTypeHandler"/>
        </resultMap>
    	<!-- 插入转换 --> 
        <insert id="insert" keyProperty="id" useGeneratedKeys="true" >
            insert into sys_user(create_time, modify_time, user_name, password, status, is_delete, nick_name, phone, extend)
            values (#{createTime}, #{modifyTime}, #{userName}, #{password, typeHandler=com.kingfish.config.handler.PwdTypeHandler}, #{status}, #{isDelete}, #{nickName}, #{phone}, #{extend})
        </insert>
        
        <!-- 更新转换(忽略了其他字段)-->
        <update id="update">
            update sys_user
            <set>
                <if test="password != null and password != ''">
                    password = #{password, typeHandler=com.kingfish.config.handler.PwdTypeHandler}
                </if>
            </set>
            where id = #{id}
        </update>
    


三、KeyGenerator

在Mybatis中,执行insert操作时,如果我们希望返回数据库生成的自增主键值,那么就需要使用到KeyGenerator对象。

关于 KeyGenerator 的内容,这里直接摘取 Mybatis之KeyGenerator 的部分内容,详细部分请阅读原文


KeyGenerator 定义如下:

public interface KeyGenerator {
  // BaseStatementHandler 构造函数中调用,在sql 执行前调用
  void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
  // StatementHandler#update 中会调用,在sql 执行后调用
  void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);

}

存在如下三个实现类:

  • Jdbc3KeyGenerator:用于处理数据库支持自增主键的情况,如MySQL的auto_increment。
  • NoKeyGenerator:空实现,不需要处理主键。
  • SelectKeyGenerator:用于处理数据库不支持自增主键的情况,比如Oracle的sequence序列。

下面以 Jdbc3KeyGenerator 为例简单看下

@Override
  public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) {
    processBatch(ms, stmt, parameter);
  }

  public void processBatch(MappedStatement ms, Statement stmt, Object parameter) {
  	// 获取key属性名,一般来说即 id,说明 key 就是属性名为 id 的字段	
    final String[] keyProperties = ms.getKeyProperties();
    if (keyProperties == null || keyProperties.length == 0) {
      return;
    }
    try (ResultSet rs = stmt.getGeneratedKeys()) {
      final ResultSetMetaData rsmd = rs.getMetaData();
      final Configuration configuration = ms.getConfiguration();
      // 如果列的长度小于 key的长度则不处理
      if (rsmd.getColumnCount() < keyProperties.length) {
        // Error?
      } else {
      	// 赋值key
        assignKeys(configuration, rs, rsmd, keyProperties, parameter);
      }
    } catch (Exception e) {
      throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e);
    }
  }

  private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties,
      Object parameter) throws SQLException {
    if (parameter instanceof ParamMap || parameter instanceof StrictMap) {
      // Multi-param or single param with @Param
      // 多个参数或单一参数 使用 @Param 场景
      assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter);
    } else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty()
        && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) {
      // Multi-param or single param with @Param in batch operation
       // 多个参数或单一参数 使用 @Param 批量操作的场景
      assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter);
    } else {
      // Single param without @Param
      // 单个参数未使用 @Param 的场景
      assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter);
    }
  }

下面以单个参数未使用 @Param 场景为例

  private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd,
      String[] keyProperties, Object parameter) throws SQLException {
     // 将对象转换为 集合,就是简单封装
    Collection<?> params = collectionize(parameter);
    if (params.isEmpty()) {
      return;
    }
    List<KeyAssigner> assignerList = new ArrayList<>();
    for (int i = 0; i < keyProperties.length; i++) {
      assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i]));
    }
    Iterator<?> iterator = params.iterator();
    // 遍历参数
    while (rs.next()) {
      if (!iterator.hasNext()) {
        throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size()));
      }
      // 获取参数
      Object param = iterator.next();
      // 反射将Key 值映射到 参数对应的属性上 (即将id的值映射到 param 的id 属性上)
      assignerList.forEach(x -> x.assign(rs, param));
    }
  }

四、Plugin

Mybatis支持我们通过插件的方式扩展具体的过程,我们可以通过如下方式:

// 声明当前类是个拦截器,拦截的类型是 StatementHandler,方法名是 prepare,该方法的入参 Connection 和 Integer 类型。
// 当 StatementHandler 的 prepare  方法执行时会被该拦截器拦截
@Component
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class, Integer.class}) })
public class DemoPlugins implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("invocation = " + invocation);
        return null;
    }
}

下面我们来看看代码的具体实现

在上面我们提到负责执行Sql的 Executor 被 Interceptor 包装了,实际上并非仅仅只有 执行器会被拦截器拦截,因此我们这里来看看 Mybatis 拦截器的具体实现。

如下是 InterceptorChain#pluginAll 的实现,当创建 Executor、ParameterHandler、ResultSetHandler、StatementHandler 时都会调用该方法:

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

可以看到,该方法会通过 Interceptor#plugin 方法对 target 进行包装,具体如下:

1 Interceptor

org.apache.ibatis.plugin.Interceptor 定义如下:

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
  	// 使用当前对象包装 target
    return Plugin.wrap(target, this);
  }
  // XML 解析 interceptor 时会调用该方法进行属性赋值,具体看实现
  default void setProperties(Properties properties) {
    // NOP
  }

}

这里可以看到,Mybatis 通过 Plugin#wrap 方法代理并返回了一个新的对象。下面我们来看下 org.apache.ibatis.plugin.Plugin 的具体实现。

2 org.apache.ibatis.plugin.Plugin

org.apache.ibatis.plugin.Plugin#wrap 实现如下:

  public static Object wrap(Object target, Interceptor interceptor) {
    // 1. 获取方法签名
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // 获取 type 的所有实现接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
     // 创建新的代理对象,这里看到,处理器实际上是Plugin
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
  // 解析Intercepts注解并获取方法签名
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    // 获取 @Intercepts 注解信息
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
    }
    // 获取 @Intercepts  注解的 @Signature 签名信息
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    for (Signature sig : sigs) {
     // 创建 代理方法集合,被代理的方法会保存到该 Set 中
      Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
      try {
      	// 获取 @Signature.type 指定的类,方法名为 sig.method(),参数为 sig.args() 的方法
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    // 返回代理方法签名
    return signatureMap;
  }

可以看到,这里会为 target 创建一个代理对象,代理处理器由 Plugin 来担任,Plugin#invoke 方法如下:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 从代理方法签名中获取当前类的代理方法,如果当前方法需要代理则进行代理,否则执行调用
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
      	// 执行代理拦截器,这里 interceptor 实际上是 Interceptor 的实现类,也就是 Mybatis 的插件类
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

3. 调用场景

在 Mybatis 中,插件的包装调用都在 Configuration 中,如下

 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
      ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
  
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }


这里可以看到Mybatis Plugin 的实现还是比较简单的,通过注解解析,来创建对应类的对应方法的拦截器,(如 PageHelper 的实现核心就是通过 com.github.pagehelper.PageInterceptor 来完成的。)

五、Mybatis 嵌套映射 BUG

1. 示例

Mybatis 嵌套映射在行数据完全相同时 (这里的行数据完全相同指的是sql 查询出来的数据万完全相同,而非 Mybatis 的ResultMap 映射的字段的值完全相同)会丢失的缺陷,以下面为例子 :

  1. sys_user 表数据如下
    在这里插入图片描述

  2. sys_role 数据如下
    在这里插入图片描述

  3. 执行如下SQL, 该 Sql 目的是为了查询有几个用户具有admin 权限,这里可以看到使用了Left join 所以会返回两条完全相同的数据:

            SELECT
                sr.*,
                su.user_name user_user_name,
                su.PASSWORD user_password
            FROM
                sys_role sr
                    LEFT JOIN sys_user su ON sr.id = su.role_id
            where sr.id = 1
    

    执行结果如下:在这里插入图片描述

  4. 但实际上如果通过Mybatis 执行上述逻辑则会出现错误结果如下:

    SysRoleDto 如下,这里不再贴出SysUser:

    public class SysRoleDto {
    
        /**
         * 自增主键ID
         */
        private Long id;
        /**
         * 用户名
         */
        private String roleName;
        /**
         * 状态
         */
        private String status;
    
        /**
         * 用户
         */
        private List<SysUser> sysUsers;
    }
    

    Mapper 如下:

    <mapper namespace="com.kingfish.dao.SysRoleDao">
        <resultMap id="BaseResultMap" type="com.kingfish.entity.SysRole">
            <result property="id" column="id" jdbcType="INTEGER"/>
            <result property="roleName" column="role_name" jdbcType="VARCHAR"/>
            <result property="status" column="status" jdbcType="VARCHAR" />
            <!-- 忽略余下属性 -->
        </resultMap>
    
        <!-- 内部嵌套映射 -->
        <resultMap id="InnerNestMap" type="com.kingfish.entity.dto.SysRoleDto" extends="BaseResultMap">
            <!-- 指定 sysUsers 属性都是前缀为 user_ 的属性 -->
            <collection property="sysUsers" columnPrefix="user_"
                        resultMap="com.kingfish.dao.SysUserDao.BaseResultMap"></collection>
        </resultMap>
        <!-- 通过联表查询出来多个属性,如果属性名跟 sysUsers 对应的com.kingfish.dao.SysUserDao.BaseResultMap配置的属性名一致则会映射上去 (属性名映射规则受到columnPrefix影响) -->
        <select id="selectRoleUser" resultMap="InnerNestMap">
            SELECT
                sr.*,
                su.user_name user_user_name,
                su.PASSWORD user_password
            FROM
                sys_role sr
                    LEFT JOIN sys_user su ON sr.id = su.role_id
            where sr.id = 1
        </select>
    </mapper>
    
  5. 执行结果如下,可以发现 sysUsers 属性少了一条记录,因为这里两条查询的记录相同 在nestedResultObjects 中被判断已经存在。
    在这里插入图片描述

  6. 如果我们把其中一个【张三】改成【李四】,其余全都不动,那么sysUsers两条记录数据就不相同,则不会出现这种问题,如下:
    在这里插入图片描述
    执行结果如下:
    在这里插入图片描述


2. 原因

该缺陷的原因在于在 Mybatis 中会缓存嵌套对象到 DefaultResultSetHandler#nestedResultObjects 中,而缓存的key 的生成策略可以简单理解为 resultMapid + 属性名 + 属性值。而上面的例子中 Sql正常执行是如下数据,可以看到查出来的两行数据完全相同:
在这里插入图片描述
当处理第一条数据时一切正常,而因为是嵌套映射则会将当前行数据缓存到 DefaultResultSetHandler#nestedResultObjects 中。当处理到第二条数据时,
在 DefaultResultSetHandler#applyNestedResultMappings 方法中从 nestedResultObjects 获取到了缓存,从而不会将该行数据保存, 如下图:

在这里插入图片描述


3. 解决方案

解决方案就是保证两行数据不完全相同,比如这里可以通过增加 sys_user 的id 查询保证数据的唯一性, 如下:

  SELECT
      sr.*,
      su.id user_id,
      su.user_name user_user_name,
      su.PASSWORD user_password
  FROM
      sys_role sr
          LEFT JOIN sys_user su ON sr.id = su.role_id
  where sr.id = 1

在这里插入图片描述

六、discriminator 标签

我们以下面的情况为例:

    <resultMap id="CollectionBaseResultMap" type="com.kingfish.entity.dto.SysUserDto" extends="BaseResultMap">
        <discriminator javaType="java.lang.Integer" column="id">
        	<!-- value = '1' 的情况下是 resultType, Mybatis会为resultType自动生成一个 ResultMap, discriminatedMapId  是 com.kingfish.dao.SysUserDao.mapper_resultMap[CollectionBaseResultMap]_discriminator_case[1]  -->
            <case value="1" resultType="com.kingfish.entity.dto.SysUserDto">
                <result column="user_name" property="extend1"/>
            </case>
            <!-- value = '1' 的情况下是 resultMap, discriminatedMapId 即为 CollectionBaseResultMap 的id : com.kingfish.dao.SysUserDao.CollectionBaseResultMap-->
            <case value="2" resultMap="CollectionBaseResultMap">
                <result column="nick_name" property="extend1"/>
            </case>
        </discriminator>
    </resultMap>

这里需要注意 :

  1. discriminator 标签中 case 中使用 resultType 和 resultMap 的 discriminatedMapId 并不相同, 返回类型是 resultType 时 则会自动生成一个 ResultMap,
  2. resultType情况下需要自己重新对名字进行转换,因为没有 ResultMap 的转换,变量名无法对应。resultMap情况下会忽略 case 条件下的Result ,因为直接从缓存中获取之前加载好的 CollectionBaseResultMap结构了。

七、其他

1. RowBounds

Mybatis可以通过传参中的 RowBounds 可以完成逻辑分页,但不推荐,因为所有的数据都是查询到内存中再筛选。如下:

	// 逻辑分页查询 :入参中有 RowBounds 参数
    List<SysMenuDto> selectByParam(RowBounds rowBounds);

2. ResultHandler

Mybatis可以通过传参中的 ResultHandler 可以结果集处理,而不再通过 Mapper Method 方法再返回结果,如果不指定,则默认是通过 DefaultResultHandler 来处理。如下:

	// 无返回值 && 入参中有 ResultHandler 实例
    void selectByParam(ResultHandler resultHandler);

官方对 ResultHandler 的说明【ResultHandler 参数允许自定义每行结果的处理过程。你可以将它添加到 List 中、创建 Map 和 Set,甚至丢弃每个返回值,只保留计算后的统计结果。你可以使用 ResultHandler 做很多事,这其实就是 MyBatis 构建 结果列表的内部实现办法。】

需要注意的是

  • ResultHandler 要求方法必须无返回值,在 MapperMethod#execute 中会判断进行该判断:
    在这里插入图片描述

  • DefaultResultSetHandler#handleResultSet 中判断了如果指定了 ResultHandler 则使用指定的,否则使用 DefaultResultHandler:

    在这里插入图片描述

3. @MapKey

官方描述 :供返回值为 Map 的方法使用的注解。它使用对象的某个属性作为 key,将对象 List 转化为 Map。属性:value,指定作为 Map 的 key 值的对象属性名。

即: 当一个查询方法想要返回 Map 时,可以通过 @MapKey 来指定用来聚合的key 是什么字段,如下:

        <select id="selectRoleForMap" resultMap="BaseResultMap">
            select *
            from sys_role
        </select
    @MapKey("id")
    Map<Long, SysRoleDto> selectRoleForMap();

查询结果会把 id 当做 Map 的key 字段来聚合,返回如下:
在这里插入图片描述


源码处理逻辑在 :org.apache.ibatis.binding.MapperMethod#executeForMap 中,调用 DefaultSqlSession#selectMap 方法来处理,这里会交由 DefaultMapResultHandler 来处理结果, 将结果封装成对应的 Map。

在这里插入图片描述


以上:内容部分参考
https://www.jianshu.com/p/05f643f27246
https://juejin.cn/post/6844904127818891278
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/875765.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

无涯教程-Perl - seekdir函数

描述 此功能将DIRHANDLE中的当前位置设置为POS。 POS的值必须是Telldir先前返回的值。 seekdir()函数类似于Unix seekdir()系统调用。 语法 以下是此函数的简单语法- seekdir DIRHANDLE, POS返回值 如果失败,此函数返回0,如果成功,则返回1。 例 以下是显示其基本用法的…

VMware Workstation 如何启用复制粘贴

产品&#xff1a;VMware Workstation 16 Pro 版本&#xff1a;16.1.1 build-17801498 我们刚安装好的 VMware Workstation 会发现无法复制粘贴文件到虚拟机中&#xff0c;如下为解决方案&#xff1a; 1.点击 虚拟机&#xff0c;点击 安装 VMware Tools(T)...。 2.虚拟机下面会…

从零实战SLAM-第五课(最小二乘法)

在七月算法报的班&#xff0c;老师讲的蛮好。好记性不如烂笔头&#xff0c;关键内容还是记录一下吧&#xff0c;课程入口&#xff0c;感兴趣的同学可以学习一下。 --------------------------------------------------------------------------------------------------------…

Windows 搜索指定软件启动路径

Windows 搜索指定软件启动路径 通过指定软件名称&#xff0c;使用命令搜索软件安装路径 # 示例&#xff1a;搜索“钉钉”的启动路径 echo off rem 指定待搜索的文件 set "FileNameDingTalk.exe" rem echo 正在搜索&#xff0c;请稍候... for %%a in (C D E F G H I J…

javaswing人事管理系统企业员工工资管理mysql数据库MVC三层框架gui源代码

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、主要功能 人事管理&#xff1a;添加员工、工资管理、部门管理 …

sykwalking8.2和mysql5.7快速部署

1.SkyWalking 是什么&#xff1f; 分布式系统的应用程序性能监视工具&#xff0c;专为微服务、云原生架构和基于容器&#xff08;Docker、K8s、Mesos&#xff09;架构而设计。 提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。 2.SkyWalking 有哪些功能…

考公-判断推理-定义判断

第九节课 例题 例题 例题 例题 例题 例题 脚一滑&#xff0c;就是工伤&#xff0c;这难道不是操作不当吗 例题 不要较真&#xff0c;公务员&#xff0c;把没有全局观念的人排除在公务员队伍之外 例题 例题 下次看到不字&#xff0c;先给我画上 例题 例题 例题 例题…

Scratch 之 3D 画笔程序使用

目录 Part1 摄像头固定的3D效果 Part2 尝试移动摄像头 Part3 边缘裁剪 总结&#xff1a; Part1 摄像头固定的3D效果 首先&#xff0c;我们知道sc中有xy坐标。 现在让我们在sc中引入一个新坐标——z坐标。z轴垂直于电脑屏幕&#xff0c;从屏幕外指向屏幕里。(如下图) z坐标…

O2OA (翱途) o2server 调用 webServices jaxws 样例

本文分两部分介绍如何在 o2server 服务器中调用 webServices(jaxws)服务. 第一部分介绍如何在tomcat上搭建一个webServices(jaxws)服务. 第二部分介绍如何在o2server服务器上来调用上面创建的服务. O2OA (翱途)官网&#xff1a;http://www.o2oa.net 一、在tomcat上搭建一个…

Redis辅助功能

一、Redis队列 1.1、订阅 subscribe ch1 ch2 1.2 publish:发布消息 publish channel message 1.3 unsubscribe: 退订 channel 1.4 模式匹配 psubscribe ch* 模糊发布&#xff0c;订阅&#xff0c;退订&#xff0c; p* <channelName> 1.5 发布订阅原理 订阅某个频道或…

【正点原子STM32连载】第五章 APM32基础知识入门摘自【正点原子】APM32F407最小系统板使用指南

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html# 第五…

电脑提示数据错误循环冗余检查怎么办?

有些时候&#xff0c;我们尝试在磁盘上创建分区或清理硬盘时&#xff0c;还可能会遇到这个问题&#xff1a;数据错误循环冗余检查。这是如何导致的呢&#xff1f;我们又该如何解决这个问题呢&#xff1f;下面我们就来了解一下。 导致冗余检查错误的原因有哪些&#xff1f; 数据…

使用AT命令操作Modem 3G/4G模块

1. 引言 AT命令是一种通信协议&#xff0c;用于控制和配置各种设备&#xff0c;尤其在通信领域中具有重要性。它的名称来源于"ATtention"&#xff08;注意&#xff09;&#xff0c;因为命令通常以"AT"开头。AT命令最早被用于调制解调器&#xff0c;用于与…

驱动阿托斯DLHZO-T伺服比例阀放大器定制

DLHZO-T型伺服比例换向阀&#xff0c;直动式&#xff0c;带LVDT位置传感器和阀芯零遮盖&#xff0c;可应用于各种位置闭环控制实现最佳的性能。 比例阀和模块式数字放大器配合使用。 LVDT传感器和阀套结构可确保非常高的调节精度和响应灵敏度。 失电保护位可实现在电源中断的…

一文读懂3D开发工具HOOPS SDK

近年来&#xff0c;随着对定制软件开发需求的增加&#xff0c;我们也目睹了新的软件开发工具和技术的加入。 大部分企业在移动和Web应用程序开发上投入了大量的精力&#xff0c;这表明市场对技术软件解决方案的需求在增加。然而&#xff0c;在开发软件的过程中&#xff0c;是可…

基于 SIFT 和 RANSAC 算法对高分辨率图像进行图像伪造检测(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

kafka基本概念及操作

kafka介绍 Kafka是最初由Linkedin公司开发&#xff0c;是一个分布式、支持分区的&#xff08;partition&#xff09;、多副本的 &#xff08;replica&#xff09;&#xff0c;基于zookeeper协调的分布式消息系统&#xff0c;它的最大的特性就是可以实时的处理大量数据以满足各…

技术解析丨主轴自动换刀系统是如何工作的?有哪些优点?

一、主轴气动自动换刀系统原理 1.当加工过程中需要更换主轴上的刀具时&#xff0c;操作人员通过控制系统发出换刀指令。 2.控制系统根据指令向气动系统发送动作信号&#xff0c;驱动气动马达带动换刀机构运动。 3.换刀机构中的刀具夹持器将现有刀具从主轴上取下&#xff0c;…

FreeRTOS(事件组)

资料来源于硬件家园&#xff1a;资料汇总 - FreeRTOS实时操作系统课程(多任务管理) 目录 一、事件的概念与应用 1、事件的概念 2、事件的应用 二、事件的运作机制 1、FreeRTOS中事件组的句柄 2、FreeRTOS 任务间事件标志组的实现 3、FreeRTOS 中断方式事件标志组的实现…

vue2学习:reduce方法和computed计算属性用法

reduce reduce可以遍历集合并将集合所有的值汇总为一个。 第一个参数是一个回调函数&#xff0c;函数第一个参数是汇总起来的最终值&#xff0c;默认是集合的第一项&#xff0c;函数第二个参数是集合遍历出来的集合元素&#xff1b; 第二个参数可以指定回调函数中第一个参数汇…