@Transactional 学习和使用

news/2024/4/19 0:29:38

知识点:

@Transactional是一种基于注解管理事务的方式,spring通过动态代理的方式为目标方法实现事务管理的增强。 

@Transactional 实质是使用了 JDBC 的事务来进行事务控制的

@Transactional 基于 Spring 的动态代理的机制

@Transactional 实现原理:

    1) 事务开始时,通过AOP机制,生成一个代理connection对象,

并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。 在接下来的整个事务中,客户代码都应该使用该 connection 连接数据库,执行所有数据库命令。

[不使用该 connection 连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚]

(物理连接 connection 逻辑上新建一个会话session;

DataSource 与 TransactionManager 配置相同的数据源)

   2) 事务结束时,回滚在第1步骤中得到的代理 connection 对象上执行的数据库命令,然后关闭该代理 connection 对象。(事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)

使用注意

1)接口实现类或接口实现方法上,而不是接口类中。

2)访问权限:public 的方法才起作用。@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。

系统设计:将标签放置在需要进行事务管理的方法上,而不是放在所有接口实现类上:

3)@Transactional注解在外部调用的函数上才有效果,内部调用的函数添加无效,要切记。这是由AOP的特性决定的。

函数之间调用

业务:创建法律模板,添加关联法律模板关系

单个类之间函数调用:外部加

情形1: create添加了@Transactional注解,insertLawInnerMappingBatch函数没有添加。insertLawInnerMappingBatch抛异常

    @Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){this.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}// 插入关联关系public void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行有异常!");
//        lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);}

单元测试:

    @Autowiredprivate LawTemplateService lawTemplateService;@Testpublic void createRelLaw() {LawTemplate law = new LawTemplate();law.setLawName("成年人");law.setLawContent("成年人为完全民事行为能力人,可以独立实施民事法律行为");law.setLawCase("成年人行为xxx");law.setCreateStaffId(1L);law.setCreateTime(new Date());List<Long> lawIds = new ArrayList<>();lawIds.add(1L);law.setRelLawIds(lawIds);lawTemplateService.create(law);}

执行报错,两个数据的操作都会回滚

单个类之间函数调用:都加

情形2: create和insertLawInnerMappingBatch都添加了@Transactional注解。insertLawInnerMappingBatch抛异常

    @Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){this.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行有异常!");
//        lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);}

  结果:同第一种情况一样,两个函数对数据库操作都会回滚。因为同一个类中函数相互调用的时候,内部函数添加@Transactional注解无效。@Transactional注解只有外部调用才有效。

单个类之间函数调用:内部加  

  情形3: create不加了@Transactional注解,insertLawInnerMappingBatch函数加@Transactional注解。insertLawInnerMappingBatch抛异常

 public Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){this.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行有异常!");
//        lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);}

 结果:两个函数对数据库的操作都不会回滚。因为内部函数@Transactional注解添加和没添加一样。

 

单个类之间函数调用:外部加,内部捕获异常

   情形4: create加了@Transactional注解,insertLawInnerMappingBatch加@Transactional注解。create 内部添加插入关系的捕获, insertLawInnerMappingBatch抛异常

    @Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){try {this.insertLawInnerMappingBatch(lawTemplate);}catch (Exception e){e.printStackTrace();}}return lawTemplate.getLawId();}// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行有异常!");
//        lawTemplateMapper.insertLawInnerMappingBatch(lawTemplate);}

 结果:不管insertLawInnerMappingBatch操作是否成功,insertSelective没有发生异常就会成功。事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。

不同类之间函数调用

不同类之间函数调用:外部加

情形1: create添加了@Transactional注解,insertLawInnerMappingBatch函数没有添加。insertLawInnerMappingBatch抛异常

@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Resourceprivate ReplyTemplateService replyTemplateService;@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}
}@Slf4j
@Service
public class ReplyTemplateService {// 插入关联关系public void insertLawInnerMappingBatch(LawTemplate lawTemplate) {throw new RuntimeException("模拟函数执行有异常!");}} 

单元测试:

    @Autowiredprivate LawTemplateService lawTemplateService;@Testpublic void createRelLaw() {LawTemplate law = new LawTemplate();law.setLawName("成年人");law.setLawContent("成年人为完全民事行为能力人,可以独立实施民事法律行为");law.setLawCase("成年人行为xxx");law.setCreateStaffId(1L);law.setCreateTime(new Date());List<Long> lawIds = new ArrayList<>();lawIds.add(1L);law.setRelLawIds(lawIds);lawTemplateService.create(law);}

执行报错,两个数据的操作都会回滚

不同类之间函数调用:都加

情形2: create和insertLawInnerMappingBatch都添加了@Transactional注解。insertLawInnerMappingBatch抛异常

@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Resourceprivate ReplyTemplateService replyTemplateService;@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}
}@Slf4j
@Service
public class ReplyTemplateService {// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate) {throw new RuntimeException("模拟函数执行有异常!");}} 

