Spring Framework 환경에서 클라이언트의 요청이 처리되는 전체적인 흐름과, MyBatis를 연동하여 데이터베이스를 효율적으로 조작하는 방법에 대해 학습한 내용을 정리한다.
Spring MVC는 DispatcherServlet을 중심으로 클라이언트의 요청을 처리한다. 전체적인 흐름은 다음과 같다.
DispatcherServlet의 요청 수신: 클라이언트의 모든 요청은 프론트 컨트롤러인 DispatcherServlet이 가장 먼저 받는다.
HandlerMapping: DispatcherServlet은 HandlerMapping을 통해 해당 요청을 처리할 적절한 컨트롤러를 찾는다.
Controller 실행: 찾은 컨트롤러의 메소드를 호출하여 로직을 수행하며, 결과로 뷰의 이름(String) 등을 리턴한다.
ViewResolver: DispatcherServlet은 application.properties에 정의된 설정을 바탕으로 ViewResolver를 통해 물리적인 뷰 경로를 완성한다.
응답 리턴: 완성된 경로의 JSP 또는 HTML을 호출하고, 모델(Model)에 담긴 값을 ${} 등을 이용해 렌더링하여 클라이언트에게 최종 응답을 보낸다.
MVC는 소프트웨어 설계 시 발생하는 문제를 해결하기 위한 디자인 패턴의 일종이다. 레이어별 역할 분담은 다음과 같다.
Controller: 사용자의 요청을 수신하고 응답하는 창구 역할을 한다.
Model: 실제 비즈니스 로직과 데이터 처리를 담당한다. (Service ↔ DAO ↔ DB)
View: 사용자에게 보여지는 화면을 담당한다.
MyBatis의 역할: 데이터 접근 객체(DAO)와 데이터 엔티티(Entity) 간의 처리를 자동화하여 SQL 쿼리와 자바 객체를 매핑해준다.
MyBatis를 사용하기 위해서는 데이터베이스 연결 정보와 매퍼(Mapper) 파일의 위치를 설정해야 한다.
driver, url, username, password는 필수 요소이며, XML 매퍼 위치와 DTO 별칭을 설정할 수 있다.
# MySQL Database setting
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/ssafyweb?serverTimezone=Asia/Seoul&useUniCode=yes&characterEncoding=UTF-8
spring.datasource.username=ssafy
spring.datasource.password=ssafy
# MyBatis 매퍼 파일 위치 설정
mybatis.mapper-locations=mapper/**/*.xml
# DTO 클래스의 패키지 경로를 축약하기 위한 별칭 설정
mybatis.type-aliases-package=com.ssafy.mybatis.dto
주의 사항: MyBatis를 사용할 때 DTO에 사용자 정의 생성자를 만들면 기본 생성자가 없어 객체 생성 시 문제가 발생할 수 있으므로 주의해야 한다.
MyBatis는 인터페이스만 정의하면 실행 시점에 동적으로 구현체를 생성해준다.
GuestBookMapper.java)@Mapper 어노테이션을 사용하여 MyBatis 매퍼임을 명시한다.
package com.ssafy.mybatis.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.ssafy.mybatis.dto.GuestBookDto;
@Mapper
public interface GuestBookMapper {
// 단건 조회
public GuestBookDto selectGuestBook(int id);
// 단건 입력
public int writeGuestBook(GuestBookDto dto);
// 사용자별 목록 조회
public List<GuestBookDto> selectByUser(String userid);
}
mapper/guestbook.xml)SQL 쿼리를 자바 코드와 분리하여 관리한다. namespace는 인터페이스의 전체 경로와 일치해야 한다.
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ssafy.mybatis.mapper.GuestBookMapper">
<select id="selectGuestBook" resultType="GuestBookDto">
select * from guestbook where articleno = #{id}
</select>
<insert id="writeGuestBook" parameterType="GuestBookDto" useGeneratedKeys="true" keyProperty="articleno">
insert into guestbook (userid, subject, content)
values (#{userid}, #{subject}, #{content})
</insert>
<select id="selectByUser" parameterType="String" resultType="GuestBookDto">
select * from guestbook where userid = #{id}
</select>
</mapper>
@Autowired를 통해 Spring 컨테이너가 생성한 매퍼 빈을 주입받아 사용한다.
@Controller
public class HomeController {
private static final Logger log = LoggerFactory.getLogger(HomeController.class);
@Autowired
private GuestBookMapper mapper;
@GetMapping("/home")
public String home(Model model) {
log.info("조회 요청 발생");
// 원칙적으로는 Service 레이어를 거쳐야 함
GuestBookDto dto = mapper.selectGuestBook(1);
model.addAttribute("dto", dto);
return "home"; // /WEB-INF/views/home.jsp 호출
}
@GetMapping("/write")
public String write() {
GuestBookDto dto = new GuestBookDto();
dto.setUserid("ssafy");
dto.setContent("신규 콘텐츠 내용");
dto.setSubject("신규 제목 " + System.currentTimeMillis());
int result = mapper.writeGuestBook(dto); // useGeneratedKeys에 의해 articleno가 dto에 담김
log.info("등록 결과: {}, 생성된 번호: {}", result, dto.getArticleno());
return "home";
}
@GetMapping("/selectByUser/{id}")
public String selectByUser(Model model, @PathVariable String id) {
List<GuestBookDto> list = mapper.selectByUser(id);
model.addAttribute("list", list);
return "home2";
}
}
Spring Boot는 기본적으로 HikariCP를 사용하여 커넥션 풀을 관리한다. 이는 매번 DB에 연결하는 비용을 줄여주어 성능을 향상시킨다.
| 속성명 | 설명 |
|---|---|
| maximum-pool-size | 풀에 유지할 수 있는 최대 커넥션 수 (기본값: 10) |
| connection-timeout | 커넥션을 얻기 위해 대기하는 최대 시간 |
| idle-timeout | 풀에서 유휴 상태로 남을 수 있는 최대 시간 |
오늘 수업을 통해 Spring의 전체적인 요청 흐름과 MyBatis를 이용한 DB 연동의 기초를 다질 수 있었다. 특히 useGeneratedKeys 속성을 통해 DB에서 자동 생성된 PK 값을 바로 객체로 받아오는 방식이 유용하게 사용될 것으로 보인다.