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=myapp
currentSchema=스키마명
: 기본 스키마 지정 (예 : app, public 다중도 가능)sslmode=
: disable
|allow
|prefer
|require
|verify-ca
|verify-full
connectTimeout=10
: 접속 타임아웃(초)socketTimeout=30
: 쿼리 실행 대기 타임아웃(초)ApplicationName=myapp
: 접속 식별용 이름reWriteBatchedInserts=true
: 배치 INSERT 최적화stringtype=unspecified
: 문자열 파라미터 타입 유연화(특정 케이스에 유용)<%
String sql = "SELECT * FROM NOTICE";
...
...
%>
SELECT 컬럼명만
지정 (와일드카드 * 지양) 스키마 명시 ex) public.notice
PreparedStatement
로 파라미터 바인딩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 전용, 결과는 ResultSet
executeUpdate(sql)
: INSERT/UPDATE/DELETE 반환값은 영향 받은 행 수 int
execute(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
→ Connection
String 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>