Spring JDBC는 스프링 프레임워크에서 제공하는 JDBC 기반의 데이터 액세스 기술입니다. JDBC(Java Database Connectivity)는 자바 언어를 사용하여 관계형 데이터베이스에 접근하고 조작하기 위한 표준 API입니다.
Spring JDBC는 이러한 JDBC를 보다 쉽고 효율적으로 사용할 수 있도록 추상화된 기능을 제공합니다. 이를 통해 개발자는 반복적이고 번거로운 JDBC 작업을 간소화하고, 코드의 가독성을 높이며, 생산성을 향상시킬 수 있습니다.
JdbcTemplate의 주요 메서드는 다음과 같습니다.

프로젝트를 생성하는 방법은 다음과 같습니다.
프로젝트 생성이 완료되었습니다.
JSP 설정과 기본 프로젝트 구성 방법에 대해서는 이 링크를 참조하세요.
프로퍼티 파일에 값을 설정합니다. 다음과 같이 resources/application.properties 파일을 작성합니다.
# Oracle 설정
spring.datasource.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.username=musthave
spring.datasource.password=1234
DTO를 생성합니다. 다음과 같이 com.edu.springboot.jdbc.MemberDTO.java 파일을 작성합니다.
package com.edu.springboot.jdbc;
import lombok.Data;
// musthave 계정의 member 테이블의 컬럼과 동일하게 작성
@Data
public class MemberDTO {
private String id;
private String pass;
private String name;
private String regidate;
}
서비스를 생성합니다. 이는 DB 작업을 하기 위한 DAO를 명세한 인터페이스 입니다.
다음과 같이 com.edu.springboot.jdbc.IMemberService.java 파일을 작성합니다.
package com.edu.springboot.jdbc;
import java.util.List;
import org.springframework.stereotype.Service;
/* 컨트롤러와 DAO 사이에서 매개 역할을 하는 인터페이스로 DAO 클래스의 부모 역할을 한다.
* @Service 어노테이션은 @Controller와 동일하게 스프링 컨테이너가 시작될 때 자동으로 Scan 되어 빈이 생성된다.
* 따라서 이 클래스도 기본패키지 하위에 있어야 한다. */
@Service
public interface IMemberService {
// 회원목록
public List<MemberDTO> select();
// 회원정보 추가
public int insert(MemberDTO memberDTO);
// 회원정보 조회
public MemberDTO selectOne(MemberDTO memberDTO);
// 회원정보 수정
public int update(MemberDTO memberDTO);
// 회원정보 삭제
public int delete(MemberDTO memberDTO);
}
JDBC 작업을 위한 준비가 완료되었습니다.
홈을 생성합니다. 다음과 같이 webapp/WEB-INF/views/home.jsp 파일을 작성합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>스프링 부트 프로젝트</h2>
<ul>
<li><a href="/">루트</a></li>
</ul>
<h2>JdbcTemplate으로 구현한 회원관리</h2>
<ul>
<li><a href="./list.do">회원목록</a></li>
</ul>
</body>
</html>
컨트롤러를 생성합니다. 다음과 같이 java/com.edu.springboot.MainController.java 파일을 작성합니다.
package com.edu.springboot;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.edu.springboot.jdbc.IMemberService;
@Controller
public class MainController {
/* Service 역할의 인터페이스의 빈을 자동으로 주입받는다.
* 이를 통해 DAO 메서드를 호출한다. */
@Autowired
IMemberService dao;
@RequestMapping("/")
public String home() {
return "home";
}
// 회원목록
@RequestMapping("/list.do")
public String list(Model model) {
/* DAO에서 회원레코드를 추가한 List를 반환해주면 이를 Model 객체에 저장한 후 View로 포워드한다. */
model.addAttribute("memberList", dao.select());
return "list";
}
}
DAO를 생성합니다. 다음과 같이 com.edu.springboot.jdbc.MemberDAO.java 파일을 작성합니다.
package com.edu.springboot.jdbc;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
/* 이 어노페이션도 스프링 컨테이너 시작 시 자동으로 빈이 생성된다.
* @Controller, @Service와 동일한 역할을 한다. */
@Repository
public class MemberDAO implements IMemberService {
/* IMemberService 인터페이스를 구현했으므로 정의된 추상메서드는 일괄적으로 오버라이딩 해야한다.
* 컨트롤러에서 서비스 인터페이스를 통해 DAO의 각 메서드를 호출하게 된다.
* (상속이 되면 부모의 추상메서드를 통해 오버라이딩 된 자식의 메서드를 호출할 수 있다) */
/* JDBC 작업을 위해 자동주입 받는다.
* JdbcTemplete 빈은 개발자가 직접 설정하지 않고, build.gradle에 의존 설정이 되어 있으므로 스프링 컨테이너가 자동으로 만들어준다. */
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<MemberDTO> select() {
// 회원레코드를 가입일 기준으로 내림차순 정렬한 후 인출한다.
String sql = "SELECT * FROM member ORDER BY regidate DESC";
/* query() 메서드를 통해 select문을 실행한다.
* 쿼리문 실행 후 반환되는 ResultSet은 RowMapper가 자동으로 반복하여 DTO에 저장하고, 이를 List에 추가해서 반환해준다.
* 즉, 레코드를 컬렉션에 저장하기 위한 반복적인 작업을 자동으로 수행해준다. */
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<MemberDTO>(MemberDTO.class));
}
}
뷰를 생성합니다. 다음과 같이 webapp/WEB-INF/views/list.jsp 파일을 작성합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>회원리스트</h2>
<table border="1">
<tr>
<th>아이디</th>
<th>패스워드</th>
<th>이름</th>
<th>가입일</th>
<th></th>
</tr>
<!-- 컨트롤러에서 Model 객체에 저장한 List 타입의 인스턴스를 통해 크기만큼 반복하여 목록을 출력한다. -->
<c:forEach items="${ memberList }" var="row" varStatus="loop">
<tr>
<td>${ row.id }</td>
<td>${ row.pass }</td>
<td>${ row.name }</td>
<td>${ row.regidate }</td>
<td>
<a href="edit.do?id=${ row.id }">수정</a>
<a href="delete.do?id=${ row.id }">삭제</a>
</td>
</tr>
</c:forEach>
</table>
<a href="regist.do">회원등록</a>
</body>
</html>
이제 회원 목록을 조회할 수 있습니다. MemberDAO.java 파일에는 회원 목록을 조회하는 메서드만 구현했습니다. 그러므로 IMemberService.java 파일에서 회원 목록을 조회하는 메서드를 제외한 메서드를 주석처리 해야 에러가 발생하지 않습니다.
다음과 같이 실행됩니다.

