Spring 声明式事务

news2024/11/19 18:37:10

Spring 声明式事务

    • 1.Spring 事务管理概述
      • 1.1 事务管理的重要性
      • 1.2 Spring事务管理的两种方式
        • 1.2.1 编程式事务管理
        • 1.2.2 声明式事务管理
      • 1.3 为什么选择声明式事务管理
    • 2. 声明式事务管理
      • 2.1 基本用法
      • 2.2 常用属性
        • 2.2.1 propagation(传播行为)
        • 2.2.2 isolation(隔离级别)
        • 2.2.3 readOnly(只读事务)
        • 2.2.4 timeout(超时时间)
        • 2.2.5 rollbackFor(回滚异常)
    • 3. Spring事务失效
    • 4. 案例
      • 4.1 前期准备
        • 4.1.1 依赖引入
        • 4.1.2 数据库建表语句
        • 4.1.3 实体类以及相关代码
      • 4.2 转账案例
        • 4.2.1 测试成功
        • 4.2.2 测试回滚
        • 4.2.3 测试受检异常
        • 4.2.4 测试 rollBackFor 属性
        • 4.2.5 测试隔离级别

Spring 提供了两个事务管理方式一种是编程式(很少用),一种是声明式事务。声明式事务管理将事务管理的代码从业务逻辑中分离出来,使得代码更清晰、可维护。使得开发者可以通过配置而不是编写大量的代码来管理事务。

使用这里我们只介绍声明式事务

1.Spring 事务管理概述

1.1 事务管理的重要性

在应用程序中,事务管理是确保数据操作的一致性、隔离性、持久性和原子性的关键机制。当多个数据库操作必须作为一个不可分割的单元执行时,事务管理变得至关重要。对于复杂的业务逻辑,事务能够确保在并发和异常情况下,数据库始终保持一致性。

1.2 Spring事务管理的两种方式

Spring框架提供了两种主要的事务管理方式,分别是编程式事务管理和声明式事务管理。

1.2.1 编程式事务管理

编程式事务管理要求开发者通过编写代码来管理事务的开始、提交和回滚。虽然具有灵活性,但容易导致代码冗余和可读性差。

try {
    // 开始事务
    transactionManager.beginTransaction();
    
    // 执行业务逻辑
    
    // 提交事务
    transactionManager.commit();
} catch (Exception e) {
    // 发生异常,回滚事务
    transactionManager.rollback();
    throw e;
}
1.2.2 声明式事务管理

相比之下,声明式事务管理通过配置文件或注解的方式实现事务控制,将事务逻辑从业务代码中分离出来。这种方式更加简洁、可维护,并提供更好的可读性。

@Transactional
public void performBusinessLogic() {
    // 业务逻辑
}

1.3 为什么选择声明式事务管理

选择声明式事务管理有以下优势:

  • 简洁性: 通过注解或XML配置,开发者无需编写冗长的事务管理代码,使代码更加简洁清晰。
  • 可维护性: 事务逻辑与业务逻辑分离,易于维护和理解。
  • 可读性: 使用注解或XML配置,事务逻辑与业务逻辑在代码中更易于辨认,提高代码的可读性。
  • 一致性: 通过统一的配置方式,整个应用程序可以保持一致的事务管理策略,减少错误和不一致性。
  • 集成性: 声明式事务更好地与Spring的其他特性(如AOP)集成,提供更全面的解决方案。

综合而言,声明式事务管理是Spring中推荐的事务管理方式,它能够提高代码的可维护性、可读性,并与其他Spring特性协同工作,使得开发者能够更专注于业务逻辑的实现而不是事务的管理。

2. 声明式事务管理

声明式事务管理建立在AOP之上,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

Spring 根据类或者方法上是否有@transactional注解来判断是否开启事务。

2.1 基本用法

在类上使用

@Transactional
public class TestService {
    // 类中所有方法都将使用默认的事务配置
}

在方法上使用

public class TestService {

    @Transactional
    public void method1() {
        // 这个方法将使用默认的事务配置
    }

    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
    public void method2() {
        // 这个方法将使用指定的事务配置
    }
}

2.2 常用属性

