SpringStudy

JDBC 최종

dding-shark 2025. 8. 21. 16:22
728x90

git link : https://github.com/DDINGJOO/Spring5PlayGround.git


1. 왜 순수 JDBC를 먼저 알아야 할까?

Spring에서 JPA/Hibernate를 사용하면 데이터 접근이 매우 편리해 보입니다. 하지만 JDBC를 모르고 ORM을 사용하면, 실제로 데이터베이스와 어떻게 통신하는지, 커넥션/트랜잭션/예외가 어떤 레벨에서 발생하는지, 성능 병목은 어디서 생기는지 등을 파악하기 어렵습니다. 순수 JDBC를 먼저 학습하면 다음과 같은 장점이 있습니다:

  • DB 드라이버, 커넥션, PreparedStatement, ResultSet의 흐름을 명확히 이해
  • 자원 정리(닫기), 예외 처리, 트랜잭션 경계의 중요성 체득
  • SQL과 파라미터 바인딩(? vs :name)의 차이와 위험 요소 인지
  • 템플릿 추상화(JdbcTemplate)가 왜 필요한지 “체감”
  • 추후 JPA/Hibernate 튜닝 및 네이티브 쿼리 작성 시 기반 지식으로 활용

이번 저장소에서는 ch6_02_DataModel 패키지에서 순수 JDBC의 기본을 경험하고, 이후 단계적으로 Spring JDBC가 제공하는 편의 기능들로 확장해 나갑니다.


2. 데이터 모델과 순수 JDBC 입문 (ch6_02)

  • 엔티티 경로:
    • ch6_02_DataModel/entities/Singer.java
    • ch6_02_DataModel/entities/Album.java
  • DAO 인터페이스 및 구현:
    • ch6_02_DataModel/dao/SingerDao.java
    • ch6_02_DataModel/dao/PlainSingerDao.java
  • 데모:
    • ch6_02_DataModel/PlainJdbcDemo.java

핵심은 “애플리케이션이 모든 것을 직접” 처리한다는 점입니다. 드라이버 로딩, 커넥션 획득, PreparedStatement 생성과 파라미터 바인딩, ResultSet에서 엔티티로 매핑, 자원 닫기까지 모두 우리의 책임입니다.

예: PlainSingerDao.insert (위치 기반 ? 바인딩)

PreparedStatement statement = connection.prepareStatement(
    "insert into singer(first_name, last_name, birth_date) values(?,?,?)",
    Statement.RETURN_GENERATED_KEYS
);
// 위치 기반("?") 파라미터 바인딩: 순서가 매우 중요합니다.
statement.setString(1, singer.getFirstName());
statement.setString(2, singer.getLastName());
statement.setDate(3, new Date(singer.getBirthDay().getTime()));
statement.executeUpdate();

이 시점에서 반드시 느껴야 하는 점:

  • ? 순서가 바뀌거나 빠지면 심각한 버그 초래
  • try-with-resources를 쓰지 않으면 자원 누수가 발생하기 쉬움
  • 예외 처리와 로그가 분산되어 코드가 장황해짐

이러한 단점을 줄이고 일관성을 확보하려고 Spring JDBC 템플릿이 등장합니다.


3. 애노테이션 기반 설정 + 내장 DB(H2) (ch6_03, ch6_04)

테스트/학습에 최적화된 환경을 만드는 방법을 익힙니다. H2 내장 DB를 사용하면 로컬 MySQL 없이도 스키마/데이터를 빠르게 올려서 반복 실험이 가능합니다.

예: ch6_03_Annotations/EmbeddedDbConfig.java

@Bean
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:db/schema.sql")
            .addScript("classpath:db/test-data.sql")
            .build();
}
  • schema.sql, test-data.sql을 통해 초기 스키마/데이터를 자동으로 로딩
  • 이후 DAO/서비스 테스트에서 빠른 피드백 루프 가능

ch6_04_Embedded/JdbcSingerDao.java는 DataSource만 주입받는 가장 단순한 DAO 스켈레톤입니다. 여기서부터 JdbcTemplate을 적용해 반복 코드를 줄여나갑니다.


4. 스프링의 예외 변환 (ch6_05)

DB 벤더별 SQLException을 Spring의 일관된 예외 계층으로 매핑합니다. 서비스 계층에서는 DB 종류와 상관없이 공통된 예외 타입으로 처리 가능해집니다.