'회원목록' 링크를 클릭하면 데이터베이스에 저장된 회원목록을 확인할 수 있습니다.

회원목록 조회 기능이 성공적으로 구현되었습니다.
컨트롤러를 생성합니다. 다음과 같이 com.edu.springboot.MainController.java 파일에 코드를 추가합니다.
// 회원정보 등록
/* @RequestMapping 어노테이션을 통해 매핑할 때 전송방식과 상관없이 설정하려면 프로퍼티 없이 요청명만 기술하면 된다.
* 하지만 아래와 같이 get/post 방식으로 구분하려면 value 프로퍼티에 요청명을, method 프로퍼티에 전송방식을 명시하면 된다. */
// @RequestMapping(value="/regist.do", method=RequestMethod.GET)
@GetMapping("/regist.do")
public String regist1() {
return "regist";
}
/* 스프링부트 3.x에서는 매핑 시 @GetMapping/@PostMapping의 사용을 권고하고 있다. */
@PostMapping("/regist.do")
@RequestMapping(value="/regist.do", method=RequestMethod.POST)
public String regist2(MemberDTO memberDTO) {
// 전송된 폼값은 DTO로 한번에 받은 후 DAO를 호출한다.
int result = dao.insert(memberDTO);
// insert의 결과이므로 1이면 성공이고 0이면 실패로 판단한다.
if (result == 1) System.out.println("회원정보가 등록되었습니다.");
/* View의 경로를 반환하는게 기본이지만, 아래와 같이 redirect:를 추가하면 해당 요청명으로 이동하게 된다. */
return "redirect:list.do";
}
뷰를 생성합니다. 다음과 같이 webapp/WEB-INF/views/regist.jsp 파일을 작성합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>회원등록</h2>
<form action="regist.do" method="post">
<table border="1">
<tr>
<th>아이디</th>
<td><input type="text" name="id" value="" /></td>
</tr>
<tr>
<th>패스워드</th>
<td><input type="text" name="pass" value="" /></td>
</tr>
<tr>
<th>이름</th>
<td><input type="text" name="name" value="" /></td>
</tr>
</table>
<input type="submit" value="전송하기" />
</form>
</body>
</html>
DAO를 생성합니다. 다음과 같이 com.edu.springboot.jdbc.MemberDAO.java 파일에 코드를 추가합니다.
// 회원정보 등록
@Override
public int insert(MemberDTO memberDTO) {
/* insert, update, delete와 같이 행의 변화가 생기는 메서드는 update() 메서드를 사용한다.
* 쿼리 실행 후 적용된 행의 개수를 int 형으로 반환한다. */
int result = jdbcTemplate.update(new PreparedStatementCreator() {
/* PreparedStatementCreator 인터페이스로 익명클래스를 생성한 후 오버라이딩 된 메서드 내에서 쿼리문을 실행하고 결과를 반환한다. */
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
/* 인파라미터가 있는 insert 쿼리문의 실행을 위해 PreparedStatement 인스턴스를 생성한 후
* 파라미터를 설정하고 쿼리문을 실행한다. */
String sql = "INSERT INTO member (id, pass, name) VALUES (?,?,?)";
PreparedStatement psmt = con.prepareStatement(sql);
psmt.setString(1, memberDTO.getId());
psmt.setString(2, memberDTO.getPass());
psmt.setString(3, memberDTO.getName());
return psmt;
}
});
// insert문 실행 후 반환받은 결과는 컨트롤러로 전달된다.
return result;
}
이제 회원정보를 등록할 수 있습니다. 앞서 IMemberService.java 파일에서 회원정보를 등록하는 메서드를 주석처리 했다면 주석을 해제합니다.
다음과 같이 실행됩니다.

