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

如何从列表内的实体提取Double值并计算每日交易剩余金额

Spring Boot 按日期计算每日交易收支剩余金额的实现方案

一、先确认核心实体结构

首先确保你的Transaction实体包含必要字段,比如交易类型、金额、日期,以及关联的Wallet:

// 交易类型枚举
public enum TransactionType {
    INCOME, EXPENSE
}

// Transaction 实体
@Entity
public class Transaction {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    // 金额:收入为正,支出为正(后续通过类型区分)
    private BigDecimal amount;
    @Enumerated(EnumType.STRING)
    private TransactionType type;
    // 交易日期(仅日期,不含时间,确保分组准确)
    private LocalDate transactionDate;
    @ManyToOne
    private Wallet wallet;

    // Getter、Setter、构造方法省略
}

// 你的 TransactionGroup 类新增每日结余字段
public class TransactionGroup {
    private LocalDate date;
    private List<Transaction> transactions;
    private BigDecimal dailyBalance;

    // Getter、Setter、构造方法省略
}

二、实现方案选择

方案1:Java Stream 分组计算(适合中小数据量)

将计算逻辑放在Service层(控制器应只负责请求转发,避免业务逻辑耦合),通过Stream对交易按日期分组,再计算每组的收支结余:

@Service
public class TransactionService {
    @Autowired
    private TransactionRepository transactionRepo;

    public List<TransactionGroup> getUserDailyTransactionGroups(Long userId) {
        // 获取该用户所有交易,按日期升序排列
        List<Transaction> userTransactions = transactionRepo.findByWallet_UserIdOrderByTransactionDateAsc(userId);

        // 按日期分组并计算每日结余
        return userTransactions.stream()
                .collect(Collectors.groupingBy(Transaction::getTransactionDate))
                .entrySet().stream()
                .map(entry -> {
                    LocalDate date = entry.getKey();
                    List<Transaction> dailyTransactions = entry.getValue();

                    // 计算当日总收入
                    BigDecimal totalIncome = dailyTransactions.stream()
                            .filter(t -> TransactionType.INCOME.equals(t.getType()))
                            .map(Transaction::getAmount)
                            .reduce(BigDecimal.ZERO, BigDecimal::add);

                    // 计算当日总支出
                    BigDecimal totalExpense = dailyTransactions.stream()
                            .filter(t -> TransactionType.EXPENSE.equals(t.getType()))
                            .map(Transaction::getAmount)
                            .reduce(BigDecimal.ZERO, BigDecimal::add);

                    // 当日结余 = 总收入 - 总支出
                    BigDecimal dailyBalance = totalIncome.subtract(totalExpense);

                    return new TransactionGroup(date, dailyTransactions, dailyBalance);
                })
                .sorted(Comparator.comparing(TransactionGroup::getDate))
                .collect(Collectors.toList());
    }
}

控制器层简化为:

@RestController
@RequestMapping("/transactions")
public class TransactionController {
    @Autowired
    private TransactionService transactionService;

    @GetMapping("/user/{userId}")
    public ResponseEntity<List<TransactionGroup>> getUserTransactions(@PathVariable Long userId) {
        List<TransactionGroup> groups = transactionService.getUserDailyTransactionGroups(userId);
        return ResponseEntity.ok(groups);
    }
}

优化点:如果你的Transaction实体中,支出金额直接存为负数(收入正、支出负),那么计算结余可以更简洁:

BigDecimal dailyBalance = dailyTransactions.stream()
        .map(Transaction::getAmount)
        .reduce(BigDecimal.ZERO, BigDecimal::add);

方案2:JPA 自定义查询(适合大数据量)

如果用户交易数据量较大,直接在数据库层面完成分组计算,减少内存开销:

在TransactionRepository中定义自定义查询:

@Repository
public interface TransactionRepository extends JpaRepository<Transaction, Long> {
    @Query("SELECT t.transactionDate as date, " +
            "SUM(CASE WHEN t.type = 'INCOME' THEN t.amount ELSE 0 END) - SUM(CASE WHEN t.type = 'EXPENSE' THEN t.amount ELSE 0 END) as dailyBalance, " +
            "COLLECT(t) as transactions " +
            "FROM Transaction t " +
            "WHERE t.wallet.userId = :userId " +
            "GROUP BY t.transactionDate " +
            "ORDER BY t.transactionDate ASC")
    List<Object[]> findDailyGroupsWithBalance(@Param("userId") Long userId);
}

然后在Service层将查询结果转换为TransactionGroup:

public List<TransactionGroup> getUserDailyTransactionGroups(Long userId) {
    List<Object[]> queryResults = transactionRepo.findDailyGroupsWithBalance(userId);
    return queryResults.stream()
            .map(result -> {
                LocalDate date = (LocalDate) result[0];
                BigDecimal dailyBalance = (BigDecimal) result[1];
                List<Transaction> transactions = (List<Transaction>) result[2];
                return new TransactionGroup(date, transactions, dailyBalance);
            })
            .collect(Collectors.toList());
}

三、注意事项

  • 确保transactionDate字段是LocalDate类型(而非LocalDateTime),避免因时间部分导致分组错误。
  • 使用BigDecimal处理金额,避免浮点精度问题,计算时可指定精度(如new MathContext(2, RoundingMode.HALF_UP))。
  • 业务逻辑尽量放在Service层,控制器只做请求响应处理,符合分层架构原则。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 21:33:25