예: MySQLErrorCodesTransfer (일부 커스텀 매핑 예)

public class MySQLErrorCodesTransfer extends SQLErrorCodeSQLExceptionTranslator {
    @Override
    protected DataAccessException customTranslate(String task, String sql, SQLException ex) {
        if (ex.getErrorCode() == -12345) {
            return new DeadlockLoserDataAccessException(task, ex);
        }
        return null;
    }
}

포인트:

  • JdbcTemplate은 예외 변환을 기본 제공하므로, 순수 JDBC 때보다 예외 처리 코드가 간결해집니다.
  • 서비스 계층은 DataAccessException 계열을 중심으로 리커버리/로깅/사용자 메시지를 표준화할 수 있습니다.

5. JdbcTemplate — 위치 기반 ? (ch6_06)

JdbcTemplate은 자원 관리, 예외 변환, 실행 흐름을 표준화해줍니다. 그러나 SQL 파라미터는 여전히 ?로 바인딩합니다.

예: ch6_06_JDBCTemplate/JdbcSingerDao_jdbctemplate.java

@Override
public String findNameById(Long id) {
    return jdbcTemplate.queryForObject(
            "select first_name || ' ' || last_name from singer where id = ?",
            new Object[]{id}, String.class);
}

장점:

  • 자원 정리/예외 처리/실행 흐름 표준화 → 보일러플레이트 대폭 감소
  • 빠르게 적용 가능, 학습 곡선 낮음

주의:

  • ? 순서 오류 가능성은 여전
  • 많은 파라미터/동적 SQL에서는 가독성과 유지보수성이 떨어짐

이 단점을 해결하는 것이 NamedParameterJdbcTemplate입니다.


6. NamedParameterJdbcTemplate — 네임드 :name (ch6_07)

네임드 파라미터는 SQL과 파라미터 매핑을 “이름”으로 연결합니다.

예: ch6_07_NamedParameterJdbcTemplate/JdbcSingerDao.java

String sql = "SELECT first_name || ' ' || last_name FROM singer WHERE id = :singerId";
Map<String, Object> params = new HashMap<>();
params.put("singerId", id);
return namedParameterJdbcTemplate.queryForObject(sql, params, String.class);

INSERT 예:

String sql = "INSERT INTO singer(first_name, last_name) VALUES (:firstName, :lastName)";
Map<String, Object> params = Map.of("firstName", singer.getFirstName(), "lastName", singer.getLastName());
namedParameterJdbcTemplate.update(sql, params);

장점:

  • 가독성, 유지보수성 향상: SQL과 파라미터의 의미가 이름으로 드러남
  • 동적 SQL에서 특히 강력: 조건 추가/제거 시 순서 혼동이 없음
  • 다수 파라미터를 가진 쿼리에서 실수 방지

실무 팁:

  • 팀 컨벤션으로 네임드 파라미터 권장
  • Spring Boot JPA에서 Native Query 작성 시에도 반드시 :name 사용 습관화

7. RowMapper와 ResultSetExtractor (ch6_08, ch6_09)

JDBC 결과를 객체로 매핑하는 두 가지 핵심 전략:

  • RowMapper: 각 행을 엔티티로 매핑하고 리스트로 합침 (단순 결과에 적합)
  • ResultSetExtractor: ResultSet 전체를 한 번 훑으며 복잡한 구조를 직접 조립 (조인/중첩 구조에 적합)

설계 팁:

  • 단순 조회: RowMapper 사용
  • 조인/중첩: ResultSetExtractor로 한 번에 처리해 N+1 문제나 중복 매핑 방지
  • 메서드/클래스 분리로 재사용성 확보

8. 배치 처리 Batch (ch6_10)

대용량 INSERT/UPDATE는 배치가 핵심입니다.

예: ch6_10_BatchOperations/BatchExamples.java

String sql = "INSERT INTO singer(first_name, last_name, birth_date) VALUES (?,?,?)";
return jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
    @Override
    public void setValues(PreparedStatement ps, int i) throws SQLException {
        Map<String, Object> row = rows.get(i);
        ps.setString(1, (String) row.get("first_name"));
        ps.setString(2, (String) row.get("last_name"));
        ps.setDate(3, new java.sql.Date(((java.util.Date) row.get("birth_date")).getTime()));
    }
    @Override
    public int getBatchSize() { return rows.size(); }
});

네임드 파라미터 배치:

String sql = "INSERT INTO singer(first_name, last_name, birth_date) VALUES (:firstName, :lastName, :birthDate)";
MapSqlParameterSource[] batch = rows.stream().map(r -> new MapSqlParameterSource()
        .addValue("firstName", r.get("first_name"))
        .addValue("lastName", r.get("last_name"))
        .addValue("birthDate", r.get("birth_date"))
).toArray(MapSqlParameterSource[]::new);
return namedParameterJdbcTemplate.batchUpdate(sql, batch);

성능 팁:

  • JDBC 드라이버/DB에 따라 rewriteBatchedStatements=true(MySQL류) 등 옵션으로 향상
  • 오토커밋을 끄고 트랜잭션 경계 안에서 실행
  • 배치 크기(예: 100~1000)를 환경/메모리/네트워크 조건에 맞게 조절

9. 자동 생성 키 (KeyHolder) (ch6_11)

INSERT 후 자동 증가 키(PK)를 얻는 방법:

String sql = "INSERT INTO singer(first_name, last_name) VALUES (?, ?)";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(con -> {
    PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
    ps.setString(1, firstName);
    ps.setString(2, lastName);
    return ps;
}, keyHolder);
Number newId = keyHolder.getKey();

네임드 버전도 동일하게 지원:

String sql = "INSERT INTO singer(first_name, last_name) VALUES (:firstName, :lastName)";
MapSqlParameterSource params = new MapSqlParameterSource()
        .addValue("firstName", firstName)
        .addValue("lastName", lastName);
KeyHolder keyHolder = new GeneratedKeyHolder();
namedParameterJdbcTemplate.update(sql, params, keyHolder);
Number newId = keyHolder.getKey();

10. SimpleJdbcInsert (ch6_12)

테이블/컬럼 메타를 제공해 INSERT를 매우 간단히 처리하고, 생성 키까지 쉽게 획득합니다.

SimpleJdbcInsert insert = new SimpleJdbcInsert(dataSource)
        .withTableName("singer")
        .usingGeneratedKeyColumns("id");
Map<String, Object> params = new HashMap<>();
params.put("first_name", firstName);
params.put("last_name", lastName);
Number newId = insert.executeAndReturnKey(params);

장점:

  • 설정 몇 줄로 INSERT + 키 반환 처리
  • 템플릿 코드가 더 짧고 명확

주의:

  • 메타정보 의존 → 스키마 변경 시 테스트로 빠르게 검증 필요

11. 트랜잭션 관리 (ch6_13)

순수 JDBC에서도 Spring의 DataSourceTransactionManager를 이용해 프로그래매틱 트랜잭션을 관리할 수 있습니다.

DataSourceTransactionManager txManager = new DataSourceTransactionManager(dataSource);
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setName("insertTwoRowsAtomically");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
try {
    jdbcTemplate.update("INSERT INTO singer(first_name, last_name) VALUES (?, ?)", "Tx", "One");
    jdbcTemplate.update("INSERT INTO singer(first_name, last_name) VALUES (?, ?)", "Tx", "Two");
    txManager.commit(status);
} catch (Exception ex) {
    txManager.rollback(status);
}

격리수준 요약:

  • READ_UNCOMMITTED: Dirty Read 허용 (거의 권장 안 함)
  • READ_COMMITTED: 커밋된 데이터만 읽기 (대다수 DB 기본)
  • REPEATABLE_READ: 같은 트랜잭션 내 동일 쿼리의 일관성
  • SERIALIZABLE: 가장 강력하지만 동시성 낮음

실무 팁:

  • 기본 READ_COMMITTED에서 시작, 필요한 경우에만 상향
  • 배치 + 트랜잭션 경계를 적절히 묶어 성능과 일관성 균형

12. SimpleJdbcCall — 프로시저/함수 (ch6_14)

CallableStatement를 단순화합니다. DB 별 시그니처가 다를 수 있으므로 반드시 실제 정의에 맞춥니다.

SimpleJdbcCall call = new SimpleJdbcCall(dataSource)
        .withProcedureName("increase_play_count");
MapSqlParameterSource in = new MapSqlParameterSource()
        .addValue("in_singer_id", singerId);
Map<String, Object> out = call.execute(in);

테스트 팁:

  • 테스트 환경(H2 등)에서 동일한 시그니처의 프로시저를 미리 생성
  • 파라미터 이름/타입이 실제 DB와 일치하는지 확인

