JdbcTemplate - update와 KeyHolder

Jang990·2026년 1월 27일

구현

여기에 생성 부분을 내가 만든 JdbcTemplate으로 변경해 볼 것이다.

// KeyHolder는 단순하게 만든다.
public class MyKeyHolder {
    private Long id;

    protected void setId(long id) { this.id = id; }
    public Long getId() { return id; }
}

// JdbcTemplate에서 제공하는 것도 MyPreparedStatementCreator에 이름만 바꿔서 생성했다.
@FunctionalInterface
public interface MyPreparedStatementCreator {
    PreparedStatement createPreparedStatement(Connection con) throws SQLException;
}

public class MyJdbcTemplate {
	// 단순하다. ps를 만들어주면 실행하고 반환해주면 된다. ID는 홀더에 넣어주고.
    public int update(Connection conn, MyPreparedStatementCreator psc, KeyHolder keyHolder) {
        try(PreparedStatement ps = psc.createPreparedStatement(conn)) {
            int result = ps.executeUpdate();
            keyHolder.setId(findGeneratedId(ps));
            return result;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private long findGeneratedId(PreparedStatement ps) throws SQLException {
        try (ResultSet keyResultSet = ps.getGeneratedKeys()) {
            if(!keyResultSet.next())
                throw new SQLException("생성된 키가 없음");
            return keyResultSet.getLong(1);
        }
    }
    
    ...
}

JdbcTemplate을 구현하는게 아니기 때문에 KeyHolder는 단순하게 만들고 넘어간다.


적용 전

    public Foods save(Foods food) {
        try (Connection conn = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword());
                PreparedStatement ps = conn.prepareStatement("""
                     INSERT INTO foods(name, price, stock)
                     VALUES
                     (?, ?, ?)
                     """, Statement.RETURN_GENERATED_KEYS)) {
            ps.setString(1, food.getName());
            ps.setInt(2, food.getPrice());
            ps.setInt(3, food.getStock());

            ps.executeUpdate();

            try(ResultSet rs = ps.getGeneratedKeys()) {
                rs.next();
                long foodId = rs.getLong(1);
                MyEntityIdInjector.injectId(food, foodId);
                return food;
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

적용 후

	public Foods save(Foods food) {
        try (Connection conn = DriverManager.getConnection(dbConfig.getUrl(), dbConfig.getUsername(), dbConfig.getPassword())) {
            MyKeyHolder myKeyHolder = new MyKeyHolder();
            myJdbcTemplate.update(conn, con -> {
                PreparedStatement ps = conn.prepareStatement("""
                        INSERT INTO foods(name, price, stock)
                        VALUES
                        (?, ?, ?)
                        """, Statement.RETURN_GENERATED_KEYS);

                ps.setString(1, food.getName());
                ps.setInt(2, food.getPrice());
                ps.setInt(3, food.getStock());
                return ps;
            }, myKeyHolder);

            MyEntityIdInjector.injectId(food, myKeyHolder.getId());
            return food;
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

헷갈리지 말자.
repository이기 때문에 내부 구현 기술인 PreparedStatement가 있어도 상관없다.


느낀점

확실히 try-catch문이 많이 사라지고 있다.

처음 시작할 때 테스트 코드 꼭 작성하자.
처음에 좀 귀찮아도 작성해 놓으니까 잘 동작하는지 체크하기 쉽다.

profile
개발 기록 아카이브

0개의 댓글