当前位置: 首页 > news >正文

MyBatis异常体系中ErrorContext和ExceptionFactory原理分析

🎮 作者主页:点击
🎁 完整专栏和代码:点击
🏡 博客主页:点击

文章目录

  • exceptions包
  • 分包设计
  • ExceptionFactory类
    • 介绍
    • 为什么使用工厂不是直接new呢?
      • 【统一的异常处理机制】
      • 【异常的封装与转化】
      • 【异常上下文(ErrorContext)】
      • 【扩展性】
  • ErrorContext 的作用
    • 概述
      • 代码
    • store()和recall()方法设计分析
    • 为什么需要 store 和 recall?

exceptions包

exceptions包为 MyBatis定义了绝大多数异常类的父类,同时也提供了异常类的生产工厂
在这里插入图片描述
在这里插入图片描述
MyBatis中异常类类图

分包设计

通过 MyBatis异常类的类图还可以看出,众多的异常类并没有放在 exceptions包中,而是散落在其他各个包中。这涉及项目规划时的分包问题。通常,在规划一个项目的包结构时,可以按照以下两种方式进行包的划分。
按照类型方式划分,例如将所有的接口类放入一个包,将所有的 Controller类放入一个包。这种分类方式从类型上看更为清晰,但是会将完成同一功能的多个类分散在不同的包中,不便于模块化开发。
按照功能方式划分,例如将所有与加/解密有关的类放入一个包,将所有与 HTTP请求有关的类放入一个包。这种分类方式下,同一功能的类内聚性高,便于模块化开发,但会导致同一包内类的类型混乱

通常,在进行一个项目的包结构设计时会同时采用以上两种划分方式。exceptions包就是按照类型划分出来的,但也有许多异常类按照功能划分到了其他包中。MyBatis 中的包也是按照上述两种方式划分的,一类是按照类型划分出来的包,如exceptions包、annotations包;一类是按照功能划分出来的包,如 logging包、plugin包。

在项目设计和开发中,我们推荐优先将功能耦合度高的类放入按照功能划分的包中,而将功能耦合度低或供多个功能使用的类放入按照类型划分的包中。这种划分思想不仅可以用在包的划分上,类、方法、代码片段的组合与拆分等都可以参照这种思想。

ExceptionFactory类

介绍

该类是负责生产 Exception的工厂。ExceptionFactory类只有两个方法。构造方法由 private修饰,确保该方法无法在类的外部被调用,也就永远无法生成该类的实例。通常,会对一些工具类、工厂类等仅提供静态方法的类进行这样的设置,因为这些类不需要实例化就可以使用。wrapException方法就是 ExceptionFactory类提供的静态方法,它用来生成并返回一个RuntimeException对象。

  @Overridepublic void rollback(boolean force) {try {executor.rollback(isCommitOrRollbackRequired(force));dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);} finally {ErrorContext.instance().reset();}}