13. Spring Boot에서의 네이티브 쿼리 — 반드시 :name 사용

JPA Repository에서 네이티브 쿼리를 쓸 때도 ?보다 :name을 사용하세요.

@Query(value = "SELECT * FROM singer s WHERE s.id = :singerId", nativeQuery = true)
Optional<Singer> findByIdNative(@Param("singerId") Long id);

이유:

  • 서비스/레포지토리 코드에서 파라미터 매핑을 명확히 문서화
  • SQL 변경 시에도 파라미터 순서 의존도 감소
  • 협업 및 리뷰 시 의도를 빠르게 파악

팀 컨벤션 제안:

  • “네이티브 쿼리는 무조건 :name을 쓴다.”
  • 파라미터 명명 규칙은 도메인 용어에 맞게 통일 (예: singerId, fromDate, toDate 등)

14. 실무 성능/안정성 체크리스트 (순수 JDBC 관점)

  • 커넥션 풀(DataSource)
    • HikariCP 권장, 최대/최소 풀 사이즈, 아이들 타임아웃, 커넥션 타임아웃 값 점검
    • DB 최대 연결 수와 애플리케이션 노드 수를 고려해 합리적 상한 설정
  • 트랜잭션 경계
    • 너무 넓으면 락 경쟁/대기, 너무 좁으면 일관성 저하 → 적절한 단위 찾기
  • 배치 옵션
    • 드라이버/DB 특성(rewriteBatchedStatements)과 배치 크기 테스트
  • 인덱스/쿼리 플랜
    • 쿼리 실행계획 점검, 불필요한 풀스캔 제거, 통계/히스토그램 최신화
  • 파라미터 바인딩
    • SQL 인젝션 방지: 문자열 연결 금지, 반드시 PreparedStatement/네임드 사용
  • 날짜/시간
    • java.time.* 사용 권장, JDBC 드라이버 매핑 방식 확인, 타임존 일관성 확보
  • LOB
    • 대용량 텍스트/바이너리는 스트리밍 API 사용 고려 (ResultSet.getBinaryStream 등)
  • 페이징
    • DB 방언별 LIMIT/OFFSET, seek 방식(키 기반) 고려, 대량 페이지 이동 최적화
  • 로깅/모니터링
    • SQL + 바인딩 파라미터 로깅(민감정보 마스킹), 슬로우 쿼리 추적, 메트릭 수집

15. 흔한 실수와 대처 요령

  • 자원 누수
    • 증상: 커넥션 고갈, 응답 지연, 타임아웃
    • 해결: try-with-resources 또는 템플릿 사용, 모든 경로에서 close 보장
  • ? 순서 오류
    • 증상: 잘못된 데이터 쓰기/읽기, 예외
    • 해결: 네임드 파라미터로 전환, 단위 테스트로 매핑 확인
  • N+1 쿼리/중복 매핑
    • 해결: ResultSetExtractor로 한 번에 조립, 필요한 컬럼만 조회
  • 트랜잭션 누락
    • 증상: 중간 실패 시 절반만 반영
    • 해결: 트랜잭션 경계 명확화, 실패 시 롤백 보장
  • 날짜/타임존 혼선
    • 해결: 서버/DB 타임존 통일, java.time 사용, 명시적 변환

16. 테스트 전략: 내장 H2로 빠르게 반복

  • 장점: 로컬/CI에서 빠른 실행, 스키마/데이터 스크립트로 테스트 독립성 확보
  • 팁:
    • 통합 테스트에서 H2로 검증 → 운영 DB 방언 차이는 별도 E2E로 보완
    • 스키마 차이를 흡수할 수 있는 추상화(DAO 인터페이스) 유지

샘플: EmbeddedDbConfig로 H2 부팅 → DAO에 주입 → JUnit에서 CRUD/트랜잭션 시나리오 검증


17. 코드 길이 줄이기 vs. 명시성의 균형

  • SimpleJdbcInsert/Call은 코드 길이를 줄여주지만, 팀 내 이해도와 익숙함도 고려해야 합니다.
  • 명시적인 JdbcTemplate 패턴이 디버깅에 유리한 경우도 존재
  • 결론: 서비스의 복잡도, 팀 숙련도, 유지보수 효율에 따라 적절히 선택

