
JSP 프로젝트를 하게 되어 학습을 하고있는 상황이다. 간단하게 해당 프로젝트에 DB연동을 위해 작성하게 되었다. 현재 환경은 IntelliJ + Maven + JSP + PostgreSQL 이다.
pom.xml에 PostgreSQL JDBC 드라이버를 넣는다.<dependencies>
...
...
...
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.3</version>
</dependency>
</dependencies>
IntelliJ → View → Tool Windows → Maven 클릭 후 상단 Sync 아이콘을 클릭하여 Reload All Maven Projects를 클릭한다.<%
String url = "jdbc:postgresql://localhost:5432/project";
...
...
%>
jdbc:postgresql://<host>:<port>/<database>?<params>jdbc:postgresql://localhost:5432/project?currentSchema=public&ApplicationName=myappcurrentSchema=스키마명 : 기본 스키마 지정 (예 : app, public 다중도 가능)sslmode= : disable|allow|prefer|require|verify-ca|verify-fullconnectTimeout=10 : 접속 타임아웃(초)socketTimeout=30 : 쿼리 실행 대기 타임아웃(초)ApplicationName=myapp : 접속 식별용 이름reWriteBatchedInserts=true : 배치 INSERT 최적화stringtype=unspecified : 문자열 파라미터 타입 유연화(특정 케이스에 유용)<%
String sql = "SELECT * FROM NOTICE";
...
...
%>
SELECT 컬럼명만 지정 (와일드카드 * 지양) 스키마 명시 ex) public.noticePreparedStatement로 파라미터 바인딩SQL문을 미리 준비하고 컴파일하여 캐시된 실행 계획을 반복적으로 재사용하는 데이터베이스 객체로, SQL쿼리 실행 속도를 향상시키고 SQL Injection 공격을 방지하는 보안 기능도 제공한다. Statement와 달리 물음표(?)와 같은 플레이스 홀더를 사용해 동적인 데이터 바인딩하는 방식으로 데이터 베이스에서 동일하거나 비슷한 SQL문을 효율적으로 실행할 때 사용된다.
주요 특징 및 동작 방식
Statement와 차이
<%
String user = "postgres";
String pass = "비밀번호";
...
...
%>
💡 하드코딩은 지양
DriverManager등록<%
Class.forName("org.postgresql.Driver");
...
...
%>
ClassNotFoundException 발생 시: Maven 의존성 확인DriverManager.getConnection 오버로드<%
Connection con = DriverManager.getConnection(url, user, pass);
...
...
%>
(url, user, pass) — 지금 방식(url) — URL에 user/password 파라미터 포함 시(url, Properties props) — 세부 옵션을 Properties로 전달유용한 Connection 설정
con.setAutoCommit(false); 트랜잭션 수동제어 (INSERT/UPDATE/DELETE 후 commit/rollback)con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); 등 격리수준 설정더 좋은 실무 방식
<%
Statement st = con.createStatement();
...
...
%>
Statement st = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE, // 스크롤 가능(원본 변경 반영 X)
ResultSet.CONCUR_READ_ONLY, // 읽기 전용
ResultSet.HOLD_CURSORS_OVER_COMMIT // COMMIT 후에도 커서 유지(드물게 사용)
);
<%
ResultSet rs = st.executeQuery(sql);
...
...
%>
executeQuery(sql) : SELECT 전용, 결과는 ResultSetexecuteUpdate(sql) : INSERT/UPDATE/DELETE 반환값은 영향 받은 행 수 intexecute(sql) : 결과가 ResultSet인지 업데이트 카운트인지 모를 때addBatch()/executeBatch() - 대량 INSERT/UPDATE최적화9️⃣🔟
읽기 패턴
while (rs.next()) { // 다음 행으로 이동, 없으면 false
int id = rs.getInt("id"); // 컬럼명 또는 1부터 시작하는 인덱스
String title = rs.getString("title");
}
SELECT id AS notice_id 라벨 지정 시 rs.getInt("notice_id")rs.getInt(...)뒤에 rs.wasNull()로 방금 읽은 값이 NULL인지 확인 가능getObject, getString, getTimestamp)은 그대로 null 반환JDBC에서 ResultSet을 스크롤 가능하게 만들 수 있는 옵션은 ResultSet이 기본적으로 순방향(Forward-only) 으로만 레코드를 읽을 수 있기 때문이다.
즉, 기본 ResultSet은
.next() 메서드로 앞으로 한 칸씩만 이동 가능이를 해결하기 위해 스크롤 가능한 ResultSet 옵션을 사용하면,
next() 뿐만 아니라 previous(), absolute(n), relative(n) 같은 메서드로 앞뒤 이동과 임의 위치 이동이 가능해진다.
ClassNotFoundException : 드라이버 없음 → 의존성 점검SQLException : 인증 실패, URL 오타, 권한, 타임아웃 등ResultSet → Statement → ConnectionString sql = "SELECT id, title FROM notice";
try (Connection con = DriverManager.getConnection(url, user, pass);
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
...
}
} catch (SQLException e) {
// 로깅/처리
}
실전형 리팩터링 예시
자바(서블릿/DAO) - 데이터 읽기
// NoticeDao.java
public class NoticeDao {
private final DataSource ds; // HikariCP or JNDI로 주입
public NoticeDao(DataSource ds) { this.ds = ds; }
public List<Notice> findAll() throws SQLException {
String sql = "SELECT id, title FROM public.notice ORDER BY id ASC";
List<Notice> list = new ArrayList<>();
try (Connection con = ds.getConnection();
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
Notice n = new Notice();
n.setId(rs.getInt("id"));
n.setTitle(rs.getString("title"));
list.add(n);
}
}
return list;
}
}
💡 컨트롤러(서블릿)에서
request.setAttribute("noticeList", dao.findAll());후 JSP로forward.
JSP - 출력만(JSTL)
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<table>
<thead><tr><th>ID</th><th>Title</th></tr></thead>
<tbody>
<c:forEach var="n" items="${noticeList}">
<tr>
<td>${n.id}</td>
<td>${n.title}</td>
</tr>
</c:forEach>
</tbody>
</table>