【Spring云原生】Spring Batch:海量数据高并发任务处理!数据处理纵享新丝滑!事务管理机制+并行处理+实例应用讲解

简介: 【Spring云原生】Spring Batch:海量数据高并发任务处理!数据处理纵享新丝滑!事务管理机制+并行处理+实例应用讲解

实例应用:数据清洗和转换

使用Spring Batch清洗和转换数据

实例应用:数据导入和导出

使用Spring Batch导入和导出数据

实例应用:批处理定时任务

使用Spring Batch实现定时任务


介绍Spring Batch


Spring Batch是一个基于Java的开源批处理框架,用于处理大规模、重复性和高可靠性的任务。它提供了一种简单而强大的方式来处理批处理作业,如数据导入/导出、报表生成、批量处理等。


什么是Spring Batch?


Spring Batch旨在简化批处理作业的开发和管理。它提供了一种可扩展的模型来定义和执行批处理作业,将作业划分为多个步骤(Step),每个步骤又由一个或多个任务块(Chunk)组成。通过使用Spring Batch,可以轻松处理大量的数据和复杂的业务逻辑。


Spring Batch的特点和优势


  1. 可扩展性和可重用性:Spring Batch采用模块化的设计,提供了丰富的可扩展性和可重用性。可以根据具体需求自定义作业流程,添加或删除步骤,灵活地适应不同的批处理场景。
  2. 事务管理:Spring Batch提供了强大的事务管理机制,确保批处理作业的数据一致性和完整性。可以配置事务边界,使每个步骤或任务块在单独的事务中执行,保证了作业的可靠性。
  3. 监控和错误处理:Spring Batch提供了全面的监控和错误处理机制。可以通过监听器和回调函数来监控作业的执行情况,处理错误和异常情况,以及记录和报告作业的状态和指标。
  4. 并行处理:Spring Batch支持并行处理,可以将作业划分为多个独立的线程或进程来执行,提高作业的处理速度和效率。


Spring Batch入门


1. 安装和配置Spring Batch


首先,确保你的Java开发环境已经安装并配置好。然后,可以使用Maven或Gradle等构建工具来添加Spring Batch的依赖项到你的项目中。详细的安装和配置可以参考Spring Batch的官方文档。


2. 创建第一个批处理作业


在Spring Batch中,一个批处理作业由一个或多个步骤组成,每个步骤又由一个或多个任务块组成。下面是一个简单的示例,演示如何创建一个简单的批处理作业:

@Configuration
@EnableBatchProcessing
public class BatchConfiguration {
 
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
 
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
 
    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .tasklet((contribution, chunkContext) -> {
                    System.out.println("Hello, Spring Batch!");
                    return RepeatStatus.FINISHED;
                })
                .build();
    }
 
    @Bean
    public Job job(Step step1) {
        return jobBuilderFactory.get("job")
                .start(step1)
                .build();
    }
}


解析


首先使用@Configuration和@EnableBatchProcessing注解将类标记为Spring Batch的配置类。然后,使用JobBuilderFactory和StepBuilderFactory创建作业和步骤的构建器。在step1方法中,定义了一个简单的任务块,打印"Hello, Spring Batch!"并返回RepeatStatus.FINISHED。最后,在job方法中,使用jobBuilderFactory创建一个作业,并将step1作为作业的起始步骤。


3. 理解Job、Step和任务块


  • Job(作业):作业是一个独立的批处理任务,由一个或多个步骤组成。它描述了整个批处理过程的流程和顺序,并可以有自己的参数和配置。
  • Step(步骤块):步骤是作业的组成部分,用于执行特定的任务。一个作业可以包含一个或多个步骤,每个步骤都可以定义自己的任务和处理逻辑。
  • 任务块(Chunk):任务块是步骤的最小执行单元,用于处理一定量的数据。任务块将数据分为一块一块进行处理,可以定义读取数据、处理数据和写入数据的逻辑。


需求缔造:


假设我们有一个需求,需要从一个CSV文件中读取学生信息,对每个学生的成绩进行转换和校验,并将处理后的学生信息写入到一个数据库表中。


数据处理


数据读取和写入:Spring Batch提供了多种读取和写入数据的方式。可以使用ItemReader读取数据,例如从数据库、文件或消息队列中读取数据。然后使用ItemWriter将处理后的数据写入目标,如数据库表、文件或消息队列。


