You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Batch定时获取产品库存并返回List<Map<Date,Long>>的实现问询

Hey there! Let's walk through how to get your Spring Batch setup working to track hourly stock levels and expose the history for your chart. I'll break this down step by step, starting with the basics you need to persist the history, then building out the batch components, and finally the endpoint to retrieve the data.

First, we need a way to store the hourly stock snapshots. Let's create an entity and repository for that:

1. Stock History Entity & Repository

This will keep track of each timestamped stock value for your product and store:

import jakarta.persistence.*;
import java.time.LocalDateTime;

@Entity
public class StockHistory {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String productRef;
    private Long storeId;
    private LocalDateTime timestamp;
    private Long stock;

    // Default constructor for JPA
    public StockHistory() {}

    // Getters and Setters
    public String getProductRef() { return productRef; }
    public void setProductRef(String productRef) { this.productRef = productRef; }
    public Long getStoreId() { return storeId; }
    public void setStoreId(Long storeId) { this.storeId = storeId; }
    public LocalDateTime getTimestamp() { return timestamp; }
    public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
    public Long getStock() { return stock; }
    public void setStock(Long stock) { this.stock = stock; }
}

Then a repository to interact with the database:

import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;

public interface StockHistoryRepository extends JpaRepository<StockHistory, Long> {
    List<StockHistory> findByProductRefAndStoreIdOrderByTimestampAsc(String productRef, Long storeId);
}

2. Custom ItemReader

This reader will fetch the current stock using your existing service, using job parameters to pass in the product ref and store ID (so you can reuse this job for different products if needed):

import org.springframework.batch.item.ItemReader;
import org.springframework.batch.core.JobParameters;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class StockItemReader implements ItemReader<Long> {

    private final ProduitService produitService;
    private final JobParameters jobParameters;

    // Inject job parameters and your service
    public StockItemReader(ProduitService produitService, 
                           @Value("#{jobExecution.jobParameters}") JobParameters jobParameters) {
        this.produitService = produitService;
        this.jobParameters = jobParameters;
    }

    @Override
    public Long read() throws Exception {
        // Extract ref and store ID from job parameters
        String ref = jobParameters.getString("productRef");
        Long storeId = jobParameters.getLong("storeId");
        
        if (ref == null || storeId == null) {
            throw new IllegalArgumentException("Job parameters 'productRef' and 'storeId' are required!");
        }
        
        // Call your existing service to get current stock
        return produitService.getStockByRef(ref, storeId);
    }
}

3. ItemProcessor

This takes the raw stock value and wraps it into a StockHistory object with the current timestamp:

import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.core.JobParameters;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;

@Component
public class StockItemProcessor implements ItemProcessor<Long, StockHistory> {

    private final JobParameters jobParameters;

    public StockItemProcessor(@Value("#{jobExecution.jobParameters}") JobParameters jobParameters) {
        this.jobParameters = jobParameters;
    }

    @Override
    public StockHistory process(Long stock) throws Exception {
        StockHistory historyEntry = new StockHistory();
        historyEntry.setProductRef(jobParameters.getString("productRef"));
        historyEntry.setStoreId(jobParameters.getLong("storeId"));
        historyEntry.setTimestamp(LocalDateTime.now());
        historyEntry.setStock(stock);
        return historyEntry;
    }
}

4. ItemWriter

This saves the stock history entry to the database using our repository:

import org.springframework.batch.item.ItemWriter;
import org.springframework.stereotype.Component;
import java.util.List;

@Component
public class StockItemWriter implements ItemWriter<StockHistory> {

    private final StockHistoryRepository stockHistoryRepository;

    public StockItemWriter(StockHistoryRepository stockHistoryRepository) {
        this.stockHistoryRepository = stockHistoryRepository;
    }

    @Override
    public void write(List<? extends StockHistory> items) throws Exception {
        stockHistoryRepository.saveAll(items);
    }
}

5. Update Batch Configuration

Let's adjust your batch config to use these new components:

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@EnableBatchProcessing
public class SpringBatchConfig {