  结果:两个函数对数据库的操作都回滚了。两个函数里面用的还是同一个事务。

不同类之间函数调用:引用加  

  情形3: create不加了@Transactional注解,insertLawInnerMappingBatch函数加@Transactional注解。insertLawInnerMappingBatch抛异常

@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Resourceprivate ReplyTemplateService replyTemplateService;public Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}
}@Slf4j
@Service
public class ReplyTemplateService {// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate) {throw new RuntimeException("模拟函数执行有异常!");}} 

 结果:create里面insertSelective正常不报错,会插入。不管 LawTemplateService 里面 insertLawInnerMappingBatch 是否报错。因为两个是不同事物的

不同类之间函数调用:外部加,内部捕获异常

   情形4: create加了@Transactional注解,insertLawInnerMappingBatch加@Transactional注解。create 内部添加插入关系的捕获, insertLawInnerMappingBatch抛异常

@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Resourceprivate ReplyTemplateService replyTemplateService;@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){try {replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}catch (Exception e){e.printStackTrace();}}return lawTemplate.getLawId();}}@Slf4j
@Service
public class ReplyTemplateService {// 插入关联关系@Transactionalpublic void insertLawInnerMappingBatch(LawTemplate lawTemplate) {throw new RuntimeException("模拟函数执行有异常!");}} 

 部分异常:

Transaction rolled back because it has been marked as rollback-only
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-onlyat org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:870)at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:707)at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:654)at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:407)at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)

结果:create操作失败,而且还添加了抛出异常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only。两个函数用的是同一个事务。insertLawInnerMappingBatch函数抛了异常,调了事务的rollback函数。事务被标记了只能rollback了。程序继续执行,create函数里面把异常给抓出来了,这个时候create函数没有抛出异常,既然你没有异常那事务就需要提交,会调事务的commit函数。而之前已经标记了事务只能rollback-only(以为是同一个事务)。直接就抛异常了,不让继续调用了。

        有@Transactional的函数里面调用有@Transactional的函数的时候,进入第二个函数的时候是新的事务,如果还是沿用之前的事务。如果报错就会抛UnexpectedRollbackException异常。

不同类之间函数调用:引用添加 Propagation.REQUIRES_NEW

   情形5: create加了@Transactional注解,insertLawInnerMappingBatch加@Transactional注解。create 内部添加插入关系的捕获, insertLawInnerMappingBatch抛异常,添加 Propagation.REQUIRES_NEW

@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Resourceprivate ReplyTemplateService replyTemplateService;@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){try {replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}catch (Exception e){e.printStackTrace();}}return lawTemplate.getLawId();}}@Slf4j
@Service
public class ReplyTemplateService {// 插入关联关系@Transactional(propagation = Propagation.REQUIRES_NEW)public void insertLawInnerMappingBatch(LawTemplate lawTemplate) {throw new RuntimeException("模拟函数执行有异常!");}} 

  结果: 不管insertLawInnerMappingBatch操作是否成功,insertSelective没有发生异常就会成功。因为两个函数不是同一个事务了。

 

 

常见出错的地方

        总结以上需要注意@Transactional失效的场景:

1、异常被捕获后没有抛出

    @Transactionalpublic Long create(LawTemplate lawTemplate) {try {lawTemplateMapper.insertSelective(lawTemplate);return lawTemplate.getLawId();}catch (Exception e){e.printStackTrace();}return null;}
这个很好理解,事务回滚的动作发生在当有@Transactional注解函数有对应异常抛出时才会回滚。

2、抛出非运行时异常 得是RuntimeException 或者指定异常

异步虽然抛出了,但是抛出的是非RuntimeException类型的异常(抛出的异常要继承RuntimeException才有效),依旧不会生效。

    @Transactionalpublic Long create(LawTemplate lawTemplate) throws MyException {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){try {replyTemplateService.insertLawInnerMappingBatch(lawTemplate);}catch (Exception e){throw new MyException ();}}return lawTemplate.getLawId();}

如果指定了回滚异常类型为Exception,那么就可以回滚非RuntimeException类型异常了。

@Transactional(rollbackFor = Exception.class)

3、方法内部直接调用 -- 这个容易忽略,需要去注意!

如果先调用createOne(),那么create()执行成功是不会回滚的,其原因就是@Transactional根本没生成代理,如果直接调用create() ,如果报错会回滚。

@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;@Autowiredprivate LawTemplateService lawTemplateService;public void createOne(LawTemplate lawTemplate) {this.create(lawTemplate);}@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){this.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}// 插入关联关系public void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行插入关系失败后抛出异常!");}
}