首先,我们需要定义一个数据模型来表示学生信息,例如

public class Student {
    private String name;
    private int score;
 
    // Getters and setters
    // ...
}

接下来,我们可以使用Spring Batch提供的FlatFileItemReader来读取CSV文件中的数据:

@Bean
public FlatFileItemReader<Student> studentItemReader() {
    FlatFileItemReader<Student> reader = new FlatFileItemReader<>();
    reader.setResource(new ClassPathResource("students.csv"));
    reader.setLineMapper(new DefaultLineMapper<Student>() {
        {
            setLineTokenizer(new DelimitedLineTokenizer() {
                {
                    setNames(new String[] { "name", "score" });
                }
            });
            setFieldSetMapper(new BeanWrapperFieldSetMapper<Student>() {
                {
                    setTargetType(Student.class);
                }
            });
        }
    });
    return reader;
}


支持的数据格式和数据源


Spring Batch支持各种数据格式和数据源。可以使用适配器和读写器来处理不同的数据格式,如CSV、XML、JSON等。同时,可以通过自定义的数据读取器和写入器来处理不同的数据源,如关系型数据库、NoSQL数据库等。


数据转换和校验


Spring Batch提供了数据转换和校验的机制。可以使用ItemProcessor对读取的数据进行转换、过滤和校验。ItemProcessor可以应用自定义的业务逻辑来处理每个数据项。


 我们配置了一个FlatFileItemReader,设置了CSV文件的位置和行映射器,指定了字段分隔符和字段到模型属性的映射关系。


接下来,我们可以定义一个ItemProcessor来对读取的学生信息进行转换和校验:

@Bean
public ItemProcessor<Student, Student> studentItemProcessor() {
    return new ItemProcessor<Student, Student>() {
        @Override
        public Student process(Student student) throws Exception {
            // 进行转换和校验
            if (student.getScore() < 0) {
                // 校验不通过,抛出异常
                throw new IllegalArgumentException("Invalid score for student: " + student.getName());
            }
            // 转换操作,例如将分数转换为百分制
            int percentage = student.getScore() * 10;
            student.setScore(percentage);
            return student;
        }
    };
}

在上述代码中,我们定义了一个ItemProcessor,对学生信息进行校验和转换。如果学生的分数小于0,则抛出异常;否则,将分数转换为百分制。


最后,我们可以使用Spring Batch提供的JdbcBatchItemWriter将处理后的学生信息写入数据库:

@Bean
public JdbcBatchItemWriter<Student> studentItemWriter(DataSource dataSource) {
    JdbcBatchItemWriter<Student> writer = new JdbcBatchItemWriter<>();
    writer.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
    writer.setSql("INSERT INTO students (name, score) VALUES (:name, :score)");
    writer.setDataSource(dataSource);
    return writer;
}

作业调度和监控


作业调度器的配置:Spring Batch提供了作业调度器来配置和管理批处理作业的执行。可以使用Spring的调度框架(如Quartz)或操作系统的调度工具(如cron)来调度作业。通过配置作业调度器,可以设置作业的触发时间、频率和其他调度参数。


在上述代码中,我们配置了一个JdbcBatchItemWriter,设置了SQL语句和数据源,将处理后的学生信息批量插入数据库表中。


最后,我们需要配置一个作业步骤来组装数据读取、处理和写入的过程:

@Bean
public Step processStudentStep(ItemReader<Student> reader, ItemProcessor<Student, Student> processor, ItemWriter<Student> writer) {
    return stepBuilderFactory.get("processStudentStep")
        .<Student, Student>chunk(10)
        .reader(reader)
        .processor(processor)
        .writer(writer)
        .build();
}

在上述代码中,我们使用stepBuilderFactory创建了一个步骤,并指定了数据读取器、处理器和写入器。


作业执行的监控和管理:Spring Batch提供了丰富的监控和管理功能。可以使用Spring Batch的管理接口和API来监控作业的执行状态、进度和性能指标。还可以使用日志记录、通知和报警机制来及时获取作业执行的状态和异常信息。


最后,我们可以配置一个作业来调度执行该步骤:

@Bean
public Job processStudentJob(JobBuilderFactory jobBuilderFactory, Step processStudentStep) {
    return jobBuilderFactory.get("processStudentJob")
        .flow(processStudentStep)
        .end()
        .build();
}

