Spring Batch批处理框架优化实践

本文涉及的产品
云原生大数据计算服务 MaxCompute,5000CU*H 100GB 3个月
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
简介: Spring Batch是一种用于批处理的框架基于Spring Framework开发,通过读取大量的数据、处理数据和写入大量数据来满足各种类型的企业级批处理需求。Spring Batch可以很好地处理大量数据,并且提供了丰富的可扩展组件,业务逻辑与框架层的一系列处理步骤的集成也比较简单。Spring Batch可以很好地支持程序员针对大量的数据,编写代码来执行规范的操作序列,提高开发效率,降低了对于数据库等系统资源访问的影响。

一、Spring Batch简介

1 框架概述

Spring Batch是一种用于批处理的框架基于Spring Framework开发,通过读取大量的数据、处理数据和写入大量数据来满足各种类型的企业级批处理需求。Spring Batch可以很好地处理大量数据,并且提供了丰富的可扩展组件,业务逻辑与框架层的一系列处理步骤的集成也比较简单。Spring Batch可以很好地支持程序员针对大量的数据,编写代码来执行规范的操作序列,提高开发效率,降低了对于数据库等系统资源访问的影响。

2 核心概念和组件

Spring Batch主要包含以下核心概念和组件:

  • Job:一个可以被执行的批业务逻辑。
  • Step:一个Job中独立的一个小步骤。
  • ExecutionContext:每次Job或者Step执行时,都会创建该对象保存这次执行的上下文状态。
  • ItemReader:用于读取相应的数据。
  • ItemProcessor:用于处理ItemReader读取出来的数据并进行相应的业务处理。
  • ItemWriter: 用于将ItemProcessor处理好后的数据写入到目标存储位置。

二、批处理优化实践

1 减少读写次数

1.1 分页处理数据

在进行批处理时需要避免扫描所有的数据,而是应该分批读取并处理数据,这样可以避免对系统资源产生过大压力。对于大数据量的处理任务,建议采取分页处理技术,将大数据量拆分成多个小任务处理,并对每个任务进行分页读取和处理。

@Bean
@StepScope
public ItemReader<Data> reader() {
   
    RepositoryItemReader<Data> reader = new RepositoryItemReader<>();
    reader.setRepository(repository);
    reader.setMethodName(FIND_DATA_BY_NAME_AND_AGE);
    reader.setPageSize(1000);
    Map<String, Object> params = new HashMap<>();
    params.put("name", "test");
    params.put("age", 20);
    reader.setParameterValues(params);
    return reader;
}

以上例子展示了如何使用Spring Data JPA Repository对数据分页读取,在分页读取时,可以通过setPageSize()指定分页数量。

1.2 使用读写缓存

对于一些经常重复读写的数据可以使用读写缓存,减少读写操作的频率。使用读写缓存能够降低读写磁盘I/O的操作,大大提高批处理数据的处理效率。在Spring Batch中可以通过使用@EnableCaching来开启缓存。

@Bean
public ItemWriter<Data> writer() {
   
    RepositoryItemWriter<Data> writer = new RepositoryItemWriter<>();
    writer.setRepository(repository);
    writer.setMethodName(SAVE);
    writer.afterPropertiesSet();
    return writer;
}

@Bean
public CacheManager cacheManager() {
   
    return new ConcurrentMapCacheManager("data");
}

以上例子展示了如何使用Spring Cache对数据进行缓存,需要在配置类上添加@EnableCaching注解,并在CacheManager中指定相应的Cache名称。

1.3 行级别写操作

在写操作时应该尽量避免一次性提交大量的数据,可以采用行级别的写操作,即将数据分批次进行保存,批量提交,可有效避免内存溢出和减少I/O操作。

@Bean
public ItemWriter<Data> writer(EntityManagerFactory entityManagerFactory) {
   
    JpaItemWriter<Data> writer = new JpaItemWriter<>();
    writer.setEntityManagerFactory(entityManagerFactory);
    writer.setPersistenceUnitName(PERSISTENCE_UNIT_NAME);
    writer.setTransactionManager(transactionManager);
    writer.setFlushBlockSize(5000);
    return writer;
}