/*** @author Clinton Begin*/
public class ExceptionFactory {private ExceptionFactory() {// Prevent Instantiation}public static RuntimeException wrapException(String message, Exception e) {return new PersistenceException(ErrorContext.instance().message(message).cause(e).toString(), e);}}

为什么使用工厂不是直接new呢?

【统一的异常处理机制】

通过 ExceptionFactory.wrapException 来封装异常,MyBatis 可以提供一个统一的异常处理方式。这样做的好处是,在整个系统中无论遇到什么异常,都会通过 wrapException 进行统一包装,并且可以附带有一致的异常信息(比如 ErrorContext 中的消息和堆栈信息)。这种方式增强了异常的一致性,方便了异常的管理和日志记录。

【异常的封装与转化】

直接 new 一个异常是可以的,但 MyBatis 通过 wrapException 将异常转化为 PersistenceException,这种做法有助于将底层异常封装成一个业务层异常(如 PersistenceException),而不是让底层的异常类型暴露给上层调用者。这样上层代码就不需要关心底层的具体实现细节(如 SQLException 或其他数据库相关的异常),只需要处理通用的业务异常(PersistenceException)。这也是常见的“异常转译”或“异常抽象”设计模式。

【异常上下文(ErrorContext)】

ErrorContext.instance().message(message).cause(e).toString() 通过 ErrorContext 来追踪异常的上下文信息,提供更多的诊断信息。这对于调试和日志分析非常有用,可以提供异常发生时的详细背景、错误信息以及导致异常的根本原因。这使得开发者可以更容易地定位问题,提升系统的可维护性。

【扩展性】

使用 ExceptionFactory 封装异常,意味着未来如果需要改变异常的处理方式(例如,加入更多的异常日志、不同的异常类型等),只需要在 wrapException 方法中做修改,而不需要修改系统中每个直接抛出异常的地方。这提高了代码的可维护性和可扩展性。
通过 ExceptionFactory.wrapException 这样的设计,MyBatis 实现了异常的统一封装、转化和上下文管理,增强了系统的可维护性、可调试性和可扩展性。直接 new 一个异常虽然简单,但缺乏统一的异常处理和额外的上下文信息,容易导致异常管理混乱。

ErrorContext 的作用

概述

MyBatis 中的 ErrorContext 设计主要用于捕获和存储在执行 SQL 操作过程中发生的错误的相关上下文信息。它通过提供详细的错误上下文信息来帮助开发人员调试和排查问题,尤其是在与数据库交互时发生异常时。ErrorContext 类主要的作用是封装和管理错误相关的诊断信息,使得错误日志更加易于理解和追踪。

代码

public class ErrorContext {private static final String LINE_SEPARATOR = System.lineSeparator();private static final ThreadLocal<ErrorContext> LOCAL = ThreadLocal.withInitial(ErrorContext::new);private ErrorContext stored;private String resource;private String activity;private String object;private String message;private String sql;private Throwable cause;private ErrorContext() {}public static ErrorContext instance() {return LOCAL.get();}public ErrorContext store() {ErrorContext newContext = new ErrorContext();newContext.stored = this;LOCAL.set(newContext);return LOCAL.get();}public ErrorContext recall() {if (stored != null) {LOCAL.set(stored);stored = null;}return LOCAL.get();}public ErrorContext resource(String resource) {this.resource = resource;return this;}public ErrorContext activity(String activity) {this.activity = activity;return this;}public ErrorContext object(String object) {this.object = object;return this;}public ErrorContext message(String message) {this.message = message;return this;}public ErrorContext sql(String sql) {this.sql = sql;return this;}public ErrorContext cause(Throwable cause) {this.cause = cause;return this;}public ErrorContext reset() {resource = null;activity = null;object = null;message = null;sql = null;cause = null;LOCAL.remove();return this;}@Overridepublic String toString() {StringBuilder description = new StringBuilder();// messageif (this.message != null) {description.append(LINE_SEPARATOR);description.append("### ");description.append(this.message);}// resourceif (resource != null) {description.append(LINE_SEPARATOR);description.append("### The error may exist in ");description.append(resource);}// objectif (object != null) {description.append(LINE_SEPARATOR);description.append("### The error may involve ");description.append(object);}// activityif (activity != null) {description.append(LINE_SEPARATOR);description.append("### The error occurred while ");description.append(activity);}// sqlif (sql != null) {description.append(LINE_SEPARATOR);description.append("### SQL: ");description.append(sql.replace('\n', ' ').replace('\r', ' ').replace('\t', ' ').trim());}// causeif (cause != null) {description.append(LINE_SEPARATOR);description.append("### Cause: ");description.append(cause.toString());}return description.toString();}}

ErrorContext 的设计可以帮助 MyBatis 更好地追踪和记录执行过程中发生的异常和错误信息。它提供了关于 SQL 执行过程中各个步骤的详细信息,例如:当前正在执行的 SQL 语句、相关的映射文件、参数信息等,这些都可以帮助开发人员快速定位问题。

store()和recall()方法设计分析

在 MyBatis 中,private ErrorContext stored; 这行代码通常用于 保存当前线程的错误上下文。具体来说,stored 变量通常是一个 ErrorContext 类型的对象,它的作用是暂时存储和保存错误相关的信息,以便在整个操作过程中能够访问和更新。

