【SpringCloud-Seata源码分析2】

news2025/2/7 17:24:30

文章目录

  • 分支事务注册-客户端
  • 分支事务服务端的执行

分支事务注册-客户端

第一篇我们将全局事务启动,以及开启源码分析完成了,现在我们需要看一下分支事务注册。
在这里插入图片描述
我们分支事务的开始需要从PreparedStatementProxy#executeUpdate中去看。

public class PreparedStatementProxy extends AbstractPreparedStatementProxy implements PreparedStatement, ParametersHolder {
    public Map<Integer, ArrayList<Object>> getParameters() {
        return this.parameters;
    }

    public PreparedStatementProxy(AbstractConnectionProxy connectionProxy, PreparedStatement targetStatement, String targetSQL) throws SQLException {
        super(connectionProxy, targetStatement, targetSQL);
    }

    public boolean execute() throws SQLException {
        return (Boolean)ExecuteTemplate.execute(this, (statement, args) -> {
            return statement.execute();
        }, new Object[0]);
    }

    public ResultSet executeQuery() throws SQLException {
        return (ResultSet)ExecuteTemplate.execute(this, (statement, args) -> {
            return statement.executeQuery();
        }, new Object[0]);
    }
//这个是分支事务的核心入口
    public int executeUpdate() throws SQLException {
        return (Integer)ExecuteTemplate.execute(this, (statement, args) -> {
            return statement.executeUpdate();
        }, new Object[0]);
    }
}

判断出当前的业务Sql是什么类型,我们需要选择不同的执行器。

public static <T, S extends Statement> T execute(List<SQLRecognizer> sqlRecognizers, StatementProxy<S> statementProxy, StatementCallback<T, S> statementCallback, Object... args) throws SQLException {
		//判断是否是全局锁,并且是否是AT模式
        if (!RootContext.requireGlobalLock() && BranchType.AT != RootContext.getBranchType()) {
        	//否 执行普通的SQL
            return statementCallback.execute(statementProxy.getTargetStatement(), args);
        } else {
        	//获取数据库类型
            String dbType = statementProxy.getConnectionProxy().getDbType();
            if (CollectionUtils.isEmpty(sqlRecognizers)) {
                sqlRecognizers = SQLVisitorFactory.get(statementProxy.getTargetSQL(), dbType);
            }

            Object executor;
            if (CollectionUtils.isEmpty(sqlRecognizers)) {
                executor = new PlainExecutor(statementProxy, statementCallback);
            } else if (sqlRecognizers.size() == 1) {
                SQLRecognizer sqlRecognizer = (SQLRecognizer)sqlRecognizers.get(0);
                label44:
                //根据不同的SQL类型选择不同的执行器
                switch(sqlRecognizer.getSQLType()) {
                case INSERT:
                    executor = (Executor)EnhancedServiceLoader.load(InsertExecutor.class, dbType, new Class[]{StatementProxy.class, StatementCallback.class, SQLRecognizer.class}, new Object[]{statementProxy, statementCallback, sqlRecognizer});
                    break;
                case UPDATE:
                    executor = new UpdateExecutor(statementProxy, statementCallback, sqlRecognizer);
                    break;
                case DELETE:
                    executor = new DeleteExecutor(statementProxy, statementCallback, sqlRecognizer);
                    break;
                case SELECT_FOR_UPDATE:
                    executor = new SelectForUpdateExecutor(statementProxy, statementCallback, sqlRecognizer);
                    break;
                case INSERT_ON_DUPLICATE_UPDATE:
                    byte var8 = -1;
                    switch(dbType.hashCode()) {
                    case 104382626:
                        if (dbType.equals("mysql")) {
                            var8 = 0;
                        }
                        break;
                    case 839186932:
                        if (dbType.equals("mariadb")) {
                            var8 = 1;
                        }
                    }

                    switch(var8) {
                    case 0:
                    case 1:
                        executor = new MySQLInsertOrUpdateExecutor(statementProxy, statementCallback, sqlRecognizer);
                        break label44;
                    default:
                        throw new NotSupportYetException(dbType + " not support to INSERT_ON_DUPLICATE_UPDATE");
                    }
                default:
                    executor = new PlainExecutor(statementProxy, statementCallback);
                }
            } else {
                executor = new MultiExecutor(statementProxy, statementCallback, sqlRecognizers);
            }

            try {
            //核心入口执行
                T rs = ((Executor)executor).execute(args);
                return rs;
            } catch (Throwable var9) {
                Throwable ex = var9;
                if (!(var9 instanceof SQLException)) {
                    ex = new SQLException(var9);
                }

                throw (SQLException)ex;
            }
        }
    }