18. 네임드 파라미터(:name)를 표준으로 채택해야 하는 이유 요약

  • 가독성: SQL만 봐도 어떤 값이 매핑되는지 즉시 이해
  • 안전성: 파라미터 추가/삭제/재배치 시 순서 오류 위험 제거
  • 동적 SQL: 조건 토글 시 유지보수 비용 최소화
  • 일관성: Spring JDBC와 Spring Data JPA Native Query 모두 동일한 철학 적용

팀 가이드라인 예시:

  • 네이티브 SQL은 “항상” 네임드 파라미터 사용
  • 파라미터 이름은 도메인 용어, lowerCamelCase 권장
  • 유의미한 네이밍 예: singerId, fromReleaseDate, toReleaseDate, minAge

19. 실무 예제 모음 (요청/응답 패턴별)

  • 단건 조회 — NamedParameterJdbcTemplate
  • String sql = "select last_name from singer where id = :id"; return namedParameterJdbcTemplate.queryForObject(sql, Map.of("id", 10L), String.class);
  • 다건 조회 — RowMapper
  • String sql = "select id, first_name, last_name from singer where last_name = :ln"; return namedParameterJdbcTemplate.query(sql, Map.of("ln", "Sheeran"), (rs, i) -> { Singer s = new Singer(); s.setId(rs.getLong("id")); s.setFirstName(rs.getString("first_name")); s.setLastName(rs.getString("last_name")); return s; });
  • 조인/중첩 — ResultSetExtractor (개념 예시)
  • String sql = "select s.id as s_id, s.first_name, a.id as a_id, a.title from singer s left join album a on a.singer_id = s.id"; return jdbcTemplate.query(sql, rs -> { Map<Long, Singer> map = new LinkedHashMap<>(); while (rs.next()) { long sid = rs.getLong("s_id"); Singer s = map.computeIfAbsent(sid, k -> { Singer x = new Singer(); x.setId(k); x.setFirstName(rs.getString("first_name")); x.setAlbums(new ArrayList<>()); return x; }); long aid = rs.getLong("a_id"); if (!rs.wasNull()) { Album a = new Album(); a.setId(aid); a.setTitle(rs.getString("title")); s.getAlbums().add(a); } } return new ArrayList<>(map.values()); });
  • 배치 Insert — 네임드 파라미터
  • String sql = "insert into singer(first_name, last_name) values(:fn, :ln)"; MapSqlParameterSource[] batch = IntStream.range(0, 1000) .mapToObj(i -> new MapSqlParameterSource() .addValue("fn", "F" + i) .addValue("ln", "L" + i)) .toArray(MapSqlParameterSource[]::new); namedParameterJdbcTemplate.batchUpdate(sql, batch);

20. 트러블슈팅 가이드

  • “커넥션이 부족합니다”
    • 원인: close 누락, 풀 사이즈 부족, 장기 트랜잭션
    • 조치: 템플릿 사용/try-with-resources, 풀 설정 조정, 트랜잭션 범위 축소
  • “슬로우 쿼리가 자주 발생합니다”
    • 원인: 인덱스 미비, 비효율적 조인, 큰 데이터 전송
    • 조치: 실행계획 분석, 인덱스 보강, 필요한 컬럼만 선택, 페이징 최적화
  • “데이터가 가끔 틀립니다”
    • 원인: ? 순서 오류, 타입 변환 문제, 타임존 혼동
    • 조치: 네임드 파라미터, 명시적 타입 처리, 타임존 통일
  • “배치 성능이 안 나옵니다”
    • 원인: 오토커밋, 배치 크기 부적절, 드라이버 옵션 미설정
    • 조치: 트랜잭션으로 감싸기, 배치 크기 튜닝, 드라이버 옵션 점검

21. 앞으로의 확장: JPA/Hibernate로의 연결 (간단 예고)

이번 보고서는 순수 JDBC에 집중했습니다. 다음 단계에서는 아래 주제를 다루기 좋습니다:

  • JPA 리포지토리 vs. 직접 SQL의 경계 설정
  • 네이티브 쿼리와 ORM 연동 시 주의점 (플러시/컨텍스트 동기화)
  • 엔티티 그래프/페치 전략과 JDBC의 결과 세트 매핑 차이

그러나 “파라미터는 네임드(:name)”를 사용한다는 철학은 그대로 유지됩니다.


22. FAQ (자주 묻는 질문)