2.2.1 propagation(传播行为)

@Transactional 注解的 propagation 属性用于定义事务的传播行为,它指定在方法被调用时,当前方法的事务如何与现有的事务进行交互。

@Transactional(propagation = Propagation.*)
public void method1() {
    // ...
}

定义事务的传播行为,包括 :

  • REQUIRED:默认,如果当前存在事务,则加入该事务,如果不存在事务,则新建一个事务。
  • REQUIRES_NEW:无论当前是否存在事务,都会创建一个新的事务,如果存在事务,则将其挂起。
  • SUPPORTS:如果当前存在事务,则加入该事务,如果不存在事务,则以非事务的方式执行。
  • MANDATORY:该传播行为要求当前方法必须在一个事务中执行,否则将抛出异常。
  • NOT_SUPPORTED:以非事务的方式执行,如果当前存在事务,则将其挂起。
  • NEVER:以非事务的方式执行,如果当前存在事务,则抛出异常。
  • NESTED:如果当前存在事务,则创建一个嵌套事务,并在嵌套事务内执行。嵌套事务是外部事务的一部分,但有独立的提交和回滚。
2.2.2 isolation(隔离级别)

@Transactional 注解中的 isolation 属性用于指定事务的隔离级别。隔离级别定义了多个事务并发执行时,彼此之间的可见性和影响的程度。

@Transactional(isolation = Isolation.*)
public void method1() {
    // ...
}

Spring 支持以下五个隔离级别:

  • DEFAULT:默认,使用底层数据库的默认隔离级别。通常为数据库的默认配置,比如 MySQL 默认的是 REPEATABLE_READ,而 Oracle 默认的是 READ_COMMITTED

  • READ_UNCOMMITTED(读未提交): 允许一个事务读取另一个事务未提交的数据。这是最低的隔离级别,可能导致脏读、不可重复读和幻读的问题。

  • READ_COMMITTED(读已提交):保证一个事务提交后才能被其他事务读取。这是大多数数据库的默认隔离级别,可以避免脏读,但仍可能存在不可重复读和幻读的问题。

  • REPEATABLE_READ(可重复读): 对相同字段的多次读取结果是一致的,除非自己进行了数据更新。避免了不可重复读的问题,但仍可能存在幻读的问题。

  • SERIALIZABLE(串行化): 最高的隔离级别,确保每个事务都完全看不到其他事务的操作,包括读取和写入。可以避免脏读、不可重复读和幻读的问题,但也降低了并发性能。

2.2.3 readOnly(只读事务)

标识事务是否为只读,可以提高事务的性能。

@Transactional(readOnly = true)
public void method1() {
    // ...
}
2.2.4 timeout(超时时间)

指定事务的超时时间,单位为秒。

@Transactional(timeout = 60)
public void method1() {
    // ...
}
2.2.5 rollbackFor(回滚异常)

指定哪些异常触发事务回滚。@Transactional 注解默认只对运行时异常进行事务回滚,对检查时异常不回滚。

@Transactional(rollbackFor = {SQLException.class, MyCustomException.class})
public void method1() {
    // ...
}

3. Spring事务失效