excute()执行

    public T execute(Object... args) throws Throwable {
    //获取事务的xid
        String xid = RootContext.getXID();
        if (xid != null) {
        //绑定xid
            this.statementProxy.getConnectionProxy().bind(xid);
        }
        //将事务绑定上全局锁
        this.statementProxy.getConnectionProxy().setGlobalLockRequire(RootContext.requireGlobalLock());
        return this.doExecute(args);
    }
    protected T executeAutoCommitTrue(Object[] args) throws Throwable {
        ConnectionProxy connectionProxy = this.statementProxy.getConnectionProxy();

        Object var3;
        try {
        	//将事务改为手动提交
            connectionProxy.changeAutoCommit();
            var3 = (new AbstractDMLBaseExecutor.LockRetryPolicy(connectionProxy)).execute(() -> {
            	//执行非自动提交
                T result = this.executeAutoCommitFalse(args);
                connectionProxy.commit();
                return result;
            });
        } catch (Exception var7) {
            LOGGER.error("execute executeAutoCommitTrue error:{}", var7.getMessage(), var7);
            if (!AbstractDMLBaseExecutor.LockRetryPolicy.isLockRetryPolicyBranchRollbackOnConflict()) {			
            	//报错将事务进行回滚
                connectionProxy.getTargetConnection().rollback();
            }

            throw var7;
        } finally {
            connectionProxy.getContext().reset();
            //将自动提交置为true
            connectionProxy.setAutoCommit(true);
        }

        return var3;
    }

    protected T executeAutoCommitFalse(Object[] args) throws Exception {
        if (!"mysql".equalsIgnoreCase(this.getDbType()) && this.isMultiPk()) {
            throw new NotSupportYetException("multi pk only support mysql!");
        } else {
        	//设置前置镜像
            TableRecords beforeImage = this.beforeImage();
            //执行业务Sql
            T result = this.statementCallback.execute(this.statementProxy.getTargetStatement(), args);
            int updateCount = this.statementProxy.getUpdateCount();
            if (updateCount > 0) {
            	//执行后置镜像
                TableRecords afterImage = this.afterImage(beforeImage);
                this.prepareUndoLog(beforeImage, afterImage);
            }

            return result;
        }
    }

执行提交

    public void commit() throws SQLException {
        try {
            this.lockRetryPolicy.execute(() -> {
                this.doCommit();
                return null;
            });
        } catch (SQLException var2) {
            if (this.targetConnection != null && !this.getAutoCommit() && !this.getContext().isAutoCommitChanged()) {
                this.rollback();
            }

            throw var2;
        } catch (Exception var3) {
            throw new SQLException(var3);
        }
    }

执行事务的提交

   private void doCommit() throws SQLException {
        if (this.context.inGlobalTransaction()) {
        	//如果是全局事务就执行此方法
            this.processGlobalTransactionCommit();
        } else if (this.context.isGlobalLockRequire()) {
			//执行全局锁的事务提交
            this.processLocalCommitWithGlobalLocks();
        } else {
        	//其他
            this.targetConnection.commit();
        }

    }

组装请求数据,发送后端

  public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid, String applicationData, String lockKeys) throws TransactionException {
        try {
            BranchRegisterRequest request = new BranchRegisterRequest();
            request.setXid(xid);
            request.setLockKey(lockKeys);
            request.setResourceId(resourceId);
            request.setBranchType(branchType);
            request.setApplicationData(applicationData);
            BranchRegisterResponse response = (BranchRegisterResponse)RmNettyRemotingClient.getInstance().sendSyncRequest(request);
            if (response.getResultCode() == ResultCode.Failed) {
                throw new RmTransactionException(response.getTransactionExceptionCode(), String.format("Response[ %s ]", response.getMsg()));
            } else {
                return response.getBranchId();
            }
        } catch (TimeoutException var9) {
            throw new RmTransactionException(TransactionExceptionCode.IO, "RPC Timeout", var9);
        } catch (RuntimeException var10) {
            throw new RmTransactionException(TransactionExceptionCode.BranchRegisterFailed, "Runtime", var10);
        }
    }