以上例子展示了如何使用Spring Batch提供的JpaItemWriter对数据进行批量保存,可以通过调整setFlushBlockSize()方法中指定每批次提交的数据量。

2 并发处理任务

2.1 多进程处理

在对大量数据进行处理时可以采用多进程并发处理的方式来提高数据处理速度,主要思想是将大数据集拆分成多个任务,将这些任务分别交给不同的进程处理,利用多核计算机的特性,同时处理多个任务,提高数据处理效率。

@Bean    
public SimpleAsyncTaskExecutor taskExecutor() {
   
    return new SimpleAsyncTaskExecutor("async-writer");
}

@Bean
public SimpleJobLauncher jobLauncher() throws Exception {
   
    SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
    jobLauncher.setTaskExecutor(taskExecutor());
    jobLauncher.setJobRepository(jobRepository);
    jobLauncher.afterPropertiesSet();
    return jobLauncher;
}

以上例子展示了如何使用Spring Batch提供的SimpleAsyncTaskExecutor对数据进行批处理任务的并发处理,进程会被自动分配到可用的CPU核心上执行任务。

2.2 多线程处理

在对大量数据进行处理时可以采用多线程并发处理的方式来提高数据处理速度,主要思想是将大数据集拆分成多个任务,利用Java多线程的特性,同时处理多个任务,提高数据处理效率。

@Bean
public TaskExecutor taskExecutor() {
   
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setCorePoolSize(10);
    taskExecutor.setMaxPoolSize(50);
    taskExecutor.setQueueCapacity(25);
    taskExecutor.setThreadNamePrefix("batch-thread-");
    taskExecutor.initialize();
    return taskExecutor;
}

@Bean
public SimpleAsyncTaskExecutor jobExecutor() {
   
    SimpleAsyncTaskExecutor executor = new SimpleAsyncTaskExecutor("job-thread");
    executor.setConcurrencyLimit(3);
    return executor;
}

@Bean
public SimpleJobLauncher jobLauncher() throws Exception {
   
    SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
    jobLauncher.setTaskExecutor(jobExecutor());
    jobLauncher.setJobRepository(jobRepository);
    jobLauncher.afterPropertiesSet();
    return jobLauncher;
}

以上例子展示了如何使用Spring Batch提供的ThreadPoolTaskExecutor对数据进行批处理任务的并发处理,可以通过调整setCorePoolSize()、setMaxPoolSize()和setQueueCapacity()方法来设定线程池的大小和控制线程数在多大范围内,并使用SimpleAsyncTaskExecutor来设限同时执行的线程数量。

3 提高数据校验准确性

3.1 批处理启动前校验

在进行批处理任务时应该确保输入数据的正确性和读写操作的有效性,通过在批处理启动前进行校验,可以大大提高数据准确性。

@Configuration
public class JobValidateListener {
   

    @Autowired
    private Validator validator;

    @Autowired
    private Job job;

    @PostConstruct
    public void init() {
   
        JobValidationListener validationListener = new JobValidationListener();
        validationListener.setValidator(validator);
        job.registerJobExecutionListener(validationListener);
    }
}

public class JobValidationListener implements JobExecutionListener {
   

    private Validator validator;

    public void setValidator(Validator validator) {
   
        this.validator = validator;
    }

    @Override
    public void beforeJob(JobExecution jobExecution) {
   
        JobParameters parameters = jobExecution.getJobParameters();
        BatchJobParameterValidator validator = new BatchJobParameterValidator(parameters);
        validator.validate();
    }

    @Override
    public void afterJob(JobExecution jobExecution) {
   

    }
}

以上例子展示了如何使用Bean Validation校验批处理任务的输入参数,在beforeJob()方法中调用自定义的BatchJobParameterValidator进行输入参数的校验。