'회원등록' 링크를 클릭하면 회원정보를 입력할 수 있는 페이지로 이동합니다.

다음과 같이 회원정보를 입력합니다.

입력한 후 '전송하기' 버튼을 클릭하면 회원정보가 등록된 후 회원목록 페이지로 이동합니다. 목록에서 회원정보가 정상적으로 등록되었음을 확인할 수 있습니다.

콘솔에는 다음과 같이 출력됩니다.
회원정보가 등록되었습니다.
콘솔에는 다음과 같이 출력됩니다.
컨트롤러를 생성합니다. 다음과 같이 com.edu.springboot.MainController.java 파일에 코드를 추가합니다.
// 회원정보 수정 1 : 기존 회원정보 조회하기
@RequestMapping(value="/edit.do", method=RequestMethod.GET)
public String edit1(MemberDTO memberDTO, Model model) {
// 아이디를 파라미터로 받아서 회원정보를 인출한다.
memberDTO = dao.selectOne(memberDTO);
model.addAttribute("dto", memberDTO);
return "edit";
}
DAO를 생성합니다. 다음과 같이 com.edu.springboot.jdbc.MemberDAO.java 파일에 코드를 추가합니다.
// 회원정보 수정 1 : 회원정보 조회
@Override
public MemberDTO selectOne(MemberDTO memberDTO) {
// 인파라미터가 있는 쿼리문 작성
String sql = "SELECT * FROM member WHERE id=?";
/* queryForObject 메서드는 반드시 하나의 결과가 있어야 하므로 없는 경우에는 예외가 발생하게 된다.
* 따라서 try~catch로 감싸는게 좋다. */
try {
/* queryForObject() 메서드는 하나의 결과를 반환하는 select 쿼리를 실행하르 때 사용한다.
* 인자1 : 쿼리문
* 인자2 : RowMapper(인출한 ResultSet을 DTO에 자동으로 입력)
* 인자3 : 인파라미터에 설정할 값을 배열로 선언 (순서대로 설정됨) */
memberDTO = jdbcTemplate.queryForObject(sql,
new BeanPropertyRowMapper<MemberDTO>(MemberDTO.class),
new Object[] { memberDTO.getId() });
}
catch (EmptyResultDataAccessException e) {
e.printStackTrace();
}
return memberDTO;
}
뷰를 생성합니다. 다음과 같이 webapp/WEB-INF/views/edit.jsp 파일을 작성합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h2>회원정보 수정</h2>
<form action="edit.do" method="post">
<table border="1">
<tr>
<th>아이디</th>
<!-- 아이디는 회원레코드에서 식별자(일련번호)로 사용되므로 수정할 수 없도록 readonly 속성을 추가해준다. -->
<td><input type="text" name="id" value="${ dto.id }" readonly /></td>
</tr>
<tr>
<th>패스워드</th>
<td><input type="text" name="pass" value="${ dto.pass }" /></td>
</tr>
<tr>
<th>이름</th>
<td><input type="text" name="name" value="${ dto.name }" /></td>
</tr>
</table>
<input type="submit" value="전송하기" />
</form>
</body>
</html>
이제 회원정보를 수정하기 위해 해당 회원정보를 조회할 수 있습니다. 앞서 IMemberService.java 파일에서 회원정보를 조회하는 메서드를 주석처리 했다면 주석을 해제합니다.
다음과 같이 실행됩니다.