insertSelective 插入成功, insertLawInnerMappingBatch报错,并没有回滚。为啥呢? 因为createOne里面调用create没有生成代理。

修改方式,把当前类自己注入一下调用 

@Slf4j
@Service
public class LawTemplateService {@Resourceprivate LawTemplateMapper lawTemplateMapper;public void createOne(LawTemplate lawTemplate) {SpringUtils.getBean(LawTemplateService.class).create(lawTemplate);}@Transactionalpublic Long create(LawTemplate lawTemplate) {lawTemplateMapper.insertSelective(lawTemplate);List<Long> relLawIds = lawTemplate.getRelLawIds();if(CollectionUtils.isNotEmpty(relLawIds)){this.insertLawInnerMappingBatch(lawTemplate);}return lawTemplate.getLawId();}// 插入关联关系public void insertLawInnerMappingBatch(LawTemplate lawTemplate){throw new RuntimeException("模拟函数执行插入关系失败后抛出异常!");}
}

insertSelective 插入成功, insertLawInnerMappingBatch报错,会回滚。

4、注解到private方法上

idea直接会给出提示Methods annotated with ‘@Transactional’ must be overridable ,原理很简单,private修饰的方式,spring无法生成动态代理。直接报错了

总结:

      @Transactional 使用:1, 要注意抛出异常是否是运行时异常;2,注解在外部调用的函数上才有效果;3,内部调用的,要注意是否生成代理。


http://www.ppmy.cn/news/1009359.html

相关文章

【C#学习笔记】数组和索引器

文章目录 数组单维数组多维数组交错数组 索引器类上的索引器方法1方法2 接口中的索引器 数组 数组具有以下属性&#xff1a; 数组可以是一维、多维或交错的。创建数组实例时&#xff0c;将建立纬度数量和每个纬度的长度。 这些值在实例的生存期内无法更改。数值数组元素的默认…

英语写作中“遭受”、“面对”suffer (from)、expose、encounter、face、confront等的用法

一、在写作中涉及所“遭受”、所“面对”的是不利的因素、问题和结果&#xff0c;例如&#xff1a; difficulty、issue、problem、change、danger、disaster、consequence、(negative) result、overflow、congestion、insecurity、attack、disorder、disaster、obstacle 、thr…

CDN安全面临的问题及防御架构

CDN安全 SQL注入攻击&#xff08;各开发小组针对密码和权限的管理&#xff0c;和云安全部门的漏洞扫描和渗透测试&#xff09; Web Server的安全&#xff08;运营商和云安全部门或者漏洞纰漏第三方定期发布漏洞报告修复&#xff0c;例如&#xff1a;nginx版本号和nginx resol…

【Linux】【docker】安装sonarQube免费社区版9.9

文章目录 ⛺sonarQube 镜像容器⛺Linux 安装镜像&#x1f341;出现 Permission denied的异常&#x1f341;安装sonarQube 中文包&#x1f341;重启服务 ⛺代码上传到sonarQube扫描&#x1f341;java语言配置&#x1f341;配置 JS TS Php Go Python⛏️出现异常sonar-scanner.ba…

HTML5中Canvas学习笔记:Canvas

目录 一、HTML中Canvas画图strokeStyle 和 fillStyle 的区别是什么&#xff1f; 二、如何设置一幅canvas图中某个颜色透明&#xff1f; 三、H5 canvas中strokeRect参数如果是小数&#xff0c;如何处理&#xff1f; 四、H5 Canvas中如何画圆角矩形框&#xff1f; 一、HTML中…

TextBox基本使用

作用&#xff1a; 文本框&#xff0c;用于展示文本、输入文本 常用属性&#xff1a; 文本属性 允许多行 常用事件&#xff1a; 后台代码&#xff1a; private void textBox4_TextChanged(object sender, EventArgs e){//实时获取输入的文本label3.Text textBox4.Text;}

2023-08-05——JVM 栈

栈 stack 栈&#xff1a;数据结构 程序数据结构算法 栈&#xff1a;先进后出&#xff0c;后进先出 好比一个&#xff1a;桶 队列&#xff1a;先进先出&#xff08;FIFO &#xff1a;First Input First Out&#xff09; 好比一个&#xff1a;管道 栈&#xff1a;喝多了吐。队列…

Implicit Graph Neural Networks: A Monotone Operator Viewpoint

Implicit graph neural networks (IGNNs) solve a fixed-point equilibrium equation using Picard iteration for representation learning. 通常G的选择为&#xff1a;

WMS仓库管理系统研发规划说明