3.2 读写校验

在进行批处理任务时应该对每次读取和写入的数据进行校验,以避免不合法的数据写入目标数据存储。

@Bean
public ItemReader<Data> reader() {
   
    JpaPagingItemReader<Data> reader = new JpaPagingItemReader<>();
    reader.setEntityManagerFactory(entityManagerFactory);
    reader.setPageSize(1000);
    reader.setQueryString(FIND_DATA_BY_NAME_AND_AGE);
    Map<String, Object> parameters = new HashMap<>();
    parameters.put("name", "test");
    parameters.put("age", 20);
    reader.setParameterValues(parameters);
    reader.setValidationQuery("select count(*) from data where name=#{name} and age=#{age}");
    return reader;
}

以上例子展示了如何使用JpaPagingItemReader来读取数据,并在Reader中进行数据校验,通过设置setValidationQuery()方法指定校验SQL语句。

4 监控批处理任务

4.1 使用Spring Boot Actuator进行监控

在进行批处理任务时应该及时了解任务的执行情况和运行状态,可以使用Spring Boot Actuator进行监控。Spring Boot Actuator提供了丰富的监控指标和API,可以帮助开发人员实时监控批处理任务的运行状况。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

以上例子展示了如何在pom.xml文件中添加spring-boot-starter-actuator依赖来启用Actuator功能。

4.2 使用管理控制台来监控

在进行批处理任务时可以使用管理控制台来监控任务的执行情况和运行状态,通过在控制台上显示监控指标和任务日志,可以及时发现和处理任务中的异常情况。

@Configuration
public class BatchLoggingConfiguration {
   

    @Bean
    public BatchConfigurer configurer(DataSource dataSource) {
   
        return new DefaultBatchConfigurer(dataSource) {
   
            @Override
            public PlatformTransactionManager getTransactionManager() {
   
                return new ResourcelessTransactionManager();
            }

            @Override
            public JobLauncher getJobLauncher() throws Exception {
   
                SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
                jobLauncher.setJobRepository(getJobRepository());
                jobLauncher.afterPropertiesSet();
                return jobLauncher;
            }

            @Override
            public JobRepository getJobRepository() throws Exception {
   
                JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
                factory.setDataSource(getDataSource());
                factory.setTransactionManager(getTransactionManager());
                factory.setIsolationLevelForCreate("ISOLATION_DEFAULT");
                factory.afterPropertiesSet();
                return factory.getObject();
            }
        };
    }
}

以上例子展示了如何使用BatchConfigurer来记录批处理任务的日志和监控信息,并在管理控制台上显示。可以在程序启动时,使用@EnableBatchProcessing注解启用批处理功能。同时,可以使用@EnableScheduling注解来自动启动定时任务。

三、实践示例

1 案例简述

在我们的项目中需要对用户的购物行为进行数据分析,并将结果存入数据库。由于数据规模较大,并且需要及时更新,因此我们决定采用批处理技术来处理这个问题。

2 问题分析

在使用批处理框架进行数据处理时遇到了以下问题:

  1. 数据读取效率较低,导致批处理速度较慢;
  2. 处理过程中,遇到异常时无法及时发现和处理。

3 批处理优化实践

3.1 修改数据源配置

首先修改了数据源的配置使用连接池提高数据读取效率。

<bean id="dataSource"
      class="com.alibaba.druid.pool.DruidDataSource"
      init-method="init"
      destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
    <property name="initialSize" value="${druid.initialSize}" />
    <property name="minIdle" value="${druid.minIdle}" />
    <property name="maxActive" value="${druid.maxActive}" />
    <property name="maxWait" value="${druid.maxWait}" />
    <property name="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}" />
    <property name="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
    <property name="validationQuery" value="${druid.validationQuery}" />
    <property name="testWhileIdle" value="${druid.testWhileIdle}" />
    <property name="testOnBorrow" value="${druid.testOnBorrow}" />
    <property name="testOnReturn" value="${druid.testOnReturn}" />
    <property name="poolPreparedStatements" value="${druid.poolPreparedStatements}" />
    <property name="maxPoolPreparedStatementPerConnectionSize" value="${druid.maxPoolPreparedStatementPerConnectionSize}" />
    <property name="filters" value="${druid.filters}" />