 private ErrorContext stored;public ErrorContext store() {ErrorContext newContext = new ErrorContext();newContext.stored = this;LOCAL.set(newContext);return LOCAL.get();}public ErrorContext recall() {if (stored != null) {LOCAL.set(stored);stored = null;}return LOCAL.get();}

store() 方法
• 这个方法的作用是 保存当前的错误上下文 到一个新的 ErrorContext 实例中,并将其设置为当前线程的错误上下文。
• newContext.stored = this:这行代码的意思是,将当前的 ErrorContext(即 this)存储到新创建的 newContext 中,这样可以在之后恢复原来的上下文。
• LOCAL.set(newContext):将新创建的上下文对象设置为当前线程的 ErrorContext。由于使用了 ThreadLocal,每个线程都会有独立的错误上下文。
• return LOCAL.get():返回当前线程的 ErrorContext(即 newContext)。

store() 方法一般在 操作开始时调用,用于 保存当前线程的错误上下文,并为后续的错误信息提供独立的上下文。比如,当处理一个新的数据库操作时,可能需要清空当前的错误上下文并开始一个新的上下文。在执行某个操作时,如果错误发生,则可以在新的上下文中记录错误信息。

recall() 方法通常用于 操作完成后,恢复原来的错误上下文,特别是在跨越多个操作的错误追踪中,恢复原来的上下文信息。比如,在处理完某个操作后,系统可能需要恢复到先前的错误上下文,以便继续处理其他操作或者记录原有的错误信息。

  protected void generateKeys(Object parameter) {KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();ErrorContext.instance().store();keyGenerator.processBefore(executor, mappedStatement, null, parameter);ErrorContext.instance().recall();}

上面这段代码就是实际mybatis使用store和recall地方。
这段代码出现在 MyBatis 中,通常是在执行某些数据库操作时生成主键的过程,尤其是在插入操作中,涉及到主键生成的逻辑。generateKeys() 方法负责生成数据库操作后的主键,并确保在生成主键的过程中能够正确管理和追踪错误上下文。让我们逐行分析这段代码,理解其具体含义和作用:

protected void generateKeys(Object parameter) {// 获取与当前映射语句相关的 KeyGeneratorKeyGenerator keyGenerator = mappedStatement.getKeyGenerator();// 存储当前线程的错误上下文,防止在主键生成过程中出现错误时丢失上下文ErrorContext.instance().store();// 在执行主键生成前,调用 KeyGenerator 的处理方法keyGenerator.processBefore(executor, mappedStatement, null, parameter);// 恢复之前存储的错误上下文,确保其他操作的错误上下文不受影响ErrorContext.instance().recall();
}

为什么需要 store 和 recall?

在 MyBatis 中,ErrorContext 负责捕获和存储当前错误的上下文信息。在多步骤的操作中(如插入操作生成主键时),有时会因为主键生成的策略(如自增长主键)涉及到复杂的数据库操作,因此需要确保在生成主键之前,当前的错误上下文能够被保存,避免在主键生成时如果发生错误丢失上下文信息。
通过调用 store() 方法,保存当前错误上下文,并在执行主键生成逻辑后通过 recall() 恢复之前的上下文,能够确保错误信息在不同的操作之间得到正确的隔离和处理。这种方式使得 MyBatis 可以精确地记录并跟踪错误,尤其是在复杂的多步骤操作中,避免错误上下文的混乱。

上面方法的最外层是DefaultSqlSession#update(java.lang.String, java.lang.Object)