哪些情况下会导致Spring事务失效,对应的原因是什么?

  • 1.方法内的自调用:Spring事务是基于AOP的,只要使用代理对象调用某个方法时,Spring事务才能生效,而在一个方法中调用使用this.xxxO调用方法时,this并不是代理对象,所以会导致事务失效。

    • 解决办法1:将需要在同一事务中执行的方法抽取到一个独立的Bean中,通过依赖注入的方式调用该Bean。确保方法调用经过代理对象,从而激活事务。

    • 解决办法2:在类内部通过依赖注入的方式,将当前类注入到自己中,然后通过注入的对象调用方法。这样确保调用经过代理对象,从而使事务生效。

    • 解决办法3:使用AopContext.currentProxy()获取当前代理对象,通过这个代理对象调用方法。结合@EnableAspectJAutoProxy(exposeProxy=true)注解开启对当前代理对象的暴露,确保事务能够正确地被激活。

  • 2.方法是private的:Spring事务会基于CGLIB来进行AOP,而CGLIB会基于父子类来失效,子类是代理类,父类是被代理类,如果父类中的某个方法是private的,那么子类就没有办法重写它,也就没有办法额外增加Spring事务的逻辑。

  • 3.方法是final的:原因和private是了样的,也是由于子类不能重写父类中的final的方法

  • 4.单独的线程调用方法:当Mybatis或JdbcTemplate执行SQL时,会从ThreadLocal中去获取数据库连接对象,如果开启事务的线程和执行SQL的线程是同一个,那么就能拿到数据库连接对象,如果不是同一个线程,那就拿到不到数据库连接对象,这样,Mybatis或JdbcTemplate就会自己去新建一个数据库连接用来执行SQL,此数据库连接的autocommit为true,那么执行完SQL就会提交,后续再抛异常也就不能再回滚之前已经提交了的SQL了。

  • 5.没加@Configuration注解:如果用SpringBoot基本没有这个问题,但是如果用的Spring,那么可能会有这个问题,这个问题的原因其实也是由于Mybatis或JdbcTemplate会从ThreadLocal中去获取数据库连接,但是ThreadLocal中存储的是一个MAP,MAP的key为DataSource对象,value为连接对象,而如果我们没有在AppConfig上添加@Configuration注解的话,会导致MAP中存的DataSource对象和Mybatis和JdbcTemplate中的DataSource对象不相等,从而也拿不到数据库连接,导致自己去创建数据库连接了。

  • 6.异常被吃掉:如果Spring事务没有捕获到异常,那么也就不会回滚了,默认情况下Spring会捕获RuntimeException和Error。

  • 7.类没有被Spring管理

  • 8.数据库不支持事务

4. 案例

4.1 前期准备

4.1.1 依赖引入
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- 使用Plus 简化开发 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>
    </dependencies>