수정할 회원의 '수정' 링크를 클릭하면 수정할 정보를 입력할 수 있는 페이지로 이동합니다.

컨트롤러를 생성합니다. 다음과 같이 com.edu.springboot.MainController.java 파일에 코드를 추가합니다.
// 회원정보 수정 2 : 수정처리
@RequestMapping(value="/edit.do", method=RequestMethod.POST)
// 전송된 폼값은 커맨드객체를 통해 한꺼번에 받아서 DAO로 전달
public String edit2(MemberDTO memberDTO) {
int result = dao.update(memberDTO);
// 결과가 1이면 수정성공
if (result == 1) System.out.println("회원정보가 수정되었습니다.");
// 상세보기 페이지가 없으므로 수정 후 목록으로 이동
return "redirect:list.do";
}
DAO를 생성합니다. 다음과 같이 com.edu.springboot.jdbc.MemberDAO.java 파일에 코드를 추가합니다.
// 회원정보 수정 2 : 수정처리
@Override
public int update(MemberDTO memberDTO) {
// 인파라미터가 있는 update 쿼리문 작성
String sql = "UPDATE member SET pass=?, name=? where id=?";
/* PreparedStatementSetter 인터페이스의 익명클래스를 이용해서 인파라미터를 설정하고 쿼리문을 실핸한다. */
int result = jdbcTemplate.update(sql, new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, memberDTO.getPass());
ps.setString(2, memberDTO.getName());
ps.setString(3, memberDTO.getId());
}
});
return result;
}
이제 회원정보를 수정할 수 있습니다. 앞서 IMemberService.java 파일에서 회원정보를 수정하는 메서드를 주석처리 했다면 주석을 해제합니다.
다음과 같이 실행됩니다.

수정할 회원의 '수정' 링크를 클릭하여 수정할 정보를 입력합니다.

'전송하기' 버튼을 누르면 회원 정보가 수정된 후 회원목록 페이지로 이동합니다.

콘솔에는 다음과 같이 출력됩니다.
회원정보가 수정되었습니다.
회원정보 수정 기능이 성공적으로 구현되었습니다.
컨트롤러를 생성합니다. 다음과 같이 com.edu.springboot.MainController.java 파일에 코드를 추가합니다.
@RequestMapping("/delete.do")
public String delete(MemberDTO memberDTO) {
int result = dao.delete(memberDTO);
if (result == 1) System.out.println("회원정보가 삭제되었습니다.");
return "redirect:list.do";
}
DAO를 생성합니다. 다음과 같이 com.edu.springboot.jdbc.MemberDAO.java 파일에 코드를 추가합니다.
// 회원정보 삭제
@Override
public int delete(MemberDTO memberDTO) {
String sql = "DELETE FROM member WHERE id=?";
// update 메서드를 통해 delete 쿼리문 실행. 인파라미터는 Object형 배열을 통해 설정한다.
int result = jdbcTemplate.update(sql, new Object[] { memberDTO.getId() });
return result;
}
다음과 같이 실행됩니다.

수정할 회원의 '삭제' 링크를 클릭하면 회원 정보가 삭제된 후 회원목록 페이지로 이동합니다. 목록에서 회원정보가 정상적으로 삭제되었음을 확인할 수 있습니다.

콘솔에는 다음과 같이 출력됩니다.
회원정보가 삭제되었습니다.
회원정보 삭제 기능이 성공적으로 구현되었습니다.