01 产品背景 1.1 背景概述 aboss WMS东南亚仓库管理系统是一个基于BigSeller系统的使用基础上&#xff0c;加上多仓库的解决思路&#xff0c;解决入库业务、出库业务、仓库调拨、库存调拨和虚仓管理等功能&#xff0c;对批次管理、物料对应、库存盘点、质检管理、虚仓管理和即…

mysql sql 语句sum求和嵌套数学表达式

今天有个需求, 已减高度 高度 x 单双开(单开1 双开2) x 2,要直接写在sql语句中。 表字段 包含 高度 和 单双开字段 值是字符串 (双开 左单开 右单开) -- 已减高度 2 * 单双开 * 高度 sum( -- 求和 表达式 已减高度 2 * 单双开 * 高度 t_cloth.hegiht * 2 * (case WHEN l…

【数据结构与算法】赫夫曼树

赫夫曼树 基本介绍 给定 n 个权值作为 n 个叶子结点&#xff0c;构造一棵二叉树&#xff0c;若该树的带权路径长度&#xff08;wpl&#xff09;达到最小&#xff0c;称这样的二叉树为最优二叉树&#xff0c;也称为哈夫曼树&#xff08;Huffman Tree&#xff09;&#xff0c;还…

物理试卷1-12题

答案解析 答案解析 备注1 滑动变阻器的分压式、限流式特点及选择方法解析 滑动变阻器的分压式、限流式特点及选择方法解析-电子发烧友网 2.为什么分压电路实验中的滑动变阻器要选择阻值范围小的 ? 为什么分压电路实验中的滑动变阻器要选择阻值范围小的&#xff1f; - 知乎

【福建事业单位-推理判断】06翻译推理、组合排列

【福建事业单位-推理判断】06翻译推理、组合排列 一、翻译推理1.1 前推后&#xff08;如果&#xff0c;那么&#xff09;1.1.2逆否命题1.3 传递关系1.4后推前&#xff08;只有才&#xff09;后推前&#xff1a;不....不............&#xff0c;除非....否则不..........1.4.2后…

JavaScript篇 this指向

文章目录 1.this 关键字2.this实质3.使用场合3.1.全局环境3.2.构造函数3.3.对象的方法 4. 使用注意4.1.避免多层 this4.2.避免数组处理方法中的 this4.3.避免回调函数中的 this 5.绑定this5.1.Function.prototype.call()5.2.Function.prototype.apply()5.3.Function.prototype.…

Netty:ByteBuf的readerIndex和writerIndex

说明 io.netty.buffer.ByteBuf的数据索引从0开始。ByteBuf保存一个readerIndex和一个writerIndex变量。readerIndex用于读取操作&#xff0c;writerIndex用于写入操作。 0 < readerIndex < writerIndex < capacity < maxCapacity 示例 获取当前的readerIndex和w…

【IDEA】常用插件清单

【IDEA】常用插件清单 arthas ideaCodeium: AI Autocomplete for xxxCommit-MessageGenerateAllSetterMaven HelperMybatisPlusOne Dark themePDF ViewerRainbow BracketsRestfulToolSequenceDiagramSonarLintTranslation arthas idea 快捷生成arthas命令 Codeium: AI Autoc…

ChatGPT: 人机交互的未来

ChatGPT: 人机交互的未来 ChatGPT背景ChatGPT的特点ChatGPT的应用场景结论 ChatGPT ChatGPT是一种基于大数据和机器学习的人工智能聊天机器人模型。它由国内团队发明、开发&#xff0c;并被命名为Mental AI。ChatGPT的目标是通过模拟自然对话的方式&#xff0c;提供高效、智能…

大数据技术之Clickhouse---入门篇---数据类型、表引擎

星光下的赶路人star的个人主页 今天没有开始的事&#xff0c;明天绝对不会完成 文章目录 1、数据类型1.1 整型1.2 浮点型1.3 布尔型1.4 Decimal型1.5 字符串1.6 枚举类型1.7 时间类型1.8 数组 2、表引擎2.1 表引擎的使用2.2 TinyLog2.3 Memory2.4 MergeTree2.4.1 Partition by分…

I/O 函数/缓存和字节流、占位符、getchar(),putchar()

I/O 函数 C 语言提供了一些函数&#xff0c;用于与外部设备通信&#xff0c;称为输入输出函数&#xff0c;简称 I/O 函数。输入&#xff08;import&#xff09;指的是获取外部数据&#xff0c;输出&#xff08;export&#xff09;指的是向外部传递数据。 缓存和字节流 严格地…

如何学好数据结构

如何学好数据结构 知道数据结构讲的是什么模块化类型化 本文 全为干货&#xff0c;没有晦涩难懂的专业名词&#xff0c;只有自己的 心得体会和有效的学习方法&#xff0c;希望可以帮到各位被数据结构犯难的同学 知道数据结构讲的是什么 在我们学习数据结构的时候&#xff0c…