我们使用jobBuilderFactory创建了一个作业,并指定了步骤来执行。


通过以上的示例,我们演示了Spring Batch中数据读取和写入的方式,使用了FlatFileItemReader读取CSV文件,使用了JdbcBatchItemWriter将处理后的学生信息写入数据库。同时,我们使用了ItemProcessor对读取的学生信息进行转换和校验。这个例子还展示了Spring Batch对不同数据源和数据格式的支持,以及如何配置和组装作业步骤来完成整个批处理任务。


错误处理和重试机制


Spring Batch提供了错误处理和重试机制,以确保批处理作业的稳定性和可靠性。可以配置策略来处理读取、处理和写入过程中的错误和异常情况。可以设置重试次数、重试间隔和错误处理策略,以适应不同的错误场景和需求。


首先,我们可以在步骤配置中设置错误处理策略。例如,我们可以使用SkipPolicy来跳过某些异常,或者使用RetryPolicy来进行重试。

@Bean
public Step processStudentStep(ItemReader<Student> reader, ItemProcessor<Student, Student> processor, ItemWriter<Student> writer) {
    return stepBuilderFactory.get("processStudentStep")
        .<Student, Student>chunk(10)
        .reader(reader)
        .processor(processor)
        .writer(writer)
        .faultTolerant()
        .skip(Exception.class)
        .skipLimit(10)
        .retry(Exception.class)
        .retryLimit(3)
        .build();
}

我们使用faultTolerant()方法来启用错误处理策略。然后,使用skip(Exception.class)指定跳过某些异常,使用skipLimit(10)设置跳过的最大次数为10次。同时,使用retry(Exception.class)指定重试某些异常,使用retryLimit(3)设置重试的最大次数为3次。


在默认情况下,如果发生读取、处理或写入过程中的异常,Spring Batch将标记该项为错误项,并尝试跳过或重试,直到达到跳过或重试的次数上限为止。


此外,您还可以为每个步骤配置错误处理器,以定制化处理错误项的逻辑。例如,可以使用SkipListener来处理跳过的项,使用RetryListener来处理重试的项。

@Bean
public SkipListener<Student, Student> studentSkipListener() {
    return new SkipListener<Student, Student>() {
        @Override
        public void onSkipInRead(Throwable throwable) {
            // 处理读取过程中发生的异常
        }
 
        @Override
        public void onSkipInWrite(Student student, Throwable throwable) {
            // 处理写入过程中发生的异常
        }
 
        @Override
        public void onSkipInProcess(Student student, Throwable throwable) {
            // 处理处理过程中发生的异常
        }
    };
}
 
@Bean
public RetryListener studentRetryListener() {
    return new RetryListener() {
        @Override
        public <T, E extends Throwable> boolean open(RetryContext retryContext, RetryCallback<T, E> retryCallback) {
            // 在重试之前执行的逻辑
            return true;
        }
 
        @Override
        public <T, E extends Throwable> void onError(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
            // 处理重试过程中发生的异常
        }
 
        @Override
        public <T, E extends Throwable> void close(RetryContext retryContext, RetryCallback<T, E> retryCallback, Throwable throwable) {
            // 在重试之后执行的逻辑
        }
    };
}
 
@Bean
public Step processStudentStep(ItemReader<Student> reader, ItemProcessor<Student, Student> processor, ItemWriter<Student> writer,
                               SkipListener<Student, Student> skipListener, RetryListener retryListener) {
    return stepBuilderFactory.get("processStudentStep")
        .<Student, Student>chunk(10)
        .reader(reader)
        .processor(processor)
        .writer(writer)
        .faultTolerant()
        .skip(Exception.class)
        .skipLimit(10)
        .retry(Exception.class)
        .retryLimit(3)
        .listener(skipListener)
        .listener(retryListener)
        .build();
}

批处理最佳实践


  • 数据量控制:在批处理作业中,应注意控制数据量的大小,以避免内存溢出或处理速度过慢的问题。可以通过分块(Chunk)处理和分页读取的方式来控制数据量。
  • 事务管理:在批处理作业中,对于需要保证数据一致性和完整性的操作,应使用适当的事务管理机制。可以配置事务边界,确保每个步骤或任务块在独立的事务中执行。
  • 错误处理和日志记录:合理处理错误和异常情况是批处理作业的重要部分。应使用适当的错误处理策略、日志记录和报警机制,以便及时发现和处理问题。
  • 性能调优:在批处理作业中,应关注性能调优的问题。可以通过合理的并行处理、合理配置的线程池和适当的数据读取和写入策略来提高作业的处理速度和效率。
  • 监控和管理:对于长时间运行的批处理作业,应设置适当的监控和管理机制。可以使用监控工具、警报系统和自动化任务管理工具来监控作业的执行情况和性能指标。


