2가지 문제를 해결해야 한다.
// 코드 1
try (
Connection conn = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword());
PreparedStatement ps = conn.prepareStatement("""
SELECT id, name, balance
FROM USERS
WHERE id = ?
""")
) {
ps.setLong(1, id);
try(ResultSet rs = ps.executeQuery()) {
boolean hasUser = rs.next();
...
Users users = createUser(rs);
return users;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
// 코드 2
try (Connection conn = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword());
PreparedStatement ps = conn.prepareStatement("""
SELECT id, name, price, stock
FROM foods
WHERE id = ?
""")) {
ps.setLong(1, id);
try(ResultSet rs = ps.executeQuery()) {
boolean hasFood = rs.next();
...
return createFood(rs);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
딱 2개의 repository 코드를 봐도 흐름의 반복이 느껴진다.
인프라 계층에서만 DriverManager, Connection 등등을 다루고
외부로 내부 구현 코드를 전파시키지 않았다.
언제든 내부 구현 기술을 바꿀 수 있는 상태를 유지하려 했다.
하지만 여러 도메인 Repository끼리 하나의 트랜잭션에서 작업을 해야하는 요구사항으로 인해서 내부 구현 기술이 인프라 외부로 전파됐다.
이제 Repository의 세부 구현을 바꾸면 외부까지 바꿔야하는 문제가 생긴것이다.
// 인프라 외부의 코드
public void order(long userId, FoodOrderRequests foodOrderRequests) {
Users user = usersRepository.findById(userId);
List<Foods> foods = foodsRepository.findAll(foodOrderRequests.foodIds());
List<FoodOrders> foodOrders = FoodOrders.from(foodOrderRequests, foods);
// 애플리케이션 구현과 Repository 내부 구현 기술간 의존 관계가 생김
try(
Connection conn = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword())
) {
try {
conn.setAutoCommit(false);
Orders order = orderService.order(user, foodOrders);
orderRepository.save(conn, order);
usersRepository.updateBalance(conn, user.getId(), user.getBalance());
for (Foods food : foods)
foodsRepository.updateStock(conn, food.getId(), food.getStock());
} catch (SQLException | RuntimeException e) {
conn.rollback();
throw e;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
JdbcTemplate -> DataSoruce -> TransactionManager
이 흐름으로 코드를 개선해 나갈 것이다.
한 번에 두 문제를 한 번에 해결할 수 없다.
JdbcTemplate은 JDBC 코드 반복 문제 해결에 집중할 것이다.
PreparedStatement, ResultSet 처리와 객체 생성 등의 코드 반복을 제거할 것이다.
당장 Connection을 없앨 순 없다.
최종적으로 내부 구현 기술 전파 문제를 해결할 것이다.
DataSource는 커넥션을 얻는 방법을 정의할 것이고,
TransactionMaanger는 트랜잭션을 관리할 것이다.
TransactionManager가 트랜잭션을 시작하면 ThreadLocal에 커넥션을 박아 넣을 것이다.
그리고 DataSource는 트랜잭션이 시작중이라면 커넥션을 얻을 때 ThreadLocal에 있는 커넥션을 가져다가 반환해줄 것이다.
DataSource는 인터페이스다.
DataSource는DriverManager로 직접 가져오는 방식도 있고, 커넥션 풀에서 가져오는 방식도 있다.
근데DataSoruce에서 트랜잭션을 어떻게 인식할까?
DataSource의 구현체는 많은데 이 모든 곳에 트랜잭션을 인식하는 코드를 생성하는 것은 비효율적이다.
여기서 Spring의TransactionAwareDataSourceProxy가 나오는 것이다.
DataSource를 구현하는 객체들을 프록시로 감싸는 것이다.getConnection() { if(트랜잭션이 열려있는가) return ThreadLocal에_있는_커넥션_반환; else return DataSource구현체.getConnection(); }이렇게 흐름을 제어해주는 것 같다.