分支事务服务端的执行

在这里插入图片描述
我们首先看一下分支事务服务端注册的入口DefaultCoordinator#

    @Override
    public AbstractResultMessage onRequest(AbstractMessage request, RpcContext context) {
    //判断请求是否来自于事务的客户端
        if (!(request instanceof AbstractTransactionRequestToTC)) {
            throw new IllegalArgumentException();
        }
        AbstractTransactionRequestToTC transactionRequest = (AbstractTransactionRequestToTC) request;
        transactionRequest.setTCInboundHandler(this);
		
        return transactionRequest.handle(context);
    }

我们的核心分支事务注册代码

    @Override
    public BranchRegisterResponse handle(BranchRegisterRequest request, final RpcContext rpcContext) {
    	//创建返回的实例
        BranchRegisterResponse response = new BranchRegisterResponse();
        exceptionHandleTemplate(new AbstractCallback<BranchRegisterRequest, BranchRegisterResponse>() {
            @Override
            public void execute(BranchRegisterRequest request, BranchRegisterResponse response)
                throws TransactionException {
                try {
                	//核心分支注册实例
                    doBranchRegister(request, response, rpcContext);
                } catch (StoreException e) {
                    throw new TransactionException(TransactionExceptionCode.FailedStore, String
                        .format("branch register request failed. xid=%s, msg=%s", request.getXid(), e.getMessage()), e);
                }
            }
        }, request, response);
        return response;
    }
 @Override
    public Long branchRegister(BranchType branchType, String resourceId, String clientId, String xid,
                               String applicationData, String lockKeys) throws TransactionException {
        // key1:获取GlobalSession
        GlobalSession globalSession = assertGlobalSessionNotNull(xid, false);
        return SessionHolder.lockAndExecute(globalSession, () -> {
            // 检查事务状态
            globalSessionStatusCheck(globalSession);
            globalSession.addSessionLifecycleListener(SessionHolder.getRootSessionManager());
            //key2: 创建分支会话
            BranchSession branchSession = SessionHelper.newBranchByGlobal(globalSession, branchType, resourceId,
                    applicationData, lockKeys, clientId);
            MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(branchSession.getBranchId()));
            //key3:增加分支事务锁
            branchSessionLock(globalSession, branchSession);
            try {
                //key4: 全局会话添加分支会话
                globalSession.addBranch(branchSession);
            } catch (RuntimeException ex) {
                // key5: 出现异常释放锁
                branchSessionUnlock(branchSession);
                throw new BranchTransactionException(FailedToAddBranch, String
                        .format("Failed to store branch xid = %s branchId = %s", globalSession.getXid(),
                                branchSession.getBranchId()), ex);
            }
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Register branch successfully, xid = {}, branchId = {}, resourceId = {} ,lockKeys = {}",
                        globalSession.getXid(), branchSession.getBranchId(), resourceId, lockKeys);
            }
            // key6: 返回分支会话的分支ID
            return branchSession.getBranchId();
        });
    }

创建封装分支事务的信息

    public static BranchSession newBranchByGlobal(GlobalSession globalSession, BranchType branchType, String resourceId,
            String applicationData, String lockKeys, String clientId) {
        BranchSession branchSession = new BranchSession();
        branchSession.setXid(globalSession.getXid());
        branchSession.setTransactionId(globalSession.getTransactionId());
        branchSession.setBranchId(UUIDGenerator.generateUUID());
        branchSession.setBranchType(branchType);
        branchSession.setResourceId(resourceId);
        branchSession.setLockKey(lockKeys);
        branchSession.setClientId(clientId);
        branchSession.setApplicationData(applicationData);

        return branchSession;
    }

分支事务加锁

