如何从列表内的实体提取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
相关产品推荐
相关产品推荐