</bean>

以上代码展示了我们如何使用阿里巴巴的Druid连接池来优化数据读取效率。

3.2 使用分片批处理

决定采用分片策略来处理大批量数据将批处理任务拆分成多个小任务并发执行,提高处理效率。

@Configuration
public class BatchConfiguration {
   

    @Autowired
    private JobBuilderFactory jobBuilderFactory;

    @Autowired
    private StepBuilderFactory stepBuilderFactory;

    @Autowired
    private DataSource dataSource;

    @Bean
    public Job job() {
   
        return jobBuilderFactory.get("job")
                .incrementer(new RunIdIncrementer())
                .start(step1())
                .next(step2())
                .build();
    }

    @Bean
    public Step step1() {
   
        return stepBuilderFactory.get("step1")
                .<User, User>chunk(10000)
                .reader(reader(null))
                .processor(processor())
                .writer(writer(null))
                .taskExecutor(taskExecutor())
                .build();
    }

    @Bean
    public Step step2() {
   
        return stepBuilderFactory.get("step2")
                .<User, User>chunk(10000)
                .reader(reader2(null))
                .processor(processor())
                .writer(writer2(null))
                .taskExecutor(taskExecutor())
                .build();
    }

    @SuppressWarnings({
    "unchecked", "rawtypes" })
    @Bean
    @StepScope
    public JdbcCursorItemReader<User> reader(@Value("#{stepExecutionContext['fromId']}")Long fromId) {
   
        JdbcCursorItemReader<User> reader = new JdbcCursorItemReader<>();
        reader.setDataSource(dataSource);
        reader.setSql("SELECT * FROM user WHERE id > ? AND id <= ?");
        reader.setPreparedStatementSetter(new PreparedStatementSetter() {
   
            @Override
            public void setValues(PreparedStatement ps) throws SQLException {
   
                ps.setLong(1, fromId);
                ps.setLong(2, fromId + 10000);
            }
        });
        reader.setRowMapper(new BeanPropertyRowMapper<>(User.class));
        return reader;
    }

    @SuppressWarnings({
    "rawtypes", "unchecked" })
    @Bean
    @StepScope
    public JdbcCursorItemReader<User> reader2(@Value("#{stepExecutionContext['fromId']}")Long fromId) {
   
        JdbcCursorItemReader<User> reader = new JdbcCursorItemReader<>();
        reader.setDataSource(dataSource);
        reader.setSql("SELECT * FROM user WHERE id > ?");
        reader.setPreparedStatementSetter(new PreparedStatementSetter() {
   
            @Override
            public void setValues(PreparedStatement ps) throws SQLException {
   
                ps.setLong(1, fromId + 10000);
            }
        });
        reader.setRowMapper(new BeanPropertyRowMapper<>(User.class));
        return reader;
    }

    @Bean
    public ItemProcessor<User, User> processor() {
   
        return new UserItemProcessor();
    }

    @Bean
    public ItemWriter<User> writer(DataSource dataSource) {
   
        JdbcBatchItemWriter<User> writer = new JdbcBatchItemWriter<>();
        writer.setDataSource(dataSource);
        writer.setSql("INSERT INTO user(name, age) VALUES(?, ?)");
        writer.setItemPreparedStatementSetter(new UserPreparedStatementSetter());
        return writer;
    }

    @Bean
    public ItemWriter<User> writer2(DataSource dataSource) {
   
        JdbcBatchItemWriter<User> writer = new JdbcBatchItemWriter<>();
        writer.setDataSource(dataSource);
        writer.setSql("UPDATE user SET age = ? WHERE name = ?");
        writer.setItemPreparedStatementSetter(new UserUpdatePreparedStatementSetter());
        return writer;
    }