扩展Spring Batch


自定义读取器、写入器和处理器


Spring Batch提供了许多扩展点,可以通过自定义读取器、写入器和处理器以及其他组件来扩展和定制批处理作业的功能。

public class MyItemReader implements ItemReader<String> {
    private List<String> data = Arrays.asList("item1", "item2", "item3");
    private Iterator<String> iterator = data.iterator();
 
    @Override
    public String read() throws Exception {
        if (iterator.hasNext()) {
            return iterator.next();
        } else {
            return null;
        }
    }
}

自定义写入器:

public class MyItemWriter implements ItemWriter<String> {
    @Override
    public void write(List<? extends String> items) throws Exception {
        for (String item : items) {
            // 自定义写入逻辑
        }
    }
}

自定义处理器:

public class MyItemProcessor implements ItemProcessor<String, String> {
    @Override
    public String process(String item) throws Exception {
        // 自定义处理逻辑
        return item.toUpperCase();
    }
}

批处理作业的并行处理:


Spring Batch支持将批处理作业划分为多个独立的步骤,并通过多线程或分布式处理来实现并行处理。


  1. 多线程处理:可以通过配置TaskExecutor来实现多线程处理。通过使用TaskExecutor,每个步骤可以在独立的线程中执行,从而实现并行处理。
@Bean
public TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(5);
    executor.setMaxPoolSize(10);
    executor.setQueueCapacity(25);
    return executor;
}
 
@Bean
public Step myStep(ItemReader<String> reader, ItemProcessor<String, String> processor, ItemWriter<String> writer) {
    return stepBuilderFactory.get("myStep")
            .<String, String>chunk(10)
            .reader(reader)
            .processor(processor)
            .writer(writer)
            .taskExecutor(taskExecutor())
            .build();
}

在上述代码中,我们通过taskExecutor()方法定义了一个线程池任务执行器,并将其配置到步骤中的taskExecutor()方法中。


  1. 分布式处理:如果需要更高的并行性和可伸缩性,可以考虑使用分布式处理。Spring Batch提供了与Spring Integration和Spring Cloud Task等项目的集成,以实现分布式部署和处理。


与其他Spring项目的集成


与Spring Integration的集成:


首先,需要在Spring Batch作业中配置Spring Integration的消息通道和适配器。可以使用消息通道来发送和接收作业的输入和输出数据,使用适配器来与外部系统进行交互。

@Configuration
@EnableBatchProcessing
@EnableIntegration
public class BatchConfiguration {
 
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
 
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
 
    @Autowired
    private MyItemReader reader;
 
    @Autowired
    private MyItemProcessor processor;
 
    @Autowired
    private MyItemWriter writer;
 
    @Bean
    public IntegrationFlow myJobFlow() {
        return IntegrationFlows.from("jobInputChannel")
                .handle(jobLaunchingGateway())
                .get();
    }
 
    @Bean
    public MessageChannel jobInputChannel() {
        return new DirectChannel();
    }
 
    @Bean
    public MessageChannel jobOutputChannel() {
        return new DirectChannel();
    }
 
    @Bean
    public MessageChannel stepInputChannel() {
        return new DirectChannel();
    }
 
    @Bean
    public MessageChannel stepOutputChannel() {
        return new DirectChannel();
    }
 
    @Bean
    public JobLaunchingGateway jobLaunchingGateway() {
        SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
        jobLauncher.setJobRepository(jobRepository());
        return new JobLaunchingGateway(jobLauncher);
    }
 
    @Bean
    public JobRepository jobRepository() {
        // 配置作业存储库
    }
 
    @Bean
    public Job myJob() {
        return jobBuilderFactory.get("myJob")
                .start(step1())
                .build();
    }
 
    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .<String, String>chunk(10)
                .reader(reader)
                .processor(processor)
                .writer(writer)
                .inputChannel(stepInputChannel())
                .outputChannel(stepOutputChannel())
                .build();
    }
}