protected void branchSessionLock(GlobalSession globalSession, BranchSession branchSession)
        throws TransactionException {
        //分支事务的参数校验
        String applicationData = branchSession.getApplicationData();
        boolean autoCommit = true;
        boolean skipCheckLock = false;
        if (StringUtils.isNotBlank(applicationData)) {
            if (objectMapper == null) {
                objectMapper = new ObjectMapper();
            }
            try {
                Map<String, Object> data = objectMapper.readValue(applicationData, HashMap.class);
                Object clientAutoCommit = data.get(AUTO_COMMIT);
                if (clientAutoCommit != null && !(boolean)clientAutoCommit) {
                    autoCommit = (boolean)clientAutoCommit;
                }
                Object clientSkipCheckLock = data.get(SKIP_CHECK_LOCK);
                if (clientSkipCheckLock instanceof Boolean) {
                    skipCheckLock = (boolean)clientSkipCheckLock;
                }
            } catch (IOException e) {
                LOGGER.error("failed to get application data: {}", e.getMessage(), e);
            }
        }
        try {
            // 增加分支锁,如果返回false加锁失败我们直接抛出异常
            if (!branchSession.lock(autoCommit, skipCheckLock)) {
                throw new BranchTransactionException(LockKeyConflict,
                    String.format("Global lock acquire failed xid = %s branchId = %s", globalSession.getXid(),
                        branchSession.getBranchId()));
            }
        } catch (StoreException e) {
            if (e.getCause() instanceof BranchTransactionException) {
                throw new BranchTransactionException(((BranchTransactionException)e.getCause()).getCode(),
                    String.format("Global lock acquire failed xid = %s branchId = %s", globalSession.getXid(),
                        branchSession.getBranchId()));
            }
            throw e;
        }
    }
    public boolean lock(boolean autoCommit, boolean skipCheckLock) throws TransactionException {
        // 必须还AT事务
        if (this.getBranchType().equals(BranchType.AT)) {
            return LockerManagerFactory.getLockManager().acquireLock(this, autoCommit, skipCheckLock);
        }
        return true;
    }
    public boolean acquireLock(BranchSession branchSession, boolean autoCommit, boolean skipCheckLock) throws TransactionException {
        if (branchSession == null) {
            throw new IllegalArgumentException("branchSession can't be null for memory/file locker.");
        }
        // 获取分支锁的key
        String lockKey = branchSession.getLockKey();
        if (StringUtils.isNullOrEmpty(lockKey)) {
            // no lock
            return true;
        }
        // get locks of branch
        // 行锁收集
        List<RowLock> locks = collectRowLocks(branchSession);
        if (CollectionUtils.isEmpty(locks)) {
            // no lock
            return true;
        }
        // 进行存储
        return getLocker(branchSession).acquireLock(locks, autoCommit, skipCheckLock);
    }

添加分支事务

 private void writeSession(LogOperation logOperation, SessionStorable sessionStorable) throws TransactionException {
        // 将事务信息写入数据库
        if (!transactionStoreManager.writeSession(logOperation, sessionStorable)) {
            if (LogOperation.GLOBAL_ADD.equals(logOperation)) {
                throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to store global session");
            } else if (LogOperation.GLOBAL_UPDATE.equals(logOperation)) {
                throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to update global session");
            } else if (LogOperation.GLOBAL_REMOVE.equals(logOperation)) {
                throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to remove global session");
            } else if (LogOperation.BRANCH_ADD.equals(logOperation)) {
                throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to store branch session");
            } else if (LogOperation.BRANCH_UPDATE.equals(logOperation)) {
                throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to update branch session");
            } else if (LogOperation.BRANCH_REMOVE.equals(logOperation)) {
                throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Fail to remove branch session");
            } else {
                throw new BranchTransactionException(TransactionExceptionCode.FailedWriteSession,
                    "Unknown LogOperation:" + logOperation.name());
            }
        }
    }

如果发生异常

    @Override
    public boolean unlock() throws TransactionException {
        if (this.getBranchType() == BranchType.AT) {
            // 释放锁
            return LockerManagerFactory.getLockManager().releaseLock(this);
        }
        return true;
    }
    public boolean releaseLock(BranchSession branchSession) throws TransactionException {
        if (branchSession == null) {
            throw new IllegalArgumentException("branchSession can't be null for memory/file locker.");
        }
        List<RowLock> locks = collectRowLocks(branchSession);
        try {
            // 释放锁
            return getLocker(branchSession).releaseLock(locks);
        } catch (Exception t) {
            LOGGER.error("unLock error, branchSession:{}", branchSession, t);
            return false;
        }
    }
  public boolean releaseLock(List<RowLock> locks) {
        if (CollectionUtils.isEmpty(locks)) {
            // no lock
            return true;
        }
        try {
            return lockStore.unLock(convertToLockDO(locks));
        } catch (StoreException e) {
            throw e;
        } catch (Exception t) {
            LOGGER.error("unLock error, locks:{}", CollectionUtils.toString(locks), t);
            return false;
        }
    }

