Spring MVC 게시판 만들기 4편: MyBatis와 데이터베이스 연동 완벽 가이드
💡 이 글에서 배울 것
MyBatis가 Java 코드와 SQL을 어떻게 연결하는지, 커넥션 풀은 왜 필요한지 쉽게 알아봅니다.
🎯 한눈에 보기
| 주제 | 핵심 내용 |
|---|---|
| MyBatis | SQL과 Java를 연결하는 프레임워크 |
| HikariCP | 빠른 데이터베이스 커넥션 풀 |
| 동적 SQL | 조건에 따라 변하는 SQL |
📌 이번 편에서 배울 것
이번 편에서는 MyBatis가 어떻게 Java와 SQL을 연결하는지 알아봅니다:
- MyBatis의 기본 개념
- Mapper 인터페이스와 XML 파일
- HikariCP 데이터베이스 연결 풀
selectKey와 동적 SQL
🤔 MyBatis란?
MyBatis는 SQL을 직접 작성하면서도 Java 코드와 쉽게 연결해주는 프레임워크예요.
flowchart LR
subgraph "Java 세계"
Mapper["BoardMapper.java<br/>(인터페이스)"]
end
subgraph "MyBatis 다리"
MB[MyBatis]
end
subgraph "SQL 세계"
XML["boardMapper.xml<br/>(SQL 쿼리)"]
end
subgraph "데이터베이스"
DB[(MySQL)]
end
Mapper <--> MB
MB <--> XML
XML <--> DB
MyBatis의 장점:
- ✅ SQL을 직접 작성 → 복잡한 쿼리도 OK
- ✅ Java 코드와 SQL 분리 → 유지보수 쉬움
- ✅ 동적 SQL → 조건에 따라 쿼리 변경 가능
⚙️ MyBatis 설정 (root-context.xml)
1. HikariCP (데이터베이스 연결 풀):

