비즈니스 로직의 원자성을 보장해야 하는 대표적인 사례인 '계좌 이체' 기능을 통해 리포지토리, 서비스, 컨트롤러의 구현 방법을 정리했습니다.
리포지토리는 데이터베이스에 직접 접근하여 계좌 정보를 조회하거나 상태를 업데이트합니다.
JdbcTemplate.queryForObject()를 사용하여 특정 계좌의 상세 정보를 가져옵니다.Account)로 변환하기 위해 RowMapper 인터페이스를 구현해야 합니다.ResultSet)의 데이터를 모델 객체의 필드에 매핑하는 역할을 합니다.@Repository
public class AccountRepository {
private final JdbcTemplate jdbc;
public AccountRepository(JdbcTemplate jdbc) {
this.jdbc = jdbc;
}
//계좌 ID를 사용하여 계좌 상세 정보를 찾는다.
public Account findAccountById(long id){ //1)
String sql = "SELECT * FROM account WHERE id = ?";
return jdbc.queryForObject(sql,new AccountRowMapper(), id);
}
//특정 계좌의 금액을 업데이트 한다.
public void changeAmount(long id, BigDecimal amount){
String sql = "UPDATE account SET amount = ? WHERE id = ?";
jdbc.update(sql, amount, id);
}
//모든 계좌 상세 정보 가져오기.
public List<Account> findAllAcounts(){
String sql = "SELECT * FROM account";
return jdbc.query(sql,new AccountRowMapper());
}
}
public class AccountRowMapper implements RowMapper<Account> {
//RowMapper 계약을 구현하고 결과 행을 매핑할 모델 클래스를 제네릭 프로그래밍타입으로 제공
@Override
public Account mapRow(ResultSet resultSet, int i)
//쿼리 결과를 매개변수(ResultSet 객체 형태)로 가져와 현재 행을 매핑한 Account 인스턴스를
//반환하는 mapRow()메서드 구현
throws SQLException{
Account a = new Account(); //현재 결과 행의 값을 Account 속성에 매핑
a.setId(resultSet.getInt("id"));
a.setName(resultSet.getString("name"));
a.setAmount(resultSet.getBigDecimal("amount"));
return a; //결과 값을 매핑한 후 Account 인스턴스 반환
}
}
서비스 계층은 비즈니스 로직을 수행하며, @Transactional을 통해 데이터의 무결성을 보장합니다.
@Service
public class TransferService {
private final AccountRepository accountRepository;
public TransferService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Transactional
public void transferMoney(long idSender, long idReceiver, BigDecimal amount){
Account sender = accountRepository.findAccountById(idSender);
Account receiver = accountRepository.findAccountById(idReceiver);
BigDecimal senderNewAmount = sender.getAmount().subtract(amount);
BigDecimal receiverNewAmount = receiver.getAmount().add(amount);
accountRepository.changeAmount(idSender, senderNewAmount);
accountRepository.changeAmount(idReceiver, receiverNewAmount);
throw new RuntimeException("Oh no! Something went wrong!"); //트랜잭션 시뮬레이션
}
public List<Account> getAllAccounts(){
return accountRepository.findAllAcounts();
}
}
클라이언트의 요청을 받고, 서비스 계층으로 데이터를 전달하는 입구 역할을 합니다.
TransferRequest가 DTO 역할을 수행합니다.@RequestBody를 통해 HTTP 요청 본문의 데이터를 DTO로 변환하여 수신합니다.transferMoney() 메서드를 호출합니다.@RestController
public class AccountController {
private final TransferService transferService;
public AccountController(TransferService transferService) {
this.transferService = transferService;
}
@PostMapping("/transfer")
public void transferMoney(
@RequestBody TransferRequest request
){
transferService.transferMoney(
request.getSenderAccountId(),
request.getReceiverAccountId(),
request.getAmount());
}
@GetMapping("/accounts")
public List<Account> getAllAccounts(){
return transferService.getAllAccounts();
}
}
//DTO
public class TransferRequest {
private long senderAccountId;
private long receiverAccountId;
private BigDecimal amount;
public BigDecimal getAmount() {
return amount;
}
public long getReceiverAccountId() {
return receiverAccountId;
}
public long getSenderAccountId() {
return senderAccountId;
}
}
RowMapper를 사용해 DB 데이터와 객체를 매핑하고 쿼리를 실행한다.@Transactional로 여러 단계의 DB 변경 작업을 하나의 단위로 묶어 안전하게 처리한다.#Spring #Transaction #JdbcTemplate #RowMapper #DTO #계좌이체구현 #백엔드공부