사용자가 자신의 지출 내역을 등록하고 → 읽고 → 수정하고 → 삭제할 수 있도록 만드는 것(CRUD)
package doHoaSen.FinTrack.expense.entity;
import doHoaSen.FinTrack.user.entity.User;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.springframework.cglib.core.Local;
import java.time.LocalDateTime;
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Expense {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
@ManyToOne(fetch = FetchType.LAZY)
private User user;
private Long amount;
private String category;
private String memo;
private LocalDateTime dateTime;
@CreationTimestamp
private LocalDateTime createdAt;
@UpdateTimestamp
private LocalDateTime updatedAt;
}
ExpenseCreateRequest, ExpenseUpdateRequest와 ExpenseResponse를 작성했다.
지출금액, 카테고리, 메모와 일시를 필요로 한다.
ExpenseCreateRequestpublic record ExpenseCreateRequest(
Integer amount,
String category,
String memo,
LocalDateTime dateTime
) {}
ExpenseUpdateRequestpublic record ExpenseUpdateRequest(
Integer amount,
String category,
String memo,
LocalDateTime dateTime
) {}
ExpenseResponsepublic record ExpenseResponse(
Long id,
Integer amount,
String category,
String memo,
LocalDateTime dateTime
) {}
@Service
@RequiredArgsConstructor
public class ExpenseService {
private final ExpenseRepository expenseRepository;
private final UserRepository userRepository;
public Long createExpense(Long userId, ExpenseCreateRequest req) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new NotFoundException("User not found"));
Expense expense = Expense.builder()
.user(user)
.amount(req.amount())
.category(req.category())
.memo(req.memo())
.dateTime(req.dateTime())
.build();
expenseRepository.save(expense);
return expense.getId();
}
public List<ExpenseResponse> getMonthlyExpenses(Long userId, int year, int month) {
LocalDateTime start = LocalDateTime.of(year, month, 1, 0, 0);
LocalDateTime end = start.plusMonths(1);
return expenseRepository.findByUserIdAndDateTimeBetween(userId, start, end)
.stream()
.map(ExpenseMapper::toResponse)
.toList();
}
public void updateExpense(Long userId, Long expenseId, ExpenseUpdateRequest req) {
Expense exp = getUserExpense(userId, expenseId);
if (req.amount() != null) exp.setAmount(req.amount());
if (req.category() != null) exp.setCategory(req.category());
if (req.memo() != null) exp.setMemo(req.memo());
if (req.dateTime() != null) exp.setDateTime(req.dateTime());
}
public void deleteExpense(Long userId, Long expenseId) {
Expense exp = getUserExpense(userId, expenseId);
expenseRepository.delete(exp);
}
private Expense getUserExpense(Long userId, Long expenseId) {
Expense expense = expenseRepository.findById(expenseId)
.orElseThrow(() -> new NotFoundException("Expense not found"));
if (!expense.getUser().getId().equals(userId))
throw new ForbiddenException("본인의 지출만 수정 가능합니다.");
return expense;
}
}
getMonthlyExpenses()특정 유저의 특정 월에 해당하는 지출 목록을 가져오는 기능을 한다.
추후 월별 소비 통계 / 카테고리별 통계 / 시간대별 분석에 필요한 기능으로 구현해두었다.
즉, 지출 데이터를 분석하기 위한 가장 기본이 되는 조회 메서드이다.
이를 위해 ExpenseRepository에
List<Expense> findByUserIdAndDateTimeBetween(
Long userId,
LocalDateTime start,
LocalDateTime end
);
findByUserIdAndDateTimeBetween를 작성했다.
그리고 Expense 엔티티 → ExpenseResponse DTO로 변환하기 위해
public class ExpenseMapper {
public static ExpenseResponse toResponse(Expense expense){
return new ExpenseResponse(
expense.getId(),
expense.getAmount(),
expense.getCategory(),
expense.getMemo(),
expense.getDateTime()
);
}
ExpenseMapper를 위와 같이 작성했다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/expenses")
public class ExpenseController {
private final ExpenseService expenseService;
/** 지출 등록 */
@PostMapping
public ResponseEntity<ApiResponse<Long>> createExpense(
@AuthenticationPrincipal User user,
@RequestBody ExpenseCreateRequest req) {
Long id = expenseService.createExpense(user.getId(), req);
return ResponseEntity.ok(
ApiResponse.success("지출이 등록되었습니다.", id)
);
}
/*월별 지출 조회*/
@GetMapping
public ResponseEntity<ApiResponse<List<ExpenseResponse>>> getMonthlyExpenses(
@AuthenticationPrincipal User user,
@RequestParam int year,
@RequestParam int month ) {
List<ExpenseResponse> expenses = expenseService.getMonthlyExpensess(user.getId(), year, month);
return ResponseEntity.ok(
ApiResponse.success("해당 월 목록 조회 성공", expenses)
);
}
// 월별 지출 목록
public List<ExpenseResponse> getMonthlyExpenses(Long userId, int year, int month){
LocalDateTime start = LocalDateTime.of(year, month, 1, 0, 0);
LocalDateTime end = start.plusMonths(1);
return expenseRepository
.findByUserIdAndDateTimeBetween(userId, start, end)
.stream()
.map(ExpenseMapper::toResponse)
.toList();
}
/** 지출 수정 */
@PatchMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> updateExpense(
@AuthenticationPrincipal User user,
@PathVariable Long id,
@RequestBody ExpenseUpdateRequest req) {
expenseService.updateExpense(user.getId(), id, req);
return ResponseEntity.ok(
ApiResponse.success("지출 수정 완료", null)
);
}
/** 지출 삭제 */
@DeleteMapping("/{id}")
public ResponseEntity<ApiResponse<Void>> deleteExpense(
@AuthenticationPrincipal User user,
@PathVariable Long id) {
expenseService.deleteExpense(user.getId(), id);
return ResponseEntity.ok(
ApiResponse.success("지출 삭제 완료", null)
);
}
}