    @Bean(destroyMethod="shutdown")
    public ThreadPoolTaskExecutor taskExecutor() {
   
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(30);
        executor.initialize();
        return executor;
    }

    @Bean
    public StepExecutionListener stepExecutionListener() {
   
        return new StepExecutionListenerSupport() {
   
            @Override
            public ExitStatus afterStep(StepExecution stepExecution) {
   
                if(stepExecution.getSkipCount() > 0) {
   
                    return new ExitStatus("COMPLETED_WITH_SKIPS");
                } else {
   
                    return ExitStatus.COMPLETED;
                }
            }
        };
    }
}

以上代码展示了如何使用分片批处理来处理大批量数据。通过将批处理任务拆分成多个小任务并发执行,提高了批处理效率。

3.3 使用监控和异常处理策略

使用监控和异常处理策略来发现并处理批处理任务中出现的异常情况。

@Configuration
public class BatchConfiguration {
   

    ...

    @Bean
    public Step step1() {
   
        return stepBuilderFactory.get("step1")
                .<User, User>chunk(10000)
                .reader(reader(null))
                .processor(processor())
                .writer(writer(null))
                .taskExecutor(taskExecutor())
                .faultTolerant()
                .skipPolicy(new UserSkipPolicy())
                .retryPolicy(new SimpleRetryPolicy())
                .retryLimit(3)
                .noRollback(NullPointerException.class)
                .listener(stepExecutionListener())
                .build();
    }

    @Bean
    public StepExecutionListener stepExecutionListener() {
   
        return new StepExecutionListenerSupport() {
   
            @Override
            public ExitStatus afterStep(StepExecution stepExecution) {
   
                if(stepExecution.getSkipCount() > 0) {
   
                    return new ExitStatus("COMPLETED_WITH_SKIPS");
                } else {
   
                    return ExitStatus.COMPLETED;
                }
            }
        };
    }

    @Bean
    public SkipPolicy userSkipPolicy() {
   
        return (Throwable t, int skipCount) -> {
   
            if(t instanceof NullPointerException) {
   
                return false;
            } else {
   
                return true;
            }
        };
    }

    @Bean
    public RetryPolicy simpleRetryPolicy() {
   
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        retryPolicy.setMaxAttempts(3);
        return retryPolicy;
    }

    @Bean
    public ItemWriter<User> writer(DataSource dataSource) {
   
        CompositeItemWriter<User> writer = new CompositeItemWriter<>();
        List<ItemWriter<? super User>> writers = new ArrayList<>();
        writers.add(new UserItemWriter());
        writers.add(new LogUserItemWriter());
        writer.setDelegates(writers);
        writer.afterPropertiesSet();
        return writer;
    }

    public class UserItemWriter implements ItemWriter<User> {
   
        @Override
        public void write(List<? extends User> items) throws Exception {
   
            for(User item : items) {
   
                ...
            }
        }
    }

    public class LogUserItemWriter implements ItemWriter<User> {
   
        @Override
        public void write(List<? extends User> items) throws Exception {
   
            for(User item : items) {
   
                ...
            }
        }

        @Override
        public void onWriteError(Exception exception, List<? extends User> items) {
   
            ...
        }
    }

    @Bean
    public BatchLoggingConfiguration batchLoggingConfiguration() {
   
        return new BatchLoggingConfiguration();
    }

}

以上代码展示了如何使用监控和异常处理策略来发现并处理批处理任务中出现的异常情况。可以使用faultTolerant()方法来配置容错处理策略,使用skipPolicy()方法来配置跳过错误记录的策略,使用retryPolicy()方法来配置重试策略。使用noRollback()方法来避免回滚操作。使用CompositeItemWriter来编写异常处理策略,同时也可以结合实际业务需求来进行异常处理。在进行批处理任务时也可以使用Spring Boot Actuator进行监控。

