SQL을 자바에서 분리해서 응집도↑ 결합도↓
오늘 수업 핵심은 DAO 내부에 박혀 있던 SQL을 밖으로 빼서 (Mapper XML)
자바 코드는 “어떤 SQL을 실행할지”만 지정하고 실제 SQL은 별도 파일에서 관리하도록 구조를 바꾸는 거였습니다.
결과적으로 모델 (DAO) 파트의 응집도는 올라가고 자바 코드가 SQL에 끌려다니는 결합도는 내려갑니다.
1) 기존 DAO (JDBC / JdbcTemplate 혼합 포함)의 “구조적 문제”가 뭐였나?
문제 1: DAO 안에 SQL이 끼어 있음 (언어 2개가 한 파일에 섞임)
- DAO는 원래 “DB 접근 로직”을 담당하지만
- SQL 문자열이 자바 코드 한복판에 들어오면 코드가 복잡해지고 읽기 어려워짐
- 유지보수 시 “자바 수정 + SQL 수정”이 한 번에 얽혀서 수정 비용이 증가
문제 2: “자바 개발자가 SQL을 반드시 알아야 하는 구조”
- 실무에서는 팀 / 역할에 따라 SQL을 집중적으로 다루는 사람이 따로 있는 경우도 많고
- 최소한 “SQL을 바꾸고 싶을 때 자바 코드까지 뜯어야 하는 구조”는 협업에 불리함
결론: DAO의 응집도가 떨어지고 결합도가 높아짐
- DAO가 “SQL 문자열 관리”까지 떠안으면 책임이 비대해짐 → 응집도↓
- SQL 변경이 곧 자바 코드 변경으로 연결 → 결합도↑
2) MyBatis는 이 문제를 어떻게 해결하나?
MyBatis는 흔히 ORM 계열로 묶어 이야기하지만 수업 관점에서 핵심은 이렇게 보면 정확합니다.
- 자바 코드 (DAO)에서는 SQL을 직접 문자열로 안 씀
- 대신 SqlSession을 통해
- "네임스페이스.아이디"로 어떤 SQL을 실행할지 선택하고
- 파라미터로 DTO를 넘기면
- Mapper XML에 정의된 SQL이 실행됨
DAO는 다음 역할로 축소됩니다.
DAO의 역할
- “DB에 어떤 작업을 요청할지”만 표현
- 실제 SQL은 외부 (Mapper XML)가 책임짐
3) 실습 구조: 파일 / 폴더는 보통 이렇게 잡는다
수업 화면 / 메모 기준 구조는 아래처럼 가는 게 정석입니다.
- src/main/resources/mappers/Member.xml ← Mapper XML 모음
- src/main/java/.../MybatisMemberDAO.java ← SqlSession 사용하는 DAO
(“mappers”라는 폴더명은 관례적으로 많이 씁니다.)
4) 1단계: pom.xml에 MyBatis 스타터 추가
수업 메모대로 mybatis-spring-boot-starter를 추가합니다.
사용자 pom.xml에도 아래 의존성이 들어가 있는 상태였습니다.
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
참고: 이 스타터를 넣으면 Spring Boot가 MyBatis 기본 구성을 자동으로 잡아주고 SqlSession도 스프링 빈으로 주입 받을 수 있게 됩니다.
5) 2단계: Mapper XML 만들기 (Member.xml)
<mapper namespace="Member">
<insert id="insert" parameterType="memberDTO">
INSERT INTO MEMBER
VALUES(#{mid}, #{mpw}, #{mname}, 'USER')
</insert>
<update id="update" parameterType="memberDTO">
UPDATE MEMBER
SET MNAME = #{mname}
WHERE MID = #{mid}
</update>
<delete id="delete" parameterType="memberDTO">
DELETE FROM MEMBER
WHERE MID = #{mid}
</delete>
<select id="getOne" parameterType="memberDTO" resultType="memberDTO">
SELECT * FROM MEMBER
WHERE MID = #{mid}
AND MPW = #{mpw}
</select>
<select id="getList" resultType="memberDTO">
SELECT * FROM MEMBER
</select>
</mapper>
여기서 “필수 개념” 2가지
(1) namespace + id가 SQL의 “주소”가 된다
- 예: namespace="Member"
- 예: <insert id="insert"> ... </insert>
이러면 DAO에서 "Member.insert"로 찾아 쓸 수 있습니다.
(2) #{}는 DTO의 필드 (프로퍼티) 이름과 매핑된다
- #{mid} → DTO의 getMid()/setMid()에 대응
- #{mname} → getMname()/setMname() …
6) 3단계: DAO를 SqlSession 기반으로 바꾸기
@Repository
public class MybatisMemberDAO {
@Autowired
private SqlSession sqlSession;
private static final String NAMESPACE = "Member.";
public boolean insertMember(MemberDTO dto) {
if (sqlSession.insert(NAMESPACE + "insert", dto) <= 0) {
return false;
}
return true;
}
public boolean updateMember(MemberDTO dto) {
if (sqlSession.update(NAMESPACE + "update", dto) <= 0) {
return false;
}
return true;
}
public boolean deleteMember(MemberDTO dto) {
if (sqlSession.delete(NAMESPACE + "delete", dto) <= 0) {
return false;
}
return true;
}
public MemberDTO getMember(MemberDTO dto) {
return sqlSession.selectOne(NAMESPACE + "getOne", dto);
}
public List<MemberDTO> getMemberList(MemberDTO dto) {
return sqlSession.selectList(NAMESPACE + "getList");
}
}
그대로 정리하면
- insert / update / delete는 실행 결과로 “영향 받은 행 수 (int)”가 돌아옴
→ <= 0이면 실패로 판단하는 패턴이 흔함 - 조회는
- 단건: getOne
- 다건: getList
7) (중요) 메모 중 “틀리기 쉬운 / 설정이 필요한 부분” 정확히 짚기
(1) parameterType="memberDTO" / resultType="memberDTO"는 “그냥 쓰면” 안 될 수 있음
MyBatis에서 memberDTO처럼 짧은 이름을 쓰려면 보통 둘 중 하나가 필요합니다.
- typeAliases 설정을 해서 MemberDTO를 별칭으로 등록하거나
- 아예 XML에 풀패키지 클래스명을 적어야 합니다.
예) 가장 확실한 방식 (풀패키지 적기)
<insert id="insert" parameterType="com.example.biz.member.MemberDTO">
...
</insert>
또는 Spring Boot 설정에서 aliases 패키지를 지정:
mybatis.type-aliases-package=com.example.biz.member
별칭을 잡아두면 XML에서 MemberDTO 같은 짧은 이름으로도 쓸 수 있어요.
반대로 별칭 설정 없이 memberDTO라고 적으면 “클래스를 못 찾는다” 류의 에러가 날 수 있습니다.
(2) mapper 파일 위치를 Spring Boot에 알려줘야 함
“스프링에게 너 매퍼 가지고 있어라고 알려줘야 함”
이건 보통 아래 설정으로 합니다.
mybatis.mapper-locations=classpath:/mappers/*.xml
(3) getMemberList(MemberDTO dto)인데 실제로 dto를 안 쓰고 있음
현재 코드는:
public List<MemberDTO> getMemberList(MemberDTO dto){
return sqlSession.selectList(NAMESPACE+"getList");
}
- 필터 / 검색 조건이 없는 “전체조회”라면 DTO 파라미터 자체가 필요 없고
- 조건 조회로 확장할 계획이면 아래처럼 DTO를 넘기는 형태로 바꾸는 게 자연스럽습니다.
예시 (조건 전달 버전):
return sqlSession.selectList(NAMESPACE + "getList", dto);
그리고 XML에서 <select id="getList" parameterType="..."> 형태로 조건을 받도록 확장합니다.
수업 결론처럼 “모든 DAO를 다 바꾸지 말고 1~2개만” 한다면 지금은 전체조회 DAO는 파라미터를 없애서 깔끔하게 정리하는 쪽이 더 설득력 있습니다.
8) Before / After (포폴 / 면접 어필 포인트)
“비포-애프터 어필, 성장곡선”은 이렇게 정리하면 좋습니다.
Before (JDBC / JdbcTemplate DAO)
- DAO 안에 SQL이 섞여 있음 → 코드가 길고 복잡
- SQL 수정이 곧 자바 수정 → 변경 비용↑
- 협업 시 SQL / 자바 경계가 불명확
After (MyBatis)
- SQL은 Mapper XML로 분리 → DAO는 “호출 코드”만 남음
- Member.insert 같은 “SQL 주소 체계”로 관리 → 유지보수 용이
- 역할 분리가 명확해져 모델 파트 응집도↑, 결합도↓
MyBatis는 “자바 코드에서 SQL을 제거”해 DAO를 얇게 만들고 SQL 변경을 자바 컴파일 / 배포 리스크에서 분리해 유지보수를 쉽게 만드는 도구다.
'수업 복습' 카테고리의 다른 글
| 게시글 삭제 프로세스 정리 (0) | 2026.02.10 |
|---|---|
| BoardDAO를 MyBatis로 리팩토링 실습 과제 (0) | 2026.02.09 |
| JDBCTemplate + RowMapper (0) | 2026.01.28 |
| 스프링에서의 JDBC 정리 (0) | 2026.01.28 |
| Spring AOP를 어노테이션 (@)으로 전환하는 흐름 정리 (0) | 2026.01.27 |