기존 코드 문제점 + 앞으로 계획 (JdbcTemplate, DataSource, TransactionManager)

Jang990·2026년 1월 25일

기존 코드의 문제점

2가지 문제를 해결해야 한다.

  1. JDBC 코드 반복
  2. 내부 구현 기술 전파

JDBC 코드 반복

		// 코드 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

한 번에 두 문제를 한 번에 해결할 수 없다.
JdbcTemplate은 JDBC 코드 반복 문제 해결에 집중할 것이다.

PreparedStatement, ResultSet 처리와 객체 생성 등의 코드 반복을 제거할 것이다.
당장 Connection을 없앨 순 없다.

DataSource + TransactionManager

최종적으로 내부 구현 기술 전파 문제를 해결할 것이다.

DataSource는 커넥션을 얻는 방법을 정의할 것이고,
TransactionMaanger는 트랜잭션을 관리할 것이다.

어떻게 Connection을 숨길 것인가?

TransactionManager가 트랜잭션을 시작하면 ThreadLocal에 커넥션을 박아 넣을 것이다.

그리고 DataSource는 트랜잭션이 시작중이라면 커넥션을 얻을 때 ThreadLocal에 있는 커넥션을 가져다가 반환해줄 것이다.

DataSource는 인터페이스다.

DataSourceDriverManager로 직접 가져오는 방식도 있고, 커넥션 풀에서 가져오는 방식도 있다.


근데 DataSoruce에서 트랜잭션을 어떻게 인식할까?
DataSource의 구현체는 많은데 이 모든 곳에 트랜잭션을 인식하는 코드를 생성하는 것은 비효율적이다.
여기서 Spring의 TransactionAwareDataSourceProxy가 나오는 것이다.


DataSource를 구현하는 객체들을 프록시로 감싸는 것이다.

getConnection() {
  if(트랜잭션이 열려있는가)
  		return ThreadLocal에_있는_커넥션_반환;
  else
  		return DataSource구현체.getConnection();
}

이렇게 흐름을 제어해주는 것 같다.

profile
개발 기록 아카이브

0개의 댓글