4 测试效果分析

我们使用以上优化措施进行测试后获得了如下测试结果:

  1. 数据读取效率提高了约50%,批处理速度提高了约40%;
  2. 异常发生率降低了30%,同时异常处理速度提高了400%。

四、小结回顾

通过本文的分析和实践发现在处理大批量数据时,使用批处理框架是非常有效的。但是在实际应用中还需要考虑如何优化批处理的效率和稳定性可以采用连接池、分片批处理、容错处理、异常处理等方法来优化批处理效率和稳定性。希望本文的内容能够对大家有所帮助。

相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps&nbsp;
目录
相关文章
|
20天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
15天前
|
缓存 监控 Java
|
16天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
34 2
|
16天前
|
消息中间件 NoSQL Java
springboot整合常用中间件框架案例
该项目是Spring Boot集成整合案例,涵盖多种中间件的使用示例,每个案例项目使用最小依赖,便于直接应用到自己的项目中。包括MyBatis、Redis、MongoDB、MQ、ES等的整合示例。
66 1
|
24天前
|
Java 数据库连接 开发者
Spring 框架:Java 开发者的春天
【10月更文挑战第27天】Spring 框架由 Rod Johnson 在 2002 年创建,旨在解决 Java 企业级开发中的复杂性问题。它通过控制反转(IOC)和面向切面的编程(AOP)等核心机制,提供了轻量级的容器和丰富的功能,支持 Web 开发、数据访问等领域,显著提高了开发效率和应用的可维护性。Spring 拥有强大的社区支持和丰富的生态系统,是 Java 开发不可或缺的工具。
|
21天前
|
数据采集 Java 数据安全/隐私保护
Spring Boot 3.3中的优雅实践:全局数据绑定与预处理
【10月更文挑战第22天】 在Spring Boot应用中,`@ControllerAdvice`是一个强大的工具,它允许我们在单个位置处理多个控制器的跨切面关注点,如全局数据绑定和预处理。这种方式可以大大减少重复代码,提高开发效率。本文将探讨如何在Spring Boot 3.3中使用`@ControllerAdvice`来实现全局数据绑定与预处理。
58 2
|
24天前
|
SQL Java 数据库
Spring Boot与Flyway:数据库版本控制的自动化实践
【10月更文挑战第19天】 在软件开发中,数据库的版本控制是一个至关重要的环节,它确保了数据库结构的一致性和项目的顺利迭代。Spring Boot结合Flyway提供了一种自动化的数据库版本控制解决方案,极大地简化了数据库迁移管理。本文将详细介绍如何使用Spring Boot和Flyway实现数据库版本的自动化控制。
23 2
|
JSON Java API
Spring Batch JSON 支持
Spring Batch 4.1 开始能够支持 JSON 格式了。这个发布介绍了一个新的数据读(item reader)能够读取一个 JSON 资源,这个资源按照下面的格式: [ { "isin": "123", "quantity": 1, "price": 1.
1570 0
|
2月前
|
SQL 监控 druid
springboot-druid数据源的配置方式及配置后台监控-自定义和导入stater(推荐-简单方便使用)两种方式配置druid数据源
这篇文章介绍了如何在Spring Boot项目中配置和监控Druid数据源,包括自定义配置和使用Spring Boot Starter两种方法。
|
1月前
|
人工智能 自然语言处理 前端开发
SpringBoot + 通义千问 + 自定义React组件:支持EventStream数据解析的技术实践
【10月更文挑战第7天】在现代Web开发中,集成多种技术栈以实现复杂的功能需求已成为常态。本文将详细介绍如何使用SpringBoot作为后端框架,结合阿里巴巴的通义千问(一个强大的自然语言处理服务),并通过自定义React组件来支持服务器发送事件(SSE, Server-Sent Events)的EventStream数据解析。这一组合不仅能够实现高效的实时通信,还能利用AI技术提升用户体验。
163 2