4.1.2 数据库建表语句
CREATE TABLE `users`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_name` varchar(18)  NOT NULL,
  `balance` decimal(10, 2) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
)
4.1.3 实体类以及相关代码

User

@Data
public class User {
    private int id;
    private String userName;
    private Double balance;
}

UserMapper

@Mapper
public interface UserMapper extends BaseMapper<User> {
}

UserService

public interface UserService extends IService<User> {
}

UserServiceImpl

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

application.yml

Spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 120125hzy.
    type:  com.alibaba.druid.pool.DruidDataSource
mybatis-plus: # MyBatis Plus配置
  configuration: 
    map-underscore-to-camel-case: true # 驼峰下划线转换
logging: # 控制台打印 SQL
  level:
    com:
      example:
        mapper: debug

4.2 转账案例

在Spring中,你可以使用@Transactional注解来实现声明式事务。这个注解可以应用于类级别或方法级别,具体取决于你想要控制事务的粒度。

接口编写

    /**
     * 测试转账
     */
    String Transfer(Integer fromId, Integer toId, Double money);

生成单元测试,每次测试前两人余额都调整为 10000.00

@SpringBootTest
class UserServiceImplTest {
    @Autowired
    private UserService userService;

    @Test
    void transfer() {
       String  ans =  userService.Transfer(1,2,600.0);
       assert ans.equals("转账成功");
    }
}
4.2.1 测试成功
 @Override
    @Transactional
    public String Transfer(Integer fromId, Integer toId, Double money) {
        try {
            // 查询转出账户
            User fromUser = getById(fromId);
            if (fromUser == null) {
                throw new RuntimeException("转出账户不存在");
            }

            // 查询转入账户
            User toUser = getById(toId);
            if (toUser == null) {
                throw new RuntimeException("转入账户不存在");
            }

            // 检查余额是否足够
            if (fromUser.getBalance() < money) {
                throw new RuntimeException("余额不足");
            }

            // 更新转出账户余额
            fromUser.setBalance(fromUser.getBalance() - money);
            updateById(fromUser);

            // 更新转入账户余额
            toUser.setBalance(toUser.getBalance() + money);
            updateById(toUser);
 			log.info("转账成功");
            return "转账成功";
        } catch (Exception e) {
            throw new RuntimeException("转账失败: " + e.getMessage());
        }
    }

测试通过

在这里插入图片描述

数据库也成功修改

在这里插入图片描述

4.2.2 测试回滚
   @Override
    @Transactional
    public String Transfer(Integer fromId, Integer toId, Double money) {
        try {
            // 查询转出账户
            User fromUser = getById(fromId);
            if (fromUser == null) {
                throw new RuntimeException("转出账户不存在");
            }

            // 查询转入账户
            User toUser = getById(toId);
            if (toUser == null) {
                throw new RuntimeException("转入账户不存在");
            }

            // 检查余额是否足够
            if (fromUser.getBalance() < money) {
                throw new RuntimeException("余额不足");
            }

            // 更新转出账户余额
            fromUser.setBalance(fromUser.getBalance() - money);
            updateById(fromUser);

            // 手动抛出异常,测试事务回滚
            if (1 == 1) throw new RuntimeException("转账异常,事务回滚");
            
            // 更新转入账户余额
            toUser.setBalance(toUser.getBalance() + money);
            updateById(toUser);

            log.info("转账成功");
            return "转账成功";
        } catch (Exception e) {
            throw new RuntimeException("转账失败: " + e.getMessage());
        }
    }

测试未通过

在这里插入图片描述

数据库也没有改变

4.2.3 测试受检异常
    @Override
    @Transactional
    public String Transfer(Integer fromId, Integer toId, Double money) throws SQLException {
        try {
            // 查询转出账户
            User fromUser = getById(fromId);
            if (fromUser == null) {
                throw new RuntimeException("转出账户不存在");
            }

            // 查询转入账户
            User toUser = getById(toId);
            if (toUser == null) {
                throw new RuntimeException("转入账户不存在");
            }

            // 检查余额是否足够
            if (fromUser.getBalance() < money) {
                throw new RuntimeException("余额不足");
            }

            // 更新转出账户余额
            fromUser.setBalance(fromUser.getBalance() - money);
            updateById(fromUser);

            // 手动抛出异常,测试事务回滚
            if (1 == 1) throw new RuntimeException("转账异常,事务回滚");

            // 更新转入账户余额
            toUser.setBalance(toUser.getBalance() + money);
            updateById(toUser);

            log.info("转账成功");
            return "转账成功";
        } catch (Exception e) {
            throw new SQLException("转账失败: " + e.getMessage());
        }
    }

测试未通过

在这里插入图片描述

事务未回滚

在这里插入图片描述

4.2.4 测试 rollBackFor 属性
@Transactional(rollbackFor = SQLException.class)

这次成功回滚了。

在这里插入图片描述

4.2.5 测试隔离级别

添加一个接口

    /**
     *  测试付款
     */
    String payment(Integer id,Double money);
    @Override
    @Transactional()
    public String payment(Integer id, Double money) {
        try {
            User user = getById(id);
            if (user == null) {
                throw new RuntimeException("支付账户不存在");
            }
            // 更新转出账户余额
            user.setBalance(user.getBalance()-money);
            updateById(user);
            Thread.sleep(5000);
            if (1 == 1) throw new RuntimeException("转账异常,事务回滚");
            log.info("支付成功");
        }catch (RuntimeException e){
            throw new RuntimeException("支付失败");
        }catch (Exception e){

        }
        return "支付成功";
    }

修改 Transfer()

@Override
    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public String Transfer(Integer fromId, Integer toId, Double money){
        try {
            // 查询转出账户
            User fromUser = getById(fromId);
            if (fromUser == null) {
                throw new RuntimeException("转出账户不存在");
            }
            log.info("id: {}的余额:{}",fromUser.getId(),fromUser.getBalance());
            // 查询转入账户
            User toUser = getById(toId);
            if (toUser == null) {
                throw new RuntimeException("转入账户不存在");
            }

            // 检查余额是否足够
            if (fromUser.getBalance() < money) {
                throw new RuntimeException("余额不足");
            }

            // 更新转出账户余额
            fromUser.setBalance(fromUser.getBalance() - money);
            updateById(fromUser);

            // 更新转入账户余额
            toUser.setBalance(toUser.getBalance() + money);
            updateById(toUser);

            log.info("转账成功");
            return "转账成功";
        } catch (Exception e) {
            throw new RuntimeException("转账失败: " + e.getMessage());
        }
    }

测试函数

    @Test
    void test() throws InterruptedException {
        Thread t1 = new Thread(()->{
            userService.payment(1,500.0);
        });
        t1.start();
        Thread.sleep(2000);
        userService.Transfer(1,2,600.0);
    }

在这里插入图片描述

转账接口读取到了支付接口修改的数据,最终转账成功。而支付接口支付失败,但是张三还是少了1100

在这里插入图片描述

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

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

相关文章

希宝猫罐头怎么样?专业人士告诉你营养好的猫罐头推荐

作为一个6年铲屎官来说&#xff0c;买过的猫罐头可以说是不计其数啦。对于猫罐头品牌选购和喂养相关知识&#xff0c;我还是有点心得的。希宝猫罐头怎么样呢&#xff1f; 希宝猫罐头采用了先进的加工工艺&#xff0c;注重产品的包装和密封性&#xff0c;包装设计比较符合年轻人…

年底了,项目预算怎么创建?9个步骤直接搞定

如果将项目比作一辆汽车&#xff0c;那么预算就是它的燃料。就像汽车需要汽油一样&#xff0c;项目也需要资金和资源来维持运转。而作为项目经理&#xff0c;应该尽量用最有效的方式规划和使用这些资源&#xff0c;使项目按时交付。 项目预算是一项计划&#xff0c;其中详细说…

使用Plex结合cpolar搭建本地私人媒体站并实现远程访问

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

使用Java语言判断一个数据类型是奇数还是偶数

判断一个数字类型是奇数&#xff0c;还是偶数&#xff0c;只需要引入Scanner类&#xff0c;然后按照数据类型的定义方式进行定义&#xff0c;比较是按照与2进行整除后的结果&#xff1b;如果余数为零&#xff0c;则代表为偶数&#xff0c;否则为奇数。 import java.util.Scann…

学习php中使用composer下载安装firebase/php-jwt 以及调用方法

学习php中使用composer下载安装firebase/php-jwt 以及调用方法 1、安装firebase/php-jwt2、封装jwt类 1、安装firebase/php-jwt composer require firebase/php-jwt安装好以后出现以下文件: 2、封装jwt类 根据所使用的php框架&#xff0c;在指定目录创建 Token.php <?ph…

对小工蚁关于LLM的技术总结+个人思考

LLM能力从何而来&#xff1a; 推理能力&#xff1f;目前还未知晓为何得到结果 斯坦福大学的AI指数 通过种子任务自动生成数据训练LLM 基于self-instruct及175条种子任务生成数据。 LLM大模型如何进行微调&#xff1f; 1.blog/trl-peft.md at main huggingface/blog GitH…

2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-A

2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-A 目录 2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-A 需要环境或者解析可以私信 &#xff08;二&#xff09;A 模块基础设施设置/安全加固&#xff08;200 分&…

【跨境营商】创新科技助力数码转型 增强大湾区企业核心竞争力

粤港澳大湾区作为国家的重点发展区域&#xff0c;坐拥丰富的资源及商机&#xff0c;企业积极推行数码化&#xff0c;务求在大湾区抢占先机。香港电讯商业客户业务董事总经理吴家隆表示&#xff0c;近年企业锐意加快数码化步伐&#xff0c;香港电讯以创新科技融入的数码方案&…

【Axure高保真原型】3D大屏可视化模板

今天和大家分享3D大屏可视化的原型模板&#xff0c;里面包括3D条形图、3D柱状图、3D饼图、3D环形图、3D金字塔图&#xff0c;鼠标移入图表&#xff0c;对应区域会高亮变色&#xff0c;并且显示对应的数据标签&#xff0c;具体效果可以点击下方视频观看或打开下方预览地址查看哦…

新媒体营销模拟实训室解决方案

一、引言 随着互联网的发展&#xff0c;新媒体已成为企业进行营销和品牌推广的重要渠道。然而&#xff0c;对于许多企业来说&#xff0c;如何在新媒体上进行有效的营销仍是一大挑战。为了解决这个问题&#xff0c;我们推出了一款新媒体营销模拟实训室解决方案&#xff0c;以帮…

Linux DataEase数据可视化分析工具本地部署与远程访问

文章目录 前言1. 安装DataEase2. 本地访问测试3. 安装 cpolar内网穿透软件4. 配置DataEase公网访问地址5. 公网远程访问Data Ease6. 固定Data Ease公网地址 前言 DataEase 是开源的数据可视化分析工具&#xff0c;帮助用户快速分析数据并洞察业务趋势&#xff0c;从而实现业务…

池化技术(对象池)

什么是池化技术 池化技术是一种很常见的编程技巧&#xff0c;目的在于提前创建如内存&#xff0c;对象&#xff0c;线程资源&#xff0c;降低程序运行时频繁的创建销毁带来的开销。常见的有线程池&#xff0c;内存池&#xff0c;对象池等。 池化技术如何提高性能&#xff0c;…

分享几个电视颜色测试图形卡

介绍 本文分享几个常见的电视颜色测试图形卡和一段matlab程序&#xff0c;完成JPG转FPGA烧写文件&#xff0c;便于把彩色图片预装载到FPGA内。 电视颜色测试图形卡 一种专业检测电视显示效果的工具。它通常由一张卡片和一些色块组成&#xff0c;可以根据标准色彩空间和颜色渐…

A simple two-dimensional Brownian motion animation

This code continues the previous blog post on two-dimensional collisions to model Brownian motion. The code is on GitHub page ‘https://github.com/xnx/collision’. The core classes, PeriodicParticle and PeriodicSimulation are derived from the original Par…

部分c++11特性介绍

在2003年C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff0c;使得C03这个名字已经取代了C98称为C11之前的最新C标准名称。不过由于C03(TC1)主要是对C98标准中的漏洞进行修复&#xff0c;语言的核心部分则没有改动&#xff0c;因此人们习惯性的把两个标准合并称为C98/03标…

二维码智慧门牌管理系统升级解决方案:存疑地址轻松管理

文章目录 前言一、存疑地址的统一管理二、数据查询、导出和编辑功能三、提交地址审核机制 前言 随着二维码智慧门牌管理系统在企业中的广泛应用&#xff0c;地址存疑成为了一个亟待解决的问题。为了帮助企业有效管理这些存疑地址&#xff0c;我们推出了升级解决方案&#xff0…

网页文章采集工具-人工智能AI功能

简数采集器是一款支持人工智能AI功能的网页文章采集工具&#xff0c;它可以调用百度的文心一言AI对采集的数据进行分析&#xff0c;处理&#xff0c;内容创作等等&#xff0c;根据你的需求进行更加灵活的数据采集和处理。 文心一言人工智能AI功能使用方法&#xff1a; 1. 填写…

2023年中国中高端服饰品牌经营现状和市场发展趋势分析|徐礼昭

徐礼昭&#xff08;商派市场负责人、RRL重构零售实验室负责人&#xff09; 一、引言 中国服饰市场在过去的几年中经历了快速的发展&#xff0c;其中中高端市场更是呈现出强劲的增长势头。然而&#xff0c;随着消费者需求的不断变化和市场竞争的加剧&#xff0c;中高端服饰品牌…

观测云产品更新 | 监控、数据脱敏、快照分享等优化

观测云更新 监控 1、监控器 - 事件内容插入链接的联动优化&#xff1a;根据检测指标自动生成跳转链接&#xff0c;支持在插入链接后调整过滤条件和时间范围&#xff0c;您也可以自定义跳转链接。其中&#xff0c;若需要插入跳转到仪表板的链接&#xff0c;基于以上逻辑&#…

《洛谷深入浅出进阶篇》同余方程+中国剩余定理——洛谷P1495

这篇文章讲介绍&#xff1a;同余方程&#xff0c;中国剩余定理 什么是同余方程&#xff1f; xy &#xff08;mod p&#xff09;这样的&#xff0c;带同余号的式子就是同余方程。 什么是中国剩余定理&#xff1f; 中国剩余定理&#xff0c;顾名思义是出自中国&#xff0c;它…