将数据库的锁删除

private static final String BATCH_DELETE_LOCK_SQL = "delete from " + LOCK_TABLE_PLACE_HOLD
        + " where " + ServerTableColumnsName.LOCK_TABLE_XID + " = ? and (" + LOCK_TABLE_PK_WHERE_CONDITION_PLACE_HOLD + ") ";

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

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

相关文章

MatLab手把手搭建FOC控制环路(全部使用matlab自带模块)

MatLab手把手搭建FOC控制环路&#xff08;全部使用matlab自带模块&#xff09; Matlab添加模块只需要在空白处双击鼠标左键&#xff0c;输入模块的名字。 添加PMSM模块&#xff1a; Permanent Magnet Synchronous Machine 参数选择&#xff1a; 添加逆变器Two-Level Conver…

CentOS 7 安装部署Cassandra4.1.5

一、Cassandra的介绍 Cassandra是一套开源分布式NoSQL数据库系统。它最初由Facebook开发&#xff0c;用于储存收件箱等简单格式数据&#xff0c;集GoogleBigTable的数据模型与Amazon Dynamo的完全分布式的架构于一身Facebook于2008将 Cassandra 开源&#xff0c;此后&#xff0…

Jmeter5.X性能测试【完整版】

目录 一、Http基础协议和解析 1、浏览器的B/S架构和C/S架构 &#xff08;1&#xff09;CS架构 &#xff08;2&#xff09;BS架构 &#xff08;3&#xff09;URL理解 2、Http超文本传输协议 &#xff08;1&#xff09;含义 # 协议 # json协议 # xml协议 &#xff08;…

unity中使用commandbuffer将自定义画面渲染到主相机上

CommandBuffer 保存渲染命令列表&#xff08;例如设置渲染目标或绘制给定网格&#xff09;。您可以指示 Unity 在内置渲染管线中的各个点安排和执行这些命令&#xff0c;因此&#xff0c;您可以自定义和扩展 Unity 的渲染功能。 这句话意味着你可以通过command buffer让相机渲…

视频汇聚安防综合管理平台EasyCVR支持GA/T 1400视图库标准及设备接入配置

一、概述 视频汇聚安防综合管理平台EasyCVR视频监控系统已经与公安部GA/T 1400视图库标准协议实现了对接&#xff0c;即《公安视频图像信息应用系统》。 安防监控系统EasyCVR支持采用GA/T 1400进行对接&#xff0c;可实现人脸数据使用的标准化、合规化。其采用统一接口对接雪…

替换或重写Tomcat内置的404页面

替换或重写Tomcat内置的404页面 准备一个Tomcat隐藏Tomcat的相关信息纯净版的Tomcat解决Tomcat启动乱码的问题 替换或重写Tomcat内置的404页面创建新的首页和错误页面页面代码如下所示&#xff1a;创建首页index.html创建error_404.html页面创建其他错误页面创建编写web.xml&am…

【C++】优先队列的使用及模拟实现

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 目录 导读 一、什么是优先队列 二、优先队列的使用 1. 优先队列的构造 2. 优先队列的基本操作 3. 使用示例 三、优先队列模拟实…

C++初学者指南第一步---12.引用

C初学者指南第一步—12.引用 文章目录 C初学者指南第一步---12.引用1. 功能&#xff08;和限制&#xff09;1.1 非常量引用1.2 常量引用1.3 auto引用 2.用法2.1 范围for循环中的引用2.2 常量引用的函数形参2.3 非常量引用的函数形参2.4 函数参数的选择&#xff1a;copy / const…

emqx5.6.1 数据、配置备份与迁移

EMQX 支持导入和导出的数据包括&#xff1a; EMQX 配置重写的内容&#xff1a; 认证与授权配置规则、连接器与 Sink/Source监听器、网关配置其他 EMQX 配置内置数据库 (Mnesia) 的数据 Dashboard 用户和 REST API 密钥客户端认证凭证&#xff08;内置数据库密码认证、增强认证…

cas客户端流程详解(源码解析)--单点登录