    private final StockItemReader stockItemReader;
    private final StockItemProcessor stockItemProcessor;
    private final StockItemWriter stockItemWriter;
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    // Constructor injection (better than @Autowired for clarity)
    public SpringBatchConfig(StockItemReader stockItemReader,
                             StockItemProcessor stockItemProcessor,
                             StockItemWriter stockItemWriter,
                             JobBuilderFactory jobBuilderFactory,
                             StepBuilderFactory stepBuilderFactory) {
        this.stockItemReader = stockItemReader;
        this.stockItemProcessor = stockItemProcessor;
        this.stockItemWriter = stockItemWriter;
        this.jobBuilderFactory = jobBuilderFactory;
        this.stepBuilderFactory = stepBuilderFactory;
    }

    @Bean
    public Job stockTrackingJob() {
        Step captureStockStep = stepBuilderFactory.get("capture-stock-step")
                .<Long, StockHistory>chunk(1)
                .reader(stockItemReader)
                .processor(stockItemProcessor)
                .writer(stockItemWriter)
                .build();

        return jobBuilderFactory.get("stock-tracking-job")
                .start(captureStockStep)
                .build();
    }
}

6. Schedule the Hourly Job

Add a scheduler to run the batch job every hour. Don't forget to add @EnableScheduling to your main Spring Boot application class!

import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class StockJobScheduler {

    private final JobLauncher jobLauncher;
    private final Job stockTrackingJob;

    public StockJobScheduler(JobLauncher jobLauncher, Job stockTrackingJob) {
        this.jobLauncher = jobLauncher;
        this.stockTrackingJob = stockTrackingJob;
    }

    // Run every hour at minute 0 (adjust cron expression if needed)
    @Scheduled(cron = "0 0 * * * ?")
    public void runHourlyStockJob() throws Exception {
        // Replace these with your actual product ref and store ID, or inject from config
        String productRef = "YOUR_PRODUCT_REF";
        Long storeId = 1L;

        // Add a unique parameter (current time) to ensure each job execution is distinct
        JobParameters jobParams = new JobParametersBuilder()
                .addString("productRef", productRef)
                .addLong("storeId", storeId)
                .addLong("executionTime", System.currentTimeMillis())
                .toJobParameters();

        jobLauncher.run(stockTrackingJob, jobParams);
    }
}

7. REST Endpoint to Return Stock History

Update your controller to fetch the stored history and convert it to the List<Map<Date, Long>> format your chart needs:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.time.ZoneId;

@RestController
public class StockController {

    private final ProduitService produitService;
    private final StockHistoryRepository stockHistoryRepository;

    public StockController(ProduitService produitService, StockHistoryRepository stockHistoryRepository) {
        this.produitService = produitService;
        this.stockHistoryRepository = stockHistoryRepository;
    }

    // Keep your existing endpoint for current stock
    @GetMapping("/products/{id}/stock/{ref}")
    public Long getStockByRef(@PathVariable Long id, @PathVariable String ref) {
        return produitService.getStockByRef(ref, id);
    }

    // New endpoint for stock history (for your chart)
    @GetMapping("/products/{id}/stock/{ref}/history")
    public List<Map<Date, Long>> getStockHistory(@PathVariable Long id, @PathVariable String ref) {
        List<StockHistory> historyEntries = stockHistoryRepository.findByProductRefAndStoreIdOrderByTimestampAsc(ref, id);
        
        // Convert each entry to the required Map format
        return historyEntries.stream()
                .map(entry -> {
                    Map<Date, Long> stockMap = new HashMap<>();
                    // Convert LocalDateTime to Date for the chart
                    Date timestamp = Date.from(entry.getTimestamp().atZone(ZoneId.systemDefault()).toInstant());
                    stockMap.put(timestamp, entry.getStock());
                    return stockMap;
                })
                .collect(Collectors.toList());
    }
}

Quick Optimization for Your Service

Your current getStockByRef method loops through all products in the store, which can be slow if you have many products. Let's optimize it by adding a direct query to your product repository:

First, add this method to your ProduitRepository:

Optional<Produit> findByReferenceAndStoreId(String reference, Long storeId);

Then update your service method:

@Override
public Long getStockByRef(String ref, Long id) {
    return produitRepository.findByReferenceAndStoreId(ref, id)
            .map(Produit::getStock)
            .orElse(0L);
}

That's it! Now every hour, the batch job will capture the current stock and save it to the database. When you call /products/{id}/stock/{ref}/history, you'll get a list of timestamp-stock maps perfect for your Angular Google Chart.

内容的提问来源于stack exchange,提问作者JavaDev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 00:07:44