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.javach6_02_DataModel/entities/Album.java
- DAO 인터페이스 및 구현:
ch6_02_DataModel/dao/SingerDao.javach6_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)과 배치 크기 테스트
- 드라이버/DB 특성(
- 인덱스/쿼리 플랜
- 쿼리 실행계획 점검, 불필요한 풀스캔 제거, 통계/히스토그램 최신화
- 파라미터 바인딩
- 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로 확장하며 겪게 되는 차이와 베스트 프랙티스를 다룰 예정입니다. 감사합니다.
'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 |