Q1. 왜 ?가 나쁘다는 거죠? JDBC의 표준 아닌가요?

  • A. 나쁘다기보다 “규모가 커질수록 위험해진다”가 핵심입니다. 순서에 의존하기 때문에 파라미터 수정 시 버그가 숨어들기 쉽습니다. :name은 이 위험을 크게 줄여줍니다.

Q2. 모든 쿼리를 네임드로 바꿔야 하나요?

  • A. 팀 컨벤션에 따르되, 특히 파라미터가 많은 쿼리/동적 SQL은 강력히 권장합니다.

Q3. H2와 운영 DB(SQL 문법 차이)가 걱정됩니다.

  • A. 테스트는 빠르게 H2로, 배포 전 운영 DB(예: MySQL, PostgreSQL)에 대한 통합/E2E 테스트로 방언 차이를 검증하세요.

Q4. 배치에서 예외가 나면 일부만 반영되나요?

  • A. 트랜잭션 경계로 감싸서 전체 성공/실패를 제어하십시오. 실패 행만 분리 재처리하는 전략도 고려합니다.

Q5. 생성 키가 null로 나옵니다.

  • A. 드라이버/DB가 RETURN_GENERATED_KEYS를 지원하는지, 키 컬럼/시퀀스 설정이 올바른지 점검하세요. SimpleJdbcInsert로 우회도 가능합니다.

23. 체크리스트 (요약)

  • 네이티브 SQL은 :name 사용
  • 트랜잭션 경계 명확화, 오토커밋 OFF (필요 시)
  • 배치 크기/드라이버 옵션 점검
  • SQL 인젝션 방지: PreparedStatement/NamedParameter 사용
  • 인덱스/실행계획 점검
  • 날짜/시간/타임존 일관성
  • SQL/파라미터 로깅(민감정보 마스킹)
  • H2 통합 테스트 + 운영 DB 차이 검증

24. 부록: 저장소 코드 참조

  • 순수 JDBC 데모: ch6_02_DataModel/PlainJdbcDemo.java
  • 순수 JDBC DAO: ch6_02_DataModel/dao/PlainSingerDao.java
  • 예외 변환: ch6_05_execptions/MySQLErrorCodesTransfer.java
  • JdbcTemplate DAO: ch6_06_JDBCTemplate/JdbcSingerDao_jdbctemplate.java
  • NamedParameterJdbcTemplate DAO: ch6_07_NamedParameterJdbcTemplate/JdbcSingerDao.java
  • 배치: ch6_10_BatchOperations/BatchExamples.java
  • 생성 키: ch6_11_GeneratedKeys_KeyHolder/KeyHolderExamples.java
  • SimpleJdbcInsert: ch6_12_SimpleJdbcInsert/SimpleJdbcInsertExamples.java
  • 트랜잭션: ch6_13_Transactions/TransactionExamples.java
  • SimpleJdbcCall: ch6_14_SimpleJdbcCall/SimpleJdbcCallExamples.java

25. 맺음말

이 보고서는 순수 JDBC부터 Spring JDBC 유틸리티까지, 실무에서 바로 활용 가능한 지식과 코드 패턴을 촘촘히 엮어 정리했습니다. 가장 강조하고 싶은 점은 다음과 같습니다:

1) 파라미터는 :name 네임드 방식을 표준으로 삼자 — 가독성, 안전성, 동적 SQL에서의 유연성이 탁월합니다.
2) 트랜잭션과 배치, 예외 변환, 자원 관리를 시스템적으로 적용하자 — “동작하지만 불안정한 코드”에서 “예측 가능하고 안정적인 코드”로 도약합니다.
3) 테스트 가능한 구조와 내장 DB(H2)를 활용한 빠른 피드백 루프를 구축하자 — 학습과 실무 품질을 동시에 끌어올립니다.

이제 본 보고서를 기반으로 자신의 프로젝트에 맞게 템플릿/네임드 파라미터/배치/트랜잭션 패턴을 도입하고, 차근차근 성능과 유지보수성을 높여 가시길 바랍니다. 다음 편에서는 JDBC 기반의 이해를 토대로 JPA/Hibernate로 확장하며 겪게 되는 차이와 베스트 프랙티스를 다룰 예정입니다. 감사합니다.

728x90

'SpringStudy' 카테고리의 다른 글

Spring Hibernate  (7) 2025.08.26
JDBC 학습 정리(1)  (0) 2025.08.21
Pointcut 문법 정리  (0) 2025.08.20
SpringAop -final  (0) 2025.08.20
Spring AOP  (1) 2025.08.19