<resultMap id="memberWithOrders" type="Member" extends="memberMap"> <!-- ❌ name, joinedAt … 전부 null -->
<resultMap id="memberWithOrders" type="Member" extends="memberMap" autoMapping="true"> <!-- ✅ -->기본 autoMappingBehavior=PARTIAL 은 중첩 매핑이 있는 resultMap 에 자동 매핑을 하지 않습니다. 증상이 "예외 없이 필드가 null" 이라 원인을 찾기 어렵습니다. 예제 6 에서 실제로 겪은 문제입니다.
boolean 과 _boolean 별칭을 혼동한다<typeHandler handler="YesNoTypeHandler" javaType="boolean"/> <!-- ❌ java.lang.Boolean 에만 등록. 원시형 boolean 필드는 기본 핸들러 → CHAR(1) 에 "TRUE" 저장 시도 → 오류 -->
<typeHandler handler="YesNoTypeHandler" javaType="_boolean"/> <!-- ✅ 원시형. 래퍼는 boolean 또는 java.lang.Boolean -->MyBatis 내장 별칭은 원시형에 _ 접두어를 붙입니다. _int, _long, _boolean 이 원시형이고 int, long, boolean 이 래퍼입니다. resultType 에도 같은 규칙입니다. resultType="long" 은 Long, resultType="_long" 은 long. 이 레슨을 작성하며 처음 실행에서 바로 이 오류가 났습니다.
< 를 이스케이프하지 않는다<if test="to != null">AND joined_at <= #{to}</if> <!-- ❌ XML 파싱 오류: 요소 이름이 잘못됨 -->
<if test="to != null"><![CDATA[ AND joined_at <= #{to} ]]></if> <!-- ✅ 또는 <= -->test 속성 안의 < 도 마찬가지입니다. test="a < b" 로 씁니다. 반대로 CDATA 안에 <if> 를 넣으면 태그가 아니라 문자열로 취급되어 SQL 에 <if …> 가 그대로 들어가 SQL 오류가 납니다.
INSERT INTO member (…, email) VALUES (…, #{email}) <!-- ❌ email 이 null 이면 Invalid column type -->
INSERT INTO member (…, email) VALUES (…, #{email, jdbcType=VARCHAR}) <!-- ✅ 개별 지정 -->
<setting name="jdbcTypeForNull" value="NULL"/> <!-- ✅ 전역. 대부분 이걸로 끝 -->Oracle JDBC 는 setNull(i, Types.OTHER) 를 거부합니다. MyBatis 기본 jdbcTypeForNull 이 OTHER 라 null 이 처음 들어오는 순간 터집니다. H2/MariaDB 에서 개발하고 Oracle 운영에 올렸을 때 첫날 만나는 오류입니다.
Page.start(1, 20);
if (!valid(cond)) return List.of(); // ❌ 조회 없이 반환 → 지시가 남아 다음 조회에 페이징이 걸림
List<Member> list = mapper.search(cond);
try { Page.start(1, 20); return mapper.search(cond); } finally { Page.clear(); } // ✅PageHelper 도 같습니다. 예제 4 가 재현한 대로 캐시 히트, 예외, 조기 반환 어느 것이든 SQL 이 실행되지 않으면 지시가 살아남습니다. 증상이 "가끔 다른 화면 목록이 비어 있다"라 재현이 어렵습니다.
Map<String, Object> row = mapper.monthlyJoinStats().get(0);
row.get("ym"); // ❌ null. Oracle(그리고 H2 Oracle 모드)은 컬럼명을 대문자로 돌려준다 → 키는 "YM"
row.get("YM"); // ✅ 하지만 MariaDB 에서는 "ym" 이라 벤더에 따라 깨진다Map 결과는 DB 마다 키 대소문자가 다릅니다. 통계·리포트도 DTO(record) 로 받아 mapUnderscoreToCamelCase 에 맡기는 것이 벤더 중립적입니다. 꼭 Map 이어야 하면 AS "ym" 처럼 따옴표 별칭으로 대소문자를 고정합니다.
// 기동 시 한 번: 매퍼 인터페이스의 모든 메서드에 statement 가 있는지 확인
for (Class<?> mapper : conf.getMapperRegistry().getMappers())
for (Method m : mapper.getMethods())
if (!conf.hasStatement(mapper.getName() + "." + m.getName()))
throw new IllegalStateException("XML 에 없는 statement: " + mapper.getName() + "." + m.getName());컴파일러가 잡지 못하는 연결을 기동 시점 검사로 앞당깁니다. Spring 이면 @PostConstruct 나 ApplicationRunner 에, 아니면 테스트 하나에 둡니다. 10줄로 "잘못된 화면 눌렀을 때 500 에러"를 "기동 실패"로 바꿉니다.