博主之前一直使用了cas客户端进行用户的单点登录操作&#xff0c;决定进行源码分析来看cas的整个流程&#xff0c;以便以后出现了问题还不知道是什么原因导致的 cas主要的形式就是通过过滤器的形式来实现的&#xff0c;来&#xff0c;贴上示例配置&#xff1a; 1 <list…

海南聚广众达电子商务咨询有限公司抖音电商新引擎

在数字化浪潮席卷而来的今天&#xff0c;抖音电商作为新兴的商业模式&#xff0c;正以其独特的魅力和无限的潜力&#xff0c;引领着电子商务行业的革新与发展。海南聚广众达电子商务咨询有限公司&#xff0c;作为专注于抖音电商服务的领军企业&#xff0c;凭借其专业的团队、丰…

双例集合(三)——双例集合的实现类之TreeMap容器类

Map接口有两个实现类&#xff0c;一个是HashMap容器类&#xff0c;另一个是TreeMap容器类。TreeMap容器类的使用在API上于HashMap容器类没有太大的区别。它们的区别主要体现在两个方面&#xff0c;一个是底层实现方式上&#xff0c;HashMap是基于Hash算法来实现的吗&#xff0c…

【C语言】函数指针数组和指向函数指针数组的指针

1 函数指针数组 数组是一个存放相同类型数据的存储空间&#xff0c;那我们已经学习了指针数组。 比如&#xff1a; int *arr[10];//数组的每个元素是int* 那要把函数的地址存到一个数组中&#xff0c;那这个数组就叫函数指针数组&#xff0c;那函数指针的数组如何定义呢&am…

OS复习笔记ch11-2

上一节我们学习的内容是I/O系统的特点和设备分类和差异&#xff0c;这一节我们将主要关注I/O控制方式、OS设计问题、I/O逻辑结构等。 I/O功能的演变 在专栏的ch1-2中&#xff0c;我们详细讲解了CPU与外设的三种交互方式&#xff0c;这里简单地带过。 &#xff08;1&#xff0…

C++之STL(一)

1、泛型程序设计 目的&#xff1a;提供相同的算法&#xff0c;相同的逻辑&#xff0c;来对不同类型的数据结构进行操作。 所以需要将类型当作参数&#xff0c;也就是参数类型化。 2、什么是STL STL是基于模板实现的。编译的时候进行实例化 3、STL组件 4、容器算法迭代器关系 …

第二次IAG

IAG in NanJing City 我与南京奥体的初次相遇&#xff0c;也可能是最后一次&#xff01; 对我来说,IAG 演唱会圆满结束啦! 做了两场充满爱[em]e400624[/em]的美梦 3.30号合肥站&#xff0c;6.21号南京站[em]e400947[/em] 其实&#xff0c;没想到昨天回去看呀!(lack of money […

如何修改外接移动硬盘的区号

- 问题介绍 当电脑自身内存不够使用的时候&#xff0c;使用外接硬盘扩展内存是一个不错的选择。但是当使用的外接硬盘数量过多的时候&#xff0c;会出现分配硬盘的区号变动的情况&#xff0c;这种情况下会极大的影响使用的体验情况。可以通过以下步骤手动调整恢复 - 配置 版本…

SpringBoot 快速入门(保姆级详细教程)

目录 一、Springboot简介 二、SpringBoot 优点&#xff1a; 三、快速入门 1、新建工程 方式2&#xff1a;使用Spring Initializr创建项目 写在前面&#xff1a; SpringBoot 是 Spring家族中的一个全新框架&#xff0c;用来简化spring程序的创建和开发过程。SpringBoot化繁…

【C语言】操作符(上)

目录 1. 操作符的分类 2. 原码、反码、补码 3. 移位操作符 3.1 左移操作符 3.2 右移操作符 4. 位操作符&#xff1a;&、|、^、~ 5. 单目操作符 6. 逗号表达式 最近准备期末考试&#xff0c;好久不见啦&#xff0c;现在回归—— 正文开始—— 1. …

WPF文本绑定显示格式StringFormat设置-数值类型处理

绑定显示格式设置 在Textblock等文本控件中&#xff0c;我们经常要绑定一些数据类型&#xff0c;但是我们希望显示的时候能够按照我们想要的格式去显示&#xff0c;比如增加文本前缀&#xff0c;后面加单位&#xff0c;显示百分号等等&#xff0c;这种就需要对绑定格式进行处理…