  @Overridepublic int update(String statement, Object parameter) {try {dirty = true;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();}}

上面会捕获所有的异常并使用ExceptionFactory,而里面输出的信息就是ErrorContext存储的。
假设执行keyGenerator.processBefore(executor, mappedStatement, null, parameter);出现数据库异常了,那么此时可能抛出SQLException异常,而且在processBefore中会执行很多的自定义的操作,这些可能导致ErrorContext中存储的信息被修改了,那么之前在processBefore前保存的上下文信息就丢失了,这就会导致最终异常信息输出的时候只有processBefore的信息了,而丢失了原来的上下文信息,在使用了store方法后,先将这个前面的上下文信息保存,这样的话,假设在processBefore中ErrorContext信息被修改了,在最后的异常信息输出的时候,也可以通过store字段获取到。store() 和 recall() 确保了每个操作的错误上下文是独立的。在 generateKeys() 执行前,保存当前的错误上下文,在生成主键后恢复原有的上下文。这样,即使在执行过程中某个操作失败,错误信息也能清楚地区分开来,避免了上下文混乱。如果 generateKeys() 阶段失败,错误信息会指明是主键生成阶段的问题,而不是后续的更新操作。这样能够更容易地诊断和解决问题。会清楚知道错误发生在更新操作而不是主键生成阶段,从而能够更快定位问题。

通过使用 ErrorContext 进行错误上下文的存储和恢复,可以确保每个操作的错误信息都能得到独立处理,便于调试和问题的准确定位。

相关文章:

MyBatis异常体系中ErrorContext和ExceptionFactory原理分析

&#x1f3ae; 作者主页&#xff1a;点击 &#x1f381; 完整专栏和代码&#xff1a;点击 &#x1f3e1; 博客主页&#xff1a;点击 文章目录 exceptions包分包设计ExceptionFactory类介绍为什么使用工厂不是直接new呢&#xff1f;【统一的异常处理机制】【异常的封装与转化】【…...

WHLUG丨deepin、华中科技大学开放原子开源俱乐部、 RustSBI 和清华大学开源操作系统训练营共话开源新生代成长之路

2024年11月30日下午&#xff0c;由 deepin&#xff08;深度&#xff09;社区联合华中科技大学开放原子开源俱乐部、 RustSBI 开源社区和清华大学开源操作系统训练营共同举办的WHLUG&#xff08;武汉Linux用户组&#xff09;线下沙龙在华中科技大学成功举办。 本次活动聚集了50余…...

通过HTML Canvas 在图片上绘制文字

目录 前言 一、HTML Canvas 简介 二、准备工作 三、绘制图片 四、绘制文字 五、完整代码 效果演示&#xff1a; 前言 HTML canvas 为我们提供了无限的创意可能性。今天&#xff0c;我们就来探索一下如何通过 HTML canvas 将图片和文字绘制到图片上&#xff0c;创造出独特…...

C# 冒泡的算法

C# 冒泡的算法 public void BubbleSort(int[] arr) {int temp;for (int j 0; j < arr.Length - 2; j){for (int i 0; i < arr.Length - 2; i){if (arr[i] > arr[i 1]){temp arr[i 1];arr[i 1] arr[i];arr[i] temp;}}} }使用方法 int[] array new int[] { 5,…...

大数据项目-Django基于聚类算法实现的房屋售房数据分析及可视化系统

《[含文档PPT源码等]精品Django基于聚类算法实现的房屋售房数据分析及可视化系统》该项目含有源码、文档、PPT、配套开发软件、软件安装教程课程答疑等&#xff01; 数据库管理工具&#xff1a;phpstudy/Navicat或者phpstudy/sqlyog 后台管理系统涉及技术&#xff1a; 后台使…...

AWS创建ec2实例并连接成功

aws创建ec2实例并连接 aws创建ec2并连接 1.ec2创建前准备 首先创建一个VPC隔离云资源并且有公有子网 2.创建EC2实例 1.启动新实例或者创建实例 2.创建实例名 3.选择AMI使用linux(HVM) 4.选择实例类型 5.创建密钥对下载到本地并填入密钥对名称 6.选择自己创建的VPC和公有子网…...

TypeScript 开始学习 -接触的新东西

目录 语言类型类型总览JavaScript 中的数据类型TypeScript 中的数据类型 定义类型断言索引签名泛型 语言类型 JS -> 弱类型 TS -> 强类型 TypeScript&#xff08;TS&#xff09;是一种强类型语言。‌这意味着在TypeScript中&#xff0c;变量和函数参数的类型必须在编译时…...

非对称任意进制转换器(安卓)

除了正常进制转换&#xff0c;还可以输入、输出使用不同的数字符号&#xff0c;达成对数值进行加密的效果 点我下载APK安装包 使用unity开发。新建一个c#代码文件&#xff0c;把代码覆盖进去&#xff0c;再把代码文件添加给main camera即可。 using System.Collections; usin…...

【优选算法篇】寻找隐藏的宝藏:用二分查找打开算法世界的大门(上篇)

文章目录 须知 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1…...

基于Vue实现的移动端手机商城项目 电商购物网站 成品源码

&#x1f4c2;文章目录 一、&#x1f4d4;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站演示 &#x1f4f8;部分截图 &#x1f3ac;视频演示 五、⚙️网站代码 &#x1f9f1;项目结构 &#x1f492;vue代码预览 六、&#x1f527;完整…...

C语言:数组

数组 数组的概念 引例 如果我们要在程序中表示一个学生的成绩&#xff0c;我们会使用一个int来表示&#xff0c;如&#xff1a;int score。假如我们要在程序中表示一组成绩&#xff0c;此时我们所学的常规的数据类型就无法再表示&#xff0c;这个时候我们就需要使用一种新的…...

时间序列预测之FiLM

没错&#xff0c;就是看电影 文章目录 前言1. 问题描述2. 创新之处3. 贡献 一、时间序列在legende - fourier域的表示1. 勒让德投影2. 傅里叶变换 二、 模型结构1. LPU: Legendre Projection Unit2. FEL: Frequency Enhanced Layer3. 多尺度专家机制的混合 二、实验结果长时预测…...

【机器学习】窥数据之序,悟算法之道:机器学习的初心与远方

文章目录 机器学习入门&#xff1a;从零开始学习基础与应用前言第一部分&#xff1a;什么是机器学习&#xff1f;1.1 机器学习的定义1.1.1 举个例子&#xff1a;垃圾邮件分类器 1.2 机器学习的核心思想1.2.1 数据驱动的模式提取1.2.2 为什么机器学习比传统方法更灵活&#xff1…...

OpenCL介绍

OpenCL&#xff08;Open Computing Language&#xff09;详解 OpenCL 是一个开源的框架&#xff0c;用于编写在异构平台&#xff08;包括中央处理单元&#xff08;CPU&#xff09;、图形处理单元&#xff08;GPU&#xff09;、数字信号处理器&#xff08;DSP&#xff09;和其他…...

「Mac畅玩鸿蒙与硬件42」UI互动应用篇19 - 数字键盘应用

本篇将带你实现一个数字键盘应用&#xff0c;支持用户通过点击数字键输入数字并实时更新显示内容。我们将展示如何使用按钮组件和状态管理来实现一个简洁且实用的数字键盘。 关键词 UI互动应用数字键盘按钮组件状态管理用户交互 一、功能说明 数字键盘应用将实现以下功能&…...

【前端知识】npm命令行详细说明

npm命令行详细说明 概述一、定义与功能二、基本命令三、配置文件与注册表四、应用场景五、高级特性 环境设置1. 设置镜像源2. 配置全局依赖存储路径3. 配置缓存路径4. 查看所有配置5. 清除缓存6. 升级npm版本 npm组件打包1. 初始化项目2. 安装依赖3. 构建脚本4. 打包项目5. 发布…...

Python网络爬虫技术详解与实践案例

Python网络爬虫技术详解与实践案例 在大数据和人工智能盛行的今天,数据的获取与分析成为许多项目和业务的关键。网络爬虫作为一种自动化的数据采集工具,广泛应用于数据挖掘、市场分析、情报收集等领域。本文将详细介绍Python网络爬虫的基本概念、工作流程、进阶技巧,并附上…...

【遥感目标检测综述】【GRSS】遥感目标检测与深度学习的相遇:挑战与进展的元综述

Remote Sensing Object Detection Meets Deep Learning&#xff1a; A Meta-review of Challenges and Advances 遥感目标检测与深度学习的相遇&#xff1a;挑战与进展的元综述 论文链接 0.论文摘要和作者信息 摘要 遥感目标检测&#xff08;RSOD&#xff09;是遥感领域最基…...

【大数据技术基础】 课程 第3章 Hadoop的安装和使用 大数据基础编程、实验和案例教程(第2版)

第3章 Hadoop的安装和使用 3.1 Hadoop简介 Hadoop是Apache软件基金会旗下的一个开源分布式计算平台&#xff0c;为用户提供了系统底层细节透明的分布式基础架构。Hadoop是基于Java语言开发的&#xff0c;具有很好的跨平台特性&#xff0c;并且可以部署在廉价的计算机集群中。H…...

【机器学习】机器学习的基本分类-监督学习-决策树-C4.5 算法

C4.5 是由 Ross Quinlan 提出的决策树算法&#xff0c;是对 ID3 算法的改进版本。它在 ID3 的基础上&#xff0c;解决了以下问题&#xff1a; 处理连续型数据&#xff1a;支持连续型特征&#xff0c;能够通过划分点将连续特征离散化。处理缺失值&#xff1a;能够在特征值缺失的…...

云计算vsphere 服务器上添加主机配置

这里是esxi 主机 先把主机打开 然后 先开启dns 再开启 vcenter 把每台设备桌面再vmware workstation 上显示 同上也是一样 &#xff0c;因为在esxi 主机的界面可能有些东西不好操作 我们选择主机和集群 左边显示172.16.100.200...

Linux笔记---进程:进程替换

1. 进程替换的概念 进程替换是指在一个正在运行的进程中&#xff0c;用一个新的程序替换当前进程的代码和数据&#xff0c;使得进程开始执行新的程序&#xff0c;而不是原来的程序。 这种技术通常用于在不创建新进程的情况下&#xff0c;改变进程的行为。 我们之前谈到过for…...

量化交易backtrader实践(五)_策略综合篇(1)_股票软件指标回测

在第三章6到9节&#xff0c;我们学习和实践了大部分股票软件指标&#xff0c;且这些指标是backtrader内置指标实践中没有讲到过的。然后&#xff0c;在进行策略综合之前&#xff0c;我们先热个身&#xff0c;把一些可能比较有参考意义的股票软件内置指标在backtrader里给实现了…...

4.STM32通信接口之SPI通信(含源码)---软件SPI与W25Q64存储模块通信实战《精讲》

经过研究SPI协议和W25Q64&#xff0c;逐步了解了SPI的通信过程&#xff0c;接下来&#xff0c;就要进行战场实战了&#xff01;跟进Whappy步伐&#xff01; 目标&#xff1a;主要实现基于软件的SPI的STM32对W25Q64存储写入和读取操作&#xff01; 开胃介绍&#xff08;代码基本…...

MINDAGENT:游戏交互中的新兴性设计

一、摘要 1.问题/研究背景 LLM具有在多智能体系统中执行复杂调度的能力&#xff0c;并可以协调这些代理以完成需要广泛合作的复杂任务。 但是&#xff0c;目前还没有一个标准的游戏场景和相关的测试指标来评估 LLM 在游戏中的表现以及与人类玩家的合作能力。 2.研究目标/动…...

【工具变量】上市公司企业所在地城市等级直辖市、副省级城市、省会城市 计划单列市(2005-2022年)

一、包含指标&#xff1a; 股票代码 股票代码 股票简称 年份 所属城市 直辖市&#xff1a;企业所在地是否属于直辖市。1是&#xff0c;0否。 副省级城市&#xff1a;企业所在地是否属于副省级城市。1是&#xff0c;0否。 省会城市&a…...

C# 动态类型 Dynamic

文章目录 前言1. 什么是 Dynamic&#xff1f;2. 声明 Dynamic 变量3. Dynamic 的运行时类型检查4. 动态类型与反射的对比5. 使用 Dynamic 进行动态方法调用6. Dynamic 与 原生类型的兼容性7. 动态与 LINQ 的结合8. 结合 DLR 特性9. 动态类型的性能考虑10. 何时使用 Dynamic&…...

Css动画:旋转相册动画效果实现

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;Css篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来Css篇专栏内容:Css动画:旋转相册动画效果实现 前言 随着Web技术的发展&#xff0c;网页不再局限于静态展示&#…...

Unity 基于Collider 组件在3D 物体表面放置3D 物体

实现 从鼠标点击的屏幕位置发送射线&#xff0c;以射线监测点击到的物体&#xff0c;根据点击物体的法线向量调整放置物体的位置及朝向。 Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, 100)) {obj.transform.…...

Hbase整合Mapreduce案例1 hdfs数据上传至hbase中——wordcount

目录 整合结构准备java API 编写pom.xmlMain.javaMap.javaReduce 运行 整合结构 准备 上传hdfs data.txt数据 data.txt I am wunaiieq QAQ 123456 Who I am In todays interconnected world the role of technology cannot be overstated It has revolutionized the way we …...