<!-- 연결 설정 -->
<bean name="hikariConfig" class="com.zaxxer.hikari.HikariConfig">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3306/springdb?serverTimezone=Asia/Seoul" />
<property name="username" value="springdbuser" />
<property name="password" value="1234" />
<property name="connectionTimeout" value="30000" />
<property name="minimumIdle" value="2" />
</bean>
<!-- 실제 DataSource -->
<bean name="dataSource" class="com.zaxxer.hikari.HikariDataSource"
destroy-method="close">
<constructor-arg ref="hikariConfig" />
</bean>
💡 커넥션 풀이란? DB 연결을 미리 여러 개 만들어두고 재사용하는 것. 매번 새로 연결하면 느리니까요!
flowchart LR
subgraph "HikariCP 풀"
C1[Connection 1]
C2[Connection 2]
C3[Connection 3]
end
App[애플리케이션] --> |"빌려주세요!"| C1
App --> |"다 썼어요~"| C1
C1 --> |"풀로 반환"| C1
2. SqlSessionFactory:
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath:/mapper/*.xml" />
<property name="configLocation" value="classpath:mybatis-config.xml" />
</bean>
3. Mapper 스캔:
<mybatis-spring:scan base-package="org.zerock.mapper"/>
이 설정으로 org.zerock.mapper 패키지의 모든 Mapper 인터페이스가 자동 등록됩니다!
📋 BoardMapper 인터페이스
Mapper 인터페이스는 메서드만 선언합니다. 구현은 XML에서!
public interface BoardMapper {
int insert(BoardDTO dto); // 등록
BoardDTO selectOne(Long bno); // 상세 조회
int remove(Long bno); // 삭제
int update(BoardDTO dto); // 수정
List<BoardDTO> list(); // 전체 목록
// 검색 + 페이징 목록
List<BoardDTO> listSearch(
@Param("skip") int skip,
@Param("count") int count,
@Param("types") String[] types,
@Param("keyword") String keyword
);
// 검색 결과 개수
int listCountSearch(
@Param("types") String[] types,
@Param("keyword") String keyword
);
}
💡 @Param(“이름”): SQL에서
#{이름}으로 값을 받을 수 있게 해줌
📄 boardMapper.xml 분석
1. INSERT (등록) + selectKey:
<insert id="insert">
<!-- 삽입 후 자동 생성된 bno를 가져오기 -->
<selectKey order="AFTER" keyProperty="bno" resultType="long">
SELECT LAST_INSERT_ID()
</selectKey>
INSERT INTO tbl_board(title, content, writer)
VALUES (#{title}, #{content}, #{writer})
</insert>
sequenceDiagram
participant S as Service
participant M as Mapper
participant DB as MySQL
S->>M: insert(dto)
M->>DB: INSERT INTO tbl_board...
DB-->>M: 완료 (새 bno = 42)
M->>DB: SELECT LAST_INSERT_ID()
DB-->>M: 42
M->>M: dto.bno = 42 설정
M-->>S: 1 (영향받은 행 수)
💡
selectKey가 실행되면dto.getBno()로 새 글 번호를 알 수 있어요!
2. SELECT (조회):
<select id="selectOne" resultType="org.zerock.dto.BoardDTO">
SELECT * FROM tbl_board WHERE bno = #{bno}
</select>
3. UPDATE (수정):
<update id="update">
UPDATE tbl_board SET
title = #{title},
content = #{content},
updatedate = now(),
delflag = #{delFlag}
WHERE bno = #{bno}
</update>
4. DELETE (소프트 삭제):
<update id="remove">
UPDATE tbl_board
SET delflag = true
WHERE bno = #{bno}
</update>
💡 실제로
DELETE가 아니라UPDATE로delflag = true만 변경합니다!
🔮 동적 SQL - 검색 기능의 핵심
검색 기능은 조건에 따라 SQL이 달라져야 해요. MyBatis의 동적 SQL 기능을 사용합니다!
<!-- 재사용 가능한 SQL 조각 -->
<sql id="search">
<if test="types != null and types.length > 0 and keyword != null and keyword != ''">
<foreach collection="types" item="type"
open=" AND (" close=")" separator="OR">
<if test='type.equals("T")'>
title LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test='type.equals("C")'>
content LIKE CONCAT('%', #{keyword}, '%')
</if>
<if test='type.equals("W")'>
writer LIKE CONCAT('%', #{keyword}, '%')
</if>
</foreach>
</if>
</sql>
사용 예시:
<select id="listSearch" resultType="BoardDTO">
SELECT bno, title, writer, regDate
FROM tbl_board
WHERE delflag = false
<include refid="search"></include> <!-- 동적 검색 조건 삽입 -->
ORDER BY bno DESC
LIMIT #{count} OFFSET #{skip}
</select>
📊 동적 SQL 결과 예시
| 검색 조건 | 생성되는 SQL |
|---|---|
| 조건 없음 | WHERE delflag = false ORDER BY... |
| T + “스프링” | WHERE delflag = false AND (title LIKE '%스프링%') ORDER BY... |
| TC + “자바” | WHERE delflag = false AND (title LIKE '%자바%' OR content LIKE '%자바%') ORDER BY... |
| TCW + “홍길동” | WHERE delflag = false AND (title LIKE '%홍길동%' OR content LIKE '%홍길동%' OR writer LIKE '%홍길동%') ORDER BY... |
flowchart TD
Input["types=TC, keyword=자바"] --> Check{types가 있나?}
Check --> |Yes| ForEach["foreach 시작"]
ForEach --> T{type=T?}
T --> |Yes| TQ["title LIKE '%자바%'"]
ForEach --> C{type=C?}
C --> |Yes| CQ["content LIKE '%자바%'"]
TQ --> |OR| Final["( title LIKE ... OR content LIKE ... )"]
CQ --> Final
📝 정리
| 개념 | 설명 |
|---|---|
| MyBatis | SQL과 Java를 연결하는 프레임워크 |
| HikariCP | 빠른 DB 커넥션 풀 |
| Mapper | 인터페이스 = 메서드 선언, XML = SQL 구현 |
| selectKey | INSERT 후 자동 생성된 키 가져오기 |
| 동적 SQL | <if>, <foreach>로 조건부 SQL 생성 |
🚀 다음 편 예고
**5편: 페이징과 검색 기능 구현하기**에서는:
- JSP에서 페이징 UI 구현
- JavaScript 이벤트 처리
- 검색 조건 유지하기
을 알아볼 예정입니다!
📚 시리즈 목차
- [1편] 프로젝트 구조와 아키텍처 완벽 정리
- [2편] Spring MVC 컨트롤러와 요청 흐름 이해하기
- [3편] 서비스 계층과 비즈니스 로직 구현하기
- [4편] MyBatis와 데이터베이스 연동 완벽 가이드 ← 현재 글
- [5편] 페이징과 검색 기능 구현하기
- [6편] 댓글 기능과 예외 처리 마스터하기