두 파일이 어떻게 짝지어지는가
"인터페이스에는 메서드 시그니처만 있고, XML 에는 SQL 만 있는데 — 어떻게 연결되어서 동작하지?"
「이름 매칭」 의 마법.
인터페이스와 XML 이 짝지어지는 데 필요한 4 가지 일치.
// src/main/java/com/smhrd/mapper/BoardMapper.java
package com.smhrd.mapper;
@Mapper
public interface BoardMapper { ... }
<!-- src/main/resources/com/smhrd/mapper/BoardMapper.xml -->
<mapper namespace="com.smhrd.mapper.BoardMapper">
↑
풀 클래스명과 완전히 일치
public interface BoardMapper {
Board selectOne(int num); // ← 메서드명
}
<mapper namespace="com.smhrd.mapper.BoardMapper">
<select id="selectOne"
parameterType="int"
resultType="com.smhrd.domain.Board">
↑
메서드 이름과 정확히 일치
SELECT num, title, writer, content
FROM myboard WHERE num = #{num}
</select>
</mapper>
<!-- root-context.xml -->
<bean id="sqlSessionFactory" ...>
<property name="mapperLocations"
value="classpath:com/smhrd/mapper/*Mapper.xml" />
</bean>
👉 인터페이스와 같은 경로(com/smhrd/mapper/) 의 *Mapper.xml 자동 등록.
<!-- root-context.xml -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.smhrd.mapper" />
</bean>
👉 이 패키지 안의 모든 @Mapper 인터페이스를 Bean 으로 자동 등록.
<!-- 단일 객체 -->
<select id="selectOne"
parameterType="int"
resultType="com.smhrd.domain.Board">
SELECT num, title, writer, content
FROM myboard WHERE num = #{num}
</select>
<!-- List 반환 -->
<select id="selectList" resultType="com.smhrd.domain.Board">
SELECT num, title, writer, content
FROM myboard ORDER BY num DESC
</select>
<!-- 인터페이스에서 List<Board> 라고 선언했으면 자동 List -->
<!-- 단순 타입 -->
<select id="count" resultType="int">
SELECT COUNT(*) FROM myboard
</select>
// 인터페이스
Board selectOne(int num);
Board selectByWriter(@Param("writer") String writer);
<!-- 단일 파라미터 — 이름 자유 -->
<select ...> WHERE num = #{num} </select>
<!-- 객체 — getter 호출 -->
<insert id="insert" parameterType="com.smhrd.domain.Board">
INSERT INTO myboard(title, writer, content)
VALUES(#{title}, #{writer}, #{content})
</insert>
<insert id="insert"
parameterType="com.smhrd.domain.Board"
useGeneratedKeys="true" keyProperty="num">
INSERT INTO myboard(title, writer, content)
VALUES(#{title}, #{writer}, #{content})
</insert>
Board b = new Board();
b.setTitle("새 글");
b.setWriter("hong");
b.setContent("안녕하세요");
mapper.insert(b);
System.out.println(b.getNum()); // ⭐ AUTO_INCREMENT 결과 자동 채워짐 (예: 7)
<!-- DB 컬럼명: snake_case (created_at) -->
<!-- Java 필드: camelCase (createdAt) -->
<select id="selectList" resultType="com.smhrd.domain.Board">
SELECT num, title, writer, content
FROM myboard ORDER BY num DESC
</select>
<!-- mybatis-config.xml 에서 한 줄 설정 -->
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
👉 컬럼명이 같은 칸(num, title, writer, content)이면 그대로 매핑. 이후 차시에서 created_at ↔ createdAt 같은 변환이 필요해지면 이 옵션이 핵심.
<resultMap id="boardMap" type="com.smhrd.domain.Board">
<id property="num" column="num" />
<result property="title" column="title" />
<result property="writer" column="writer" />
<result property="content" column="content" />
</resultMap>
<select id="selectList" resultMap="boardMap">
SELECT num, title, writer, content FROM myboard
</select>
👉 복잡한 매핑이 필요할 때 사용. 본 과정은 mapUnderscoreToCamelCase 만으로 대부분 충분.
| 증상 | 원인 |
|---|---|
| "Invalid bound statement" | namespace ↔ 풀 클래스명 / id ↔ 메서드명 불일치 |
| "No qualifying bean of type 'XMapper'" | MapperScannerConfigurer basePackage 잘못 |
| XML 못 찾음 | mapperLocations 경로 잘못 |
| 필드 모두 null | mapUnderscoreToCamelCase 누락 |
| INSERT 후 id 가 0 | useGeneratedKeys 누락 |
Mapper 가 동작은 하지만 어떻게 연결되는지 막연.
4 가지 매칭 규칙을 안다. resultType·#{}·useGeneratedKeys 사용.