在上述代码中,我们配置了Spring Batch作业的消息通道和适配器。myJobFlow()方法定义了一个整合流程,它从名为jobInputChannel的消息通道接收作业请求,并通过jobLaunchingGateway()方法启动作业。jobLaunchingGateway()方法创建一个JobLaunchingGateway实例,用于启动作业。


与Spring Cloud Task的集成:


首先,需要在Spring Batch作业中配置Spring Cloud Task的任务启动器和任务监听器。任务启动器用于启动和管理分布式任务,任务监听器用于在任务执行期间执行一些操作。

@Configuration
@EnableBatchProcessing
@EnableTask
public class BatchConfiguration {
 
    @Autowired
    private JobBuilderFactory jobBuilderFactory;
 
    @Autowired
    private StepBuilderFactory stepBuilderFactory;
 
    @Autowired
    private MyItemReader reader;
 
    @Autowired
    private MyItemProcessor processor;
 
    @Autowired
    private MyItemWriter writer;
 
    @Bean
    public TaskConfigurer taskConfigurer() {
        return new DefaultTaskConfigurer();
    }
 
    @Bean
    public TaskExecutor taskExecutor() {
        return new SimpleAsyncTaskExecutor();
    }
 
    @Bean
    public Job myJob() {
        return jobBuilderFactory.get("myJob")
                .start(step1())
                .build();
    }
 
    @Bean
    public Step step1() {
        return stepBuilderFactory.get("step1")
                .<String, String>chunk(10)
                .reader(reader)
                .processor(processor)
                .writer(writer)
                .taskExecutor(taskExecutor())
                .build();
    }
 
    @Bean
    public TaskListener myTaskListener() {
        return new MyTaskListener();
    }
 
    @Bean
    public TaskExecutionListener myTaskExecutionListener() {
        return new MyTaskExecutionListener();
    }
}
相关文章
|
2月前
|
Java Spring
Spring中事务失效的场景
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是被代理对象调用, 那么@Transactional会失效 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效 如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
|
2月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
51 0
|
2月前
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
121 0
|
2月前
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
33 0
|
2月前
|
SQL Java 数据库连接
Spring中的事务是如何实现的
1. Spring事务底层是基于数据库事务和AOP机制的 2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean 3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解 4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接 5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮ 常重要的⼀步 6. 然后执⾏当前⽅法,⽅法中会执⾏sql 7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务 8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务
|
2月前
|
JavaScript Java 开发者
Spring事务失效,常见的情况有哪些?
本文总结了Spring事务失效的7种常见情况,包括未启用事务管理功能、方法非public类型、数据源未配置事务管理器、自身调用问题、异常类型错误、异常被吞以及业务和事务代码不在同一线程中。同时提供了两种快速定位事务相关Bug的方法:通过查看日志(设置为debug模式)或调试代码(在TransactionInterceptor的invoke方法中设置断点)。文章帮助开发者更好地理解和解决Spring事务中的问题。
|
2月前
|
Java 微服务 Spring
微服务——SpringBoot使用归纳——Spring Boot中使用拦截器——拦截器使用实例
本文主要讲解了Spring Boot中拦截器的使用实例,包括判断用户是否登录和取消特定拦截操作两大场景。通过token验证实现登录状态检查,未登录则拦截请求;定义自定义注解@UnInterception实现灵活取消拦截功能。最后总结了拦截器的创建、配置及对静态资源的影响,并提供两种配置方式供选择,帮助读者掌握拦截器的实际应用。
46 0
|
3月前
|
传感器 监控 安全
智慧工地云平台的技术架构解析:微服务+Spring Cloud如何支撑海量数据?
慧工地解决方案依托AI、物联网和BIM技术,实现对施工现场的全方位、立体化管理。通过规范施工、减少安全隐患、节省人力、降低运营成本,提升工地管理的安全性、效率和精益度。该方案适用于大型建筑、基础设施、房地产开发等场景,具备微服务架构、大数据与AI分析、物联网设备联网、多端协同等创新点,推动建筑行业向数字化、智能化转型。未来将融合5G、区块链等技术,助力智慧城市建设。
137 0
|
4月前
|
SQL Java 关系型数据库
【SpringFramework】Spring事务
本文简述Spring中数据库及事务相关衍伸知识点。
65 9
|
5月前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
315 13

热门文章

最新文章