자바 에 는 표준 스펙 , 상용 제품 , 오픈 소스 를 통틀 어서 사용 방법 과 형식 은 다르 지만 기능 과 목적 이 유사 한 기술 이 존재 한다 . 
 환경 과 상황 에 따라서 기술 이 바뀌 고 , 그 에 따라 다른 API 를 사용 하 고 다른 스타일 의 접근 방법 을 따라야 한다는 건 매우 피곤 한 일 이 다 . 
 스프링 이 어떻게 성격 이 비슷 한 여러 종류 의 기술 을 추상 화 하 고 이 를 일관 된 방법 으로 사용 할 수 있 도록 지원 하 는지 를 살펴본다 . 
 1 . 사용자 레벨 관리 기능 추가 
 UserDao 에 비즈니스 로직 을 추가 해 보 자 
 정기 적 으로 사용 자 의 활동 내역 을 참고 해서 레벨 을 조정 해 주 는 기능 
 사용 자 의 레벨 은 BASIC , SILVER , GOLD 세 가지 중 하나 이 다 . 
 사용자 가 처음 가입 하 면 BASIC 레벨 , 이후 활동 에 따라 한 단계 씩 업그레이드 된다 . 
 가입 후 50 회 이상 로그인 시 BASIC 레벨 에서 SILVER 레벨 이 된다 . 
 SILVER 레벨 이 면서 30 번 이상 추천 을 받 으면 GOLD 레벨 이 된다 . 
 사용자 레벨 의 변경 작업 은 일정 한 주기 를 가지 고 일괄 적 으로 진행 됨 , 변경 작업 전 에 는 조건 을 충족 하 더라도 레벨 의 변경 이 일어나 지 않 음 
 1 . 1 필드 추가 
 User 클래스 에 사용 자 의 레벨 을 저장 할 필드 를 추가 한다 . 
 DB : 각 레벨 을 코드 화 해서 숫자 로 넣 는다 . 범위 가 작 은 숫자 로 관리 하 면 DB 용량 도 만 히 차지 하 지 않 고 가볍 다 . 
 User : User 에 추가 할 프로 퍼티 타입 도 숫자 로 하 면 될까 ? 이건 별로 좋 지 않 다 . 의미 없 는 숫자 를 프로 퍼티 에 사용 하 면 타입 이 안 전하 지 않 아서 위험 할 수 있 기 때문 이 다 . 
 숫자 타입 을 직접 사용 하 는 것 보다 는 자바 5 이상 에서 제공 하 는 이 늄 ( enum ) 을 이용 하 는 게 안 전하 고 편리 하 다 . 
 Level 타입 의 변수 추가 및 로그인 횟수 와 추천 수도 추가 
 DB 의 유저 테이블 에 도 필드 를 추가 한다 . 
 새롭 게 추가 된 필드 에 맞 게 테스트 코드 를 수정 한다 . 
 테스트 를 먼저 수정 했으니 테스트 가 실패 한다 . 
 테스트 가 성공 하 도록 UserDaoJdbc 를 수정 한다 . 
 UserDaoJdbc 를 수정 하 는 과정 에서 실수 가 나오 더라도 , UserDaoTest 덕분 에 빠르 게 버그 를 찾 을 수 있 다 . 
 빠르 게 실행 가능 한 포괄 적 인 테스트 를 만들 어 두 면 이렇게 기능 의 추가 나 수정 이 일어날 때 그 위력 을 발휘 한다 . 
 1 . 2 사용자 수정 기능 추가 
 수정 할 정보 가 담긴 User 오브젝트 를 전달 하 면 id 를 참고 해서 사용 자 를 찾 아 필드 정보 를 UPDATE 문 을 이용 해 모두 변경 해 주 는 메소드 를 하나 추가 한다 . 
 픽스 처 오브젝트 를 하나 등록 한다 . 
 id 를 제외 한 필드 를 변경 한 뒤 에 update ( ) 를 호출 한다 . 
 다시 id 로 조회 해서 가져온 오브젝트 와 수정 한 픽스 처 오브젝트 를 비교 한다 . 
 dao 변수 의 타입 인 UserDao 인터페이스 에 update ( ) 메소드 가 없 다는 컴파일 에러 가 날 것 이 다 . 따라서 update ( ) 메소드 를 추가 한다 . 
 UserDao 인터페이스 에 update ( ) 를 추가 하 고 나 면 이번 엔 UserDaoJdbc 에서 메소드 를 구현 하 지 않 았 다고 에러 가 날 것 이 다 . IDE 에서 지원 하 는 , 구현 되 지 않 은 메소드 를 자동 추가 히 는 기능 을 사용 하 면 편리 하 다 . 
 IDE 의 자동 수정 기능 과 테스트 코드 작성 
 대부분 의 자바 IDE 에 는 컴파일 에러 에 대한 자동 수정 기능 이 있 다 . 
 테스트 를 먼저 만들 면 , 아직 준비 되 지 않 은 클래스 나 메소드 를 테스트 코드 내 에서 먼저 사용 하 는 경우 가 있 다 . 이 경우 IDE 의 자바 코드 에디터 에서 에러 가 표시 되 면 자동 고침 기능 을 이용해 클래스 나 메소드 를 생성 하 도록 만들 면 매우 편리 하 다 . 
 수정 테스트 보안 
 update 의 WHERE 절 에 대한 검증 이 되 진 않 는다 . 실제로 update 문 에서 WHERE 절 을 빼 고 테스트 를 돌려 보 면 테스트 가 성공 한다 . 이 는 테스트 에 결함 이 있 다는 증거 이 다 . 
 첫 번 째 방법 은 JdbcTemplate 의 update ( ) 가 돌려주 는 리턴 값 을 확인 하 는 것 이 다 . update ( ) 는 UPDATE 나 DELETE 같이 테이블 의 내용 에 영향 을 주 는 SQL 을 실행 하 면 영향 받 은 로우 의 개수 를 돌려준다 . 
 두 번 째 방법 은 테스트 를 보강 해서 원 하 는 사용 자 외 의 정보 는 변경 되 지 않 았 음 을 직접 확인 하 는 것 이 다 . 
 TDD 의 3 가지 원칙 
 1 . 3 UserService . upgradeLevels ( ) 
 사용 자 관리 로직 은 어디 에 두 는 게 좋 을까 ? 
 UserDaoJdbc 는 적당 하 지 않 다 . DAO 는 데이터 를 어떻게 가져오 고 조작 할지 를 다루 는 곳 이 지 비즈니스 로직 을 두 는 곳 이 아니 다 . 
 사용자 관리 비즈니스 로직 을 담 을 UserService 클래스 를 하나 추가 하 자 . 또한 이 를 테스트 하 기 위한 UserServiceTest 도 추가 하 자 
 bean ( ) 테스트 : userService 빈 이 생성 돼서 userService 변수 에 주입 되 는지 테스트 
 사용 자 관리 기능 추가 
 / / BASIC 레벨 업그레이드 
 / / SILVER 레벨 업그레이드 
 / / GOLD 레벨 업그레이드 는 일어나 지 않 는다 . 
 changed 플래그 를 확인 해서 레벨 변경 이 있 는 경우 에 만 UserDao 의 update ( ) 를 이용 해 수정 내용 을 DB 에 반영 한다 . 
 마찬가지 로 위 의 로직 을 검증 할 수 있 는 테스트 코드 를 추가 한다 . 
 적어도 가능 한 모든 조건 을 하나 씩 은 확인 해 봐야 한다 . 
 테스트 픽스 처 추가 
 new User ( " bumjin ", " 박범진 ", " p 1 ", Level . BASIC , 49 , 0 ) , 
 new User ( " joytouch ", " 강명 성 ", " p 2 ", Level . BASIC , 50 , 0 ) , 
 new User ( " erwins ", " 신승 한 ", " p 3 ", Level . SILVER , 60 , 29 ) , 
 new User ( " madnite 1 ", " 이상호 ", " p 4 ", Level . SILVER , 60 , 30 ) , 
 new User ( " green ", " 오민규 ", " p 5 ", Level . GOLD , 100 , 100 ) 
 테스트 코드 추가 : 다섯 가지 종류 의 사용 자 정보 를 저장 한 뒤 에 upgradeLevels ( ) 메소드 를 실행 한다 . 업그레이드 작업 이 끝나 면 사용 자 정보 를 하나 씩 가져와 레벨 의 변경 여부 를 확인 한다 . 
 1 . 4 UserService . add ( ) 
 사용 자 관리 비즈니스 로직 에서 대부분 은 구현 했 지만 아직 하나 가 남 았 다 . 처음 가입 하 는 사용 자 는 기본 적 으로 BASIC 레벨 이 어야 한다는 부분 이 다 . 
 1 . 5 코드 개선 
 코드 에 중복 된 부분 은 없 는가 ? 
 코드 가 무엇 을 하 는 것 인지 이해 하 기 불편 하 지 않 은가 ? 
 코드 가 자신 이 있 어야 할 자리 에 있 는가 ? 
 앞 으로 변경 이 일어난다면 어떤 것 이 있 을 수 있 고 , 그 변화 에 쉽 게 대응 할 수 있 게 작성 되 어 있 는가 ? 
 upgradeLevels ( ) 메소드 코드 의 문제점 
 for 루프 속 에 들 어 있 는 if / else if / else 블록 들 이 읽 기 불편 하 다 . 
 레벨 의 변화 단계 와 업그레이드 조건 , 조건 이 충족 됐 을 때 해야 할 작업 이 한 데 섞여 있 다 . 
 플래그 를 두 고 이 를 변경 하 고 마지막 에 이 를 확인 해서 업데이트 를 진행 하 는 방법 도 그리 깔끔 해 보이 지 않 는다 . 
 성격 이 다른 여러 가지 로직 이 한 데 섞여 있 다 . 
 새로운 레벨 이 추가 된다면 ? 
 Level Enum 을 수정 
 upgradeLevels ( ) 메소드 수정 - if 조건식 과 블럭 을 추가 
 업그레이드 조건 이 복잡 해 지 거나 업그레이드 작업 에서 하 는 일 이 단지 level 필드 를 변경 하 는 수준 이상 이 라면 upgradeLevels ( ) 메소드 는 점점 길 어 지 고 복잡 지 며 , 갈수록 이해 하 고 관리 하 기 가 힘들 어 진다 . 
 upgradeLevels ( ) 리팩토링 
 추상 적 인 레벨 에서 로직 을 작성 해 보 자 - upgradeLevels ( ) 메소드 는 자주 변경 될 가능 성 이 있 는 구체 적 인 내용 이 추상 적 인 로직 의 흐름 과 함께 섞여 있 다 . 
 위 코드 는 이렇게 읽 을 수 있 다 . 모든 사용자 정보 를 가져와 한 명 씩 업그레이드 가 가능 한지 확인 하 고 , 가능 하 면 업그레이드 를 한다 . 
 canUpgradeLevel ( ) 메소드 : 주어진 user 에 대해 업그레이드 가 가능 하 면 true , 가능 하 지 않 으면 false 를 리턴 하 면 된다 . 
 새로운 레벨 이 추가 됐 지만 업그레이드 로직 을 추가 하 지 않 았 다면 , 예외 가 발생 할 테 니 쉽 게 확인 할 수 있 다 . 
 upgradeLevel ( ) 메소드 : 레벨 업그레이드 를 위한 작업 은 사용 자 의 레벨 을 다음 단계 로 바꿔 주 는 것 과 변경 사항 을 DB 에 업데이트 해 주 는 것 이 다 . 
 다음 단계 가 무엇 인가 하 는 로직 과 그때 사용 자 오브젝트 의 level 필드 를 변경 해 준다는 로직 이 함께 있 다 . 
 예외 상황 에 대한 처리 가 없 다 . 
 레벨 이 늘어난다면 if 문 이 점점 길 어 질 것 이 다 . 
 UpgradeLevel 메소드 를 분리 해 보 자 . 
 레벨 의 순서 와 디 음 단계 레벨 이 무엇 인지 를 결정 하 는 일 은 Level 에게 맡기 자 . ( Level 의 순서 를 UserService 에 담 아 둘 이유 가 없 다 ) 
 Level 이 늄 에 next 라는 다음 단계 레벨 정보 를 담 을 수 있 도록 필드 를 추가 한다 . 
 GOLD ( 3 , null ) , 
 SILVER ( 2 , GOLD ) , 
 사용 자 정보 가 바뀌 는 부분 을 UserService 메소드 에서 User 로 옮겨 보 자 . 
 User 의 내부 정보 가 변경 되 는 것 은 UserService 보다 는 User 가 스스로 다루 는 게 적절 하 다 . 
 User 에게 레벨 업그레이드 를 해야 하 니 정보 를 변경 하 라고 요청 하 는 편 이 낫 다 . 
 아래 와 같 은 메소드 를 User 클래스 에 추가 한다 . 
 User 에 업그레이드 작업 을 담당 하 는 독립 적 인 메소드 를 두 고 사용 할 경우 , 업그레이드 시 기타 정보 도 변경 이 필요 해 지 면 그 장점 이 무엇 인지 알 수 있 을 것 이 다 . 
 가장 최근 에 레벨 을 변경 할 일자 를 기록 해야 한다면 ? 
 UserService 는 User 오브젝트 에게 알 아서 업그레이드 에 필요 한 작업 을 수행 하 라고 요청 만 해 주 면 된다 . 
 객체 지향 적 인 코드 는 다른 오브젝트 의 데이터 를 가져와서 작업 하 는 대신 데이터 를 갖 고 있 는 다른 오브젝트 에게 작업 을 해 달 라고 요청 한다 . 오브젝트 에게 데이터 를 요구 하 지 말 고 작업 을 요청 하 라는 것 이 객체 지향 프로그래밍 의 가장 기본 이 되 는 원리 이 기 도 하 다 . 
 UserService -> User : 레벨 업그레이드 작업 을 해 달 라 
 User -> Level : 다음 레벨 이 무엇 인지 알려 달 라 
 User 에 로직 을 추가 했으니 이 에 대한 테스트 코드 도 추가 하 자 
 User 클래스 에 대한 테스트 는 굳이 스프링 의 테스트 컨텍스트 를 사용 하 지 않 아도 된다 . 
 checkLevel ( ) : Level 이 갖 고 있 어야 할 다음 레벨 이 무엇 인가 하 는 정보 를 테스트 에 직접 넣 어 둘 이유 가 없 다 . 레벨 이 추가 되 거나 변경 되 면 테스트 도 따라서 수정 해야 하 니 번거 롭 다 . 
 따라서 아래 와 같이 수정 하 자 . 아래 와 같이 수정 하 면 다음 과 같 은 장점 이 있 다 . 
 테스트 의 의도 가 더 분명히 드러난다 . 개선 한 upgradeLevels ( ) 테스트 는 각 사용자 에 대해 업그레이드 를 확인 하 려는 것 인지 아닌지 가 좀 더 이해 하 기 쉽 게 true , false 로 나타나 있 어서 보 기 좋 다 . 
 새로운 Level 이 추가 되 더라도 테스트 가 수정 될 필요 가 없 다 . 
 상수 값 중복 제거 : 기준 이 되 는 로그인 횟수 , 추천 횟수 
 정수 형 상수 로 변경 
 2 . 트랙 잭 션 서비스 추상 화 
 사용 자 레벨 조정 작업 은 중간 에 문제 가 발생 해서 작업 이 중단 된다면 그 때 까지 진행 된 변경 작업 도 모두 취소 시키 도 록 결정 했 다 . 
 2 . 1 모 아니 면 도 
 모든 사용자 에 대해 업그레이드 작업 을 진행 하 다가 중간 에 예외 가 발생 해서 작업 이 중단 된다면 어떻게 될까 ? 테스트로 한 번 확인 해 보 자 
 테스트 용 으로 특별히 만든 UserService 의 대역 을 사용 하 는 방법 이 좋 다 . 
 업데이트 중간 에 강제 로 예외 가 발생 하 도록 동작 하 는 TestUserService 를 만든 다 . 
 테스트 를 해 보 니 업 데이터 중간 에 예외 가 발생 하 더라도 , 이전 에 업데이트 된 데이터 들 이 롤백 되 지 않 았 다 . 
 트랜잭션 문제 다 . 모든 사용자 의 레벨 을 업그레이드 하 는 작업 인 upgradeLevels ( ) 메소드 가 하나 의 트랜잭션 안 에서 동작 하 지 않 았 기 때문 이 다 . 
 중간 에 예외 가 발생 해서 작업 을 완료 할 수 없 다면 아예 작업 이 시작 되 지 않 은 것 처럼 초기 상태 로 돌려 뇌 야 한다 . 
 2 . 2 트랜잭션 경계 설정 
 하나 의 SQL 명령 을 처리 하 는 경우 는 DB 가 트랜잭션 을 보장 해 준다 . 
 트랜잭션 롤백 : 하나 의 트랜잭션 안 에서 여러 개 의 SQL 문 을 실행 할 때 , 모든 SQL 문 을 실행 하 기 전 에 문제 가 발생 할 경우 에 는 앞 에서 처리 한 SQL 작업 도 취소 시켜야 한다 . 이런 취소 작업 을 트랜잭션 롤백 이 라 한다 . 
 트랜잭션 커밋 : 여러 개 의 SQL 을 하나 의 트랜잭션 으로 처리 하 는 경우 에 모든 SQL 수행 작업 이 다 성공 적 으로 마무리 됐 다고 DB 에 알려 줘서 작업 을 확정 시켜야 한다 . 
 JDBC 트랜잭션 의 트랜잭션 경계 설정 
 트랜잭션 경계 : 애플리케이션 내 에서 트랜잭션 이 시작 되 고 끝 나 는 위치 
 트랜잭션 경계 설정 : 트랜잭션 이 시작 하 는 곳 과 끝 나 는 곳 을 지정 하 는 것 
 트랙 잭 션 시작 : Connection . setAutoCommit ( false ) 
 JDBC 에서 트랜잭션 을 시작 하 려면 자동 커밋 옵션 을 false 로 만들 어 주 면 된다 . JDBC 의 기본 설정 은 DB 작업 을 수행 한 직후 에 자동 으로 커 밋 이 되 도록 되 어 있 다 . 
 JDBC 에서 는 이 기능 을 false 로 설정 해 주 면 새로운 트랜잭션 이 시작 되 게 만들 수 있 다 . 트랜잭션 이 한 번 시작 되 면 commit ( ) 또는 rollback ( ) 메소드 가 호출 될 때 까지 의 작업 이 하나 의 트랜잭션 으로 묶인 다 . 
 트랜잭션 완료 
 커밋 : Connection . commit ( ) 
 롤백 : Connection . rollback ( ) 
 JDBC 의 트랜잭션 은 하나 의 Connection 을 가져와 사용 하 다가 닫 는 사이 에서 일어난다 . 트랜잭션 의 시작 과 종료 는 Connection 오브젝트 를 통해 이뤄지기 때문 이 다 . 
 트랜잭션 의 경계 는 하나 의 Connection 이 만들 어 지 고 닫히 는 범위 안 에 존재 한다는 점 도 기억 해 두 자 . 이렇게 하나 의 DB 커넥션 안 에서 만들 어 지 는 트랜잭션 을 로컬 트랜잭션 이 라고 도 한다 . 
 JdbcTemplate 의 메소드 를 사용 히 는 UserDao 는 각 메소드 마다 하나 씩 의 독립 적 인 트랜잭션 으로 실행 될 수 밖 에 없 다 . 
 UserDao 는 update ( ) 메소드 가 호출 될 때 마다 JdbcTemplate 을 통해 매번 새로운 DB 커넥션 과 트랜잭션 을 만들 어 사용 한다 . 
 데이터 액세스 코드 를 DAO 로 만들 어서 분리 해 놓 았 을 경우 에 는 이 처럼 DAO 메소드 를 호출 할 때 마다 하나 의 새로운 트랜잭션 이 만들 어 지 는 구조 가 될 수 밖에 없 다 . 
 DAO 메소드 에서 DB 커넥션 을 매번 만들 기 때문 에 어쩔 수 없이 나타나 는 결과 다 . 
 어떤 일련 의 작업 이 하나 의 트랜잭션 으로 묶이 려면 그 작업 이 진행 되 는 동안 DB 커넥션 도 하나 만 사용 돼야 한다 . 트랜잭션 은 Connection 오브젝트 안 에서 만들 어 지 기 때문 이 다 . 
 비즈니스 로직 내 의 트랜잭션 경계 설정 
 UserService 와 UserDao 를 그대로 둔 채 로 트랜잭션 을 적용 하 려면 결국 트랜잭션 의 경계 설정 작업 을 UserService 쪽 으로 가져와야 한다 . 
 ( 1 ) DB Connection 생성 
 ( 2 ) 트랜잭션 시작 
 ( 3 ) DAO 메소드 호출 
 ( 4 ) 트랜잭션 커밋 
 ( 5 ) 트랜잭션 롤백 
 ( 6 ) DB Connection 종료 
 트랜잭션 경계 를 upgradeLevels ( ) 메소드 안 에 두 려면 DB 커넥션 도 이 메소드 안 에서 만들 고 , 종료 시킬 필요 가 있 다 . 
 여기 서 생성 된 Connection 오브젝트 를 가지 고 데이터 액세스 작업 을 진행 하 는 코드 는 UserDao 의 update ( ) 메소드 안 에 있 어야 한다 . 트랜잭션 때문 에 DB 커넥션 과 트랜잭션 관련 코드 는 어쩔 수 없이 UserService 로 가져왔 지만 순수 한 데이터 액세스 로직 은 UserDao 에 둬야 하 기 때문 이 다 . 
 UserService 에서 만든 Connection 오브젝트 를 UserDao 에서 사용 하 려면 DAO 메소드 를 호출 할 때 마다 Connection 오브젝트 를 파라미터 로 전달 해 줘야 한다 . 
 이렇게 Connection 오브젝트 를 전달 해서 사용 하 면 UserService 의 upgradeLevels ( ) 안 에서 시작 한 트랜잭션 에 UserDao 의 메소드 들 도 참여 하 게 할 수 있 다 . 
 첫 번 째 : DB 커넥션 을 비롯 한 리소스 의 깔끔 한 처리 를 가능 하 게 했 던 JdbcTemplate 을 더 이상 활용 할 수 없 다는 점 이 다 . 
 두 번 째 : DAO 의 메소드 와 비즈니스 로직 을 담 고 있 는 UserService 의 메소드 에 Connection 파라미터 가 추가 돼야 한다는 점 이 다 . 
 세 번 째 : Connection 파라미터 가 UserDao 인터페이스 메소드 에 추가 되 면 UserDao 는 더 이상 데이터 액세스 기술 에 독립 적 일 수 가 없 다는 점 이 다 . JPA 나 하이버 네이트 로 UserDao 의 구현 방식 을 변경 하 려고 하 면 Connection 대신 EntityManager 나 Session 오브젝트 를 UserDao 메소드 가 전달 받 도록 해야 한다 . 
 네 번 째 : DAO 메소드 에 Connection 파라미터 를 받 게 하 면 테스트 코드 에 도 영향 을 미친다 . 
 2 . 3 트랜잭션 동기 화 
 트랜잭션 동기 화 방식 : 트랜잭션 동기 화 란 UserService 에서 트랜잭션 을 시작 하 기 위해 만든 Connection 오브젝트 를 특별 한 저 장소 에 보관 해 두 고 , 이후 에 호출 되 는 DAO 의 메소드 에서 는 저장 된 Connection 을 가져다가 사용 하 게 하 는 것 이 다 . 
 아래 는 트랜잭션 동기 화 방식 을 사용 한 경우 의 작업 흐름 을 보여준다 . 
 UserService 는 커넥션 을 생성 한다 . 
 Connection 을 트랜잭션 동기 화 저 장소 에 저장 한다 . 그리고 Connection 의 setAutoCommit ( false ) 를 호출 해 트랜잭션 을 시작 시킨 후 에 본격 적 으로 DAO 의 기능 을 이용 하 기 시작 한다 . 
 첫 번 째 update 가 호출 된다 . 
 update 메소드 내부 에서 이용 하 고 있 는 JdbcTemplate 메소드 에서 는 가장 먼저 트랜잭션 동기 화 저 장소 에 현재 시작 된 트랜잭션 을 가진 Connection 오브젝트 가 존재 하 는지 확인 한다 . 저장 된 Connection 오브젝트 가 있 다면 이 를 가져온다 . 
 가져온 Connection 을 이용 해 SQL 을 실행 한다 . 트랜잭션 동기 화 저 장소 에서 커넥션 을 가져왔 을 때 는 JdbcTemplate 은 Connnection 을 닫 지 않 은 채 로 작업 을 마친다 . 따라서 여전히 Connenction 은 열려 있 고 , 트랜잭션 은 진행 중 인 상태 이 다 . 
 3 ~ 5 과정 을 모든 update 메소드 가 호출 될 때 마다 반복 한다 . 
 트랜잭션 내 의 모든 작업 이 정상 적 으로 끝났으면 UserService 는 Connection 의 commit ( ) 을 호출 해서 트랜잭션 을 완료 시킨다 . 
 마지막 으로 트랜잭션 저 장소 가 더 이상 Connection 오브젝트 를 저장 해 두 지 않 도록 이 를 제거 한다 . 
 트랜잭션 동기 화 기법 을 사용 하 면 파라미터 를 통해 일일이 Connection 오브젝트 를 전달 할 필요 가 없 어 진다 . 
 // DB 커넥션 을 생성 하 고 트랜잭션 을 시작 한다 . 
 / / 이후 의 DAO 작업 은 모두 여기 서 시작 한 트랜잭션 안 에서 진행 된다 . 
 / / 예외 가 발생 하 면 롤백 한다 . 
 / / 스프링 유틸리티 메소드 를 이용 해 DB 커넥션 을 안 전하 게 닫 는다 . 
 먼저 T r a n s a c t ionSynchronizationManager 클래스 를 이용 해 트랜잭션 동기 화 작업 을 초기 화 하 도록 요청 한다 . 
 DataSourceUtils 에서 제공 히 는 getConnection ( ) 메소드 를 통해 DB 커넥션 을 생성 한다 . 스프링 이 제공 하 는 유틸리티 메 소드 를 쓰 는 이유 는 DataSourceUtils 의 getConnection ( ) 메소드 는 Connection 오브젝트 를 생성 해 줄 뿐 만 아니 라 트랜잭션 동기 화 에 사용 하 도록 저 장소 에 바인딩 해 주 기 때문 이 다 . 
 동기 화 준비 가 됐 으면 트랜잭션 을 시작 하 고 DAO 의 메소드 를 사용 하 는 트랜잭션 내 의 작업 을 진행 한다 . 
 작업 을 정상 적 으로 마치 면 트랜잭션 을 커 밋 해 준다 . 
 스프링 유틸리티 메소드 의 도움 을 받 아 커넥션 을 닫 고 트랜잭션 동기 화 를 마치 도록 요청 하 면 된다 . 
 JdbcTemplate 은 영리 하 게 동작 하 도록 설계 되 어 있 다 . 만약 미리 생성 돼서 트랜잭션 동기 화 저 장소 에 등록 된 DB 커넥션 이나 트랜잭션 이 없 는 경우 에 는 JdbcTemplate 이 직접 DB 커넥션 을 만들 고 트랜잭션 을 시작 해서 JDBC 작업 을 진행 한다 . 
 반면 에 upgradeLevels ( ) 메소드 에서 처럼 트랜잭션 동기 화 를 시작 해 놓 았 다면 그때 부터 실행 되 는 JdbcTemplate 의 메소드 에서 는 직접 DB 커넥션 을 만드 는 대신 트랜잭션 동기 화 저 장소 에 들 어 있 는 DB 커넥션 을 가져와서 사용 한다 . 이 를 통해 이미 시작 된 트랙 잭 션 에 참여 하 는 것 이 다 . 
 덕분 에 트랜잭션 적용 여부 에 맞 쳐 Dao 코드 를 수정 할 필요 가 없 다 . 
 2 . 4 트랜잭션 서비스 추상 화 
 두 개 이상 의 DB 로 의 작업 을 하나 의 트랜잭션 으로 만드 는 건 JDBC 의 Connection 을 이용 한 트랜잭션 방식 인 로컬 트랜잭션 으로 는 불 가능 하 다 . 
 두 개 이상 의 DB 작업 을 하나 의 트랜잭션 으로 처리 하 기 위해서 는 각 DB 와 독립 적 으로 만들 어 지 는 Connection 을 통해서 가 아니 라 , 별도 의 트랜잭션 관리자 를 통해 트랜잭션 을 관리 히 는 글로벌 트랜잭션 방식 을 사용 해야 한다 . 
 자바 는 JDBC 외 에 이런 글로벌 트랜잭션 을 지원 하 는 트랜잭션 매니저 를 지원 하 기 위한 API 인 JTA 를 제공 하 고 있 다 . 
 하나 이상 의 DB 가 참여 하 는 트랜잭션 을 만들 려면 JTA 를 사용 해야 한다는 사실 만 기억 해 두 자 . 
 문제 는 JDBC 로컬 트랜잭션 을 JTA 를 이용 하 는 글로벌 트랜잭션 으로 바꾸 려면 UserService 의 코드 를 수정 해야 한다는 점 이 다 . 
 UserService 는 자신 의 로직 이 바뀌 지 않 았 음 에 도 기술 환경 에 따라서 코드 가 바뀌 는 코드 가 돼 버리 고 말 았 다 . 
 하이버 네이트 를 이용 한 트랜잭션 관리 코드 는 JDBC 나 JTA 의 코드 와 는 또 다르 다는 것 이 다 . 하이버 네이트 는 Connection 을 직접 사용 하 지 않 고 Session 이 라는 것 을 사용 하 고 , 독자 적 인 트랜잭션 관리 API 를 사용 한다 . 
 UserService 는 UserDao 인터페이스 에 만 의존 하 는 구조 였 다 . 
 구현 기술 이 JDBC 에서 하이버 네이트 나 여타 기술 로 바뀌 더라도 UserService 코드 는 영향 을 받 지 않 았 다 . 
 문제 는 JDBC 에 종속 적 인 Connection 을 이용 한 트랜잭션 코드 가 UserService 에 등장 하 면서 부터 UserService 는 UserDaoJdbc 에 간접 적 으로 의존 하 는 코드 가 돼버렸 다는 점 이 다 . 
 다행히 도 트랜잭션 의 경계 설정 을 담당 하 는 코드 는 일정 한 패턴 을 갖 는 유사 한 구조 다 . 이렇게 여러 기술 의 사용 방법 에 공통점 이 있 다면 추상화 를 생각 해 볼 수 있 다 . 추상 화 란 하위 시스템 의 공통점 뽑아내 서 분리 시키 는 것 을 말 한다 . 그렇게 하 면 하위 시스템 이 어떤 것 인지 알 지 못해도 또는 하위 시스템 이 바뀌 더라도 일관 된 방법 으로 접근 할 수 가 있 다 . 
 DB 에서 제공 하 는 DB 클라이언트 라이브러리 와 API 는 서로 전혀 호환 이 되 지 않 는 독자 적 인 방식 으로 만들 어 져 있 다 . 하지만 모두 SQL 을 이용 하 는 방식 이 라는 공통점 이 있 다 . 이 공통점 을 뽑아내 추상 화 한 것 이 JDBC 다 . JDBC 라는 추상 화 기술 이 있 기 때문 에 자바 의 DB 프로그램 개발자 는 DB 의 종류 에 상관없이 일관 된 방법 으로 데이터 액세스 코드 를 작성 할 수 가 있 다 . 
 공통 적 인 특징 을 모아서 추상 화 된 트랜잭션 관리 계층 을 만들 수 있 다 . 그리고 애플리케이션 코드 에서 는 트랜잭션 추상 계층 이 제공 히 는 API 를 이용 해 트랜잭션 을 이용 하 게 만들 어 준다면 특정 기술 에 종속 되 지 않 는 트랜잭션 경계 설정 코드 를 만들 수 있 을 것 이 다 . 
 스프링 이 제공 하 는 트랜잭션 경계 설정 을 위한 추상 인터페이스 는 P latformTransactionManager 다 . JDBC 의 로컬 트랜잭션 을 이용 한다면 P latformTransactionManager 를 구현 한 D a t aSourceTransactionManager 를 사용 하 면 된다 . 
 P latformTransactionManager 에서 는 트랜잭션 을 가져오 는 요청 인 getTransaction ( ) 메소드 를 호출 하 기 만 하 면 된다 . 필요 에 따라 트랜잭션 매니저 가 DB 커넥션 을 가져오 는 작업 도 같이 수행 해 주 기 때문 이 다 . 여기 서 트랜잭션 을 가져온다는 것 은 일단 트랜잭션 을 시작 한다는 의미 라고 생각 하 자 . 
 파라미터 로 D e f aultTransactionDefinition 오브젝트 는 트랜잭션 에 대한 속성 을 담 고 있 다 . 
 스프링 의 트랜잭션 추상 화 기술 은 앞 에서 적용 해 봤 던 트랜잭션 동기 화 를 사용 한다 . P latformTransactionManager 로 시작 한 트랜잭션 은 트랜잭션 동기 화 저 장소 에 저장 된다 . P latformTransactionManager 를 구현 한 D a t aSourceTransactionManager 오브젝트 는 JdbcTemplate 에서 사용 될 수 있 는 방식 으로 트랜잭션 을 관리 해 준다 . 따라서 P latformTransactionManager 를 통해 시작 한 트랜잭션 은 UserDao 의 JdbcTemplate 안 에서 사용 된다 . 
 다른 기술 을 사용 해야 한다면 ? 코드 는 변경 할 필요 없이 P latformTransactionManager 구현 체 만 변경 해 주 면 된다 . 
 P latformTransactionManager 를 DI 받 도록 수정 한다 . 
 DAO 를 하이버 네이트 나 JPA 등 을 사용 하 도록 수정 했 다면 그 에 맞 게 transactionManager 의 클래스 만 변경 해 주 면 된다 
 3 . 서비스 추상 화 와 단일 책임 원칙 
 기술 과 서비스 에 대한 추상 화 기법 을 이용 하 면 특정 기술 환경 에 종속 되 지 않 는 포터블 한 코드 를 만들 수 있 다 . 
 수평 적 분리 : 같 은 애플리케이션 로직 을 담 은 코드 지만 내용 에 따라 분리 했 다 . 같 은 계층 에서 수평 적 인 분리 라고 볼 수 있 다 . 
 UserService 와 UserDao 
 아래 그림 은 지금 까지 만들 어 진 사용 자 관리 모듈 의 의존 관계 를 나타낸다 . 
 애플리케이션 계층 : UserService , UserDao 
 UserDao 와 DB 연결 기술 은 결합 도 가 낮 다 . 왜냐하면 DataSource 인터페이스 에 만 의존 하 기 때문 이 다 . 
 마찬가지 로 UserService 는 트랜잭션 기술 과 도 스프링 이 제공 하 는 P latformTransactionManager 인터페이스 를 통한 추상 화 계층 을 사이 에 두 고 사용 하 게 했 기 때문 에 , 구체 적 인 트랜잭션 기술 에 독립 적 인 코드 가 됐 다 . 
 애플리케이션 로직 의 종류 에 따른 수평 적 인 구분 이 든 , 로직 과 기술 이 라는 수직 적 인 구분 이 든 모두 결합 도 가 낮 으며 서로 영향 을 주 지 않 고 자유 롭 게 확장 될 수 있 는 구조 를 만들 수 있 는 데 는 스프링 의 DI 가 중요 한 역할 을 하 고 있 다 . DI 의 가치 는 이렇게 관심 , 책임 , 성격 이 다른 코드 를 깔끔 하 게 분리 하 는데 있 다 . 
 단일 책임 원칙 은 하나 의 모듈 은 한 가지 책임 을 가져야 한다는 의미 다 . 하나 의 모듈 이 바뀌 는 이유 는 한 가지 여야 한다고 설명 할 수 도 있 다 . 변경 의 이유 가 2 가지 이상 이 라면 단일 책임 의 원칙 을 지키 지 못한 것 이 다 . 
 단일 책임 원칙 을 잘 지키 고 있 다면 , 어떤 변경 이 필요 할 때 수정 대상 이 명확 해진다 . 
 적절 하 게 책임 과 관심 이 다른 코드 를 분리 하 고 , 서로 영향 을 주 지 않 도록 다양 한 추상 화 기법 을 도입 하 고 , 애플리케이션 로직 과 기술 / 환경 을 분리 하 는 등 의 작업 은 갈수록 복잡 해 지 는 엔터 프라이즈 애플리케이션 에 는 반드시 필요 하 다 . 이 를 위한 핵심 적 인 도구 가 바로 DI 이 다 . 
 스프링 의 의존 관계 주입 기술 인 DI 는 모든 스프링 기술 의 기반 이 되 는 핵심 엔진 이 자 원리 이 며 , 스프링 이 지지 하 고 지원 하 는 , 좋 은 설계 와 코드 를 만드 는 모든 과정 에서 사용 되 는 가장 중요 한 도구 다 . 
 4 . 메일 서비스 추상화 
 1 . 메일 기능 테스트 의 어려운 점 
 실제 메일 서버 에 메일 전송 요청 을 보낸다면 , 테스트 로 인해 메일 서버 에 부담 을 줄 수 있 다 . 
 테스트 용 으로 따로 준비 된 메일 서버 를 이용 하 는 방법 은 어떨까 ? 운영 중 인 메일 서버 에 부담 을 주 지 않 는다는 점 에서 는 분명히 나 은 방법 이 다 . 
 테스트 용 메일 서버 는 메일 전송 요청 은 받 지만 실제로 메일 을 보내 진 않 는다 . 
 테스트 시 에 는 테스트 용 메일 서버 로 메일 전송 요청 을 보냈 는지 만 확인 한다 . 
 JavaMail API 를 통해 요청 이 들어간다는 보장 만 있 다면 굳이 테스트 할 때 마다 JavaMail 을 직접 구동 시킬 필요 가 없 지 않 을까 ? 
 운영 시 : JavaMail 을 직접 이용 해서 동작 하 도록 한다 . 
 테스 트시 : 테스트 용 JavaMail 을 이용 해서 특정 메소드 가 호출 되 는지 여부 만 확인 한다 . 
 2 . 테스트 를 위한 서비스 추상 화 
 JavaMail 의 API 는 이 방법 을 적용 할 수 없 다 . JavaMail 의 핵심 API 에 는 DataSource 처럼 인터페이스 로 만들 어 져서 구현 을 바꿀 수 있 는 게 없 다 . 
 대부분 이 인터페이스 가 아니 라 그냥 클 래스 이 며 , private 생성자 를 가진 클래스 도 존재 한다 . 
 JavaMail 의 구현 을 테스트 용 으로 바꿔치기 하 는 건 불 가능 하 다고 볼 수 밖에 없 다 . 
 JavaMail 처럼 테스트 하 기 힘든 구조 인 API 를 테스트 하 기 좋 게 만드 는 방법 이 있 다 . 트랜잭션 을 적용 하 면서 살펴봤 던 서비스 추상화 를 적용 하 면 된다 . 
 스프링 은 JavaMail 에 대한 추상 화 기능 을 제공 하 고 있 다 . 
 JavaMailSenderlmpl 는 JavaMail 클래스 를 사용 해 메일 발송 기능 을 제공 한다 . 실제 운영 환경 에서 는 JavaMailSenderlmpl 을 DI 받 아서 동작 하 도록 한다 . 
 테스트 용 MailSender 구현 체 를 만든 다 . 테스트 가 수행 될 때 에 는 JavaMail 을 사용 해서 메일 을 보낼 필요 가 없 다 . 
 테스트 용 설정 파일 은 JavaMailSenderImpl 대신 DummyMailSender 를 빈 으로 등록 한다 . 
 그러 면 UserService 코드 는 변경 하 지 않 고 테스트 시 에 는 메일 을 보내 지 않 도록 할 수 있 다 . 
 테스트 서비스 추상 화 
 서비스 추상화 라고 하 면 트랜잭션 과 같이 기능 은 유사 하나 사용 방법 이 다른 로우 레벨 의 다양 한 기술 에 대해 추상 인터페이스 와 일관 성 있 는 접근 방법 을 제공 해 주 는 것 을 말 한다 . 반면 에 JavaMail 의 경우 처럼 테스트 를 어렵 게 만드 는 건전 하 지 않 은 방식 으로 설계 된 API 를 사용 할 때 도 유용 하 게 쓰일 수 있 다 . 
 서비스 추상화 란 이렇게 원활 한 테스트 만 을 위해서 도 충분히 가치 가 있 다 . 기술 이나 환경 이 바뀔 가능 성 이 있 음 에 도 , JavaMail 처럼 확장 이 불 가능 하 게 설계 해 놓 은 API 를 사용 해야 하 는 경우 라면 추상 화 계층 의 도입 을 적극 고려 해 볼 필요 가 있 다 . 
 외부 의 리소스 와 연동 하 는 대부분 의 작업 은 추상화 의 대상 이 될 수 있 다 . 
 테스트 대역 
 테스트 환경 에서 유용 하 게 사용 하 는 기법 이 있 다 . 대부분 테스트 할 대상 이 의존 하 고 있 는 오브젝트 를 DI 를 통해 바꿔치기 하 는 것 이 다 . 
 테스트 시 : DB 커넥션 풀 을 사용 하 지 않 은 단순 한 DataSource 인 SimpleDataSource 를 사용 
 테스트 시 : DummyMailSender 를 사용 
 UserServiceTest 의 관심사 는 UserService 에서 구현 해 놓 은 사용 자 정보 를 가공 히 는 비즈니스 로직 이 지 , 메일 이 어떻게 전송 이 될 것 인지 가 아니 기 때문 
 사용 할 오브젝트 를 교체 하 지 않 더라도 , 단지 테스트 만 을 위해서 도 Dl 는 유용 하 다 . 
 테스트 대역 : 테스트 용 으로 시용 되 는 특별 한 오브젝트 들 
 테스트 스 텁 : 테스트 스 텁 은 테스트 대상 오브젝트 의 의존 객체 로서 존재 하 면서 테스트 동안 에 코드 가 정상 적 으로 수행 할 수 있 도록 돕 는 것 을 말 한다 . 스 텁 에 미리 테스트 중 에 필요 한 정보 를 리턴 해 주 도록 만들 수 있 다 . 또는 어떤 스 텁 은 메소드 를 호출 하 면 강제 로 예외 를 발생 시키 게 해서 테스트 대상 오브젝트 가 예외 상황 에서 어떻게 반응 하 지 를 테스트 할 때 적용 할 수 도 있 다 . 
 목 오브젝트 : 테스트 대상 오브젝트 와 의존 오브젝트 사이 에서 일어나 는 일 을 검증 할 수 있 도록 특별히 설계 된 오브젝트 
 목 오브젝트 는 스 텁 처럼 테스트 오브젝트 가 정상 적 으로 실행 되 도록 도와 주 면서 , 테스트 오브젝트 와 자신 의 사이 에서 일어나 는 커뮤니케이션 내용 을 저장 해뒀 다가 테스트 결과 를 검증 하 는 데 활용 할 수 있 게 해준다 . 
 의존 오브젝트 를 얼마나 사용 했 는가 하 는 커뮤니케이션 행위 자체 에 관심 이 있 을 수 가 있 다 . 이럴 때 는 테스트 대상 과 의존 오브젝트 사이 에 주 고 받 는 정보 를 보존 해 두 는 기능 을 가진 테스트 용 의존 오브젝트 인 목 오브젝트 를 만들 어서 사용 해야 한다 . 
 테스트 대상 은 의존 오브젝트 에게 값 을 출력 하 기 도 하 고 값 을 입력 받 기 도 한다 . 출력 은 무시 한다고 칠 수 있 지만 , 간접 적 으로 테스트 대상 이 받 아야 할 입력 값 은 필요 하 다 . 이 를 위해 별도 로 준비 해 둔 스 텁 오브젝트 가 메소드 호출 시 특정 값 을 리턴 하 도록 만들 어 두 면 된다 . 
 아래 와 같이 MockMailSender 를 만든다 . MockMailSender 는 아무런 일 을 하 지 않 지만 , 메일 전송 요청 이 왔 을 때 수신자 메일 을 리스트 에 저장 해 둔다 . 
 DummyMailSender 대신 에 MockMailSender 를 사용 하 면 , 어떤 수신자 에게 메일 을 보냈 는지 확인 할 수 있 다 . 
 테스트 가 수행 될 수 있 도록 의존 오브젝트 에 간접 적 으로 입력 값 을 제공 해 주 는 스 텁 오브젝트 와 간접 적 인 출력 값 까지 확인 이 가능 한 목 오브젝트 , 이 두 가지 는 테스트 대역 의 가장 대표 적 인 방법 이 며 효과 적 인 테스트 코드 를 작성 하 는 데 빠질 수 없 는 중요 한 도구 다 . 
 비즈니스 로직 을 담 은 코드 는 데이터 액세스 로직 을 담 은 코드 와 깔끔 하 게 분리 되 는 것 이 바람직 하 다 . 비즈니스 로직 코드 또한 내부 적 으로 책임 과 역할 에 따라서 깔끔 하 게 메소드 로 정리 돼야 한다 . 
 트랜잭션 방법 에 따라 비즈니스 로직 을 담 은 코드 가 함께 변경 되 면 단일 책임 원칙 에 위배 되 며 , DAO 가 사용 하 는 특정 기술 에 대해 강한 결합 을 만들 어 낸다 . 
 서비스 추상 화 는 로우 레벨 의 트랜잭션 기술 과 API 의 변화 에 상관없이 일관 된 API 를 가진 추상 화 계층 을 도입 한다 . 
 서비스 추상 화 는 테스트 하 기 어려운 JavaMail 같 은 기술 에 도 적용 할 수 있 다 . 테스트 를 편리 하 게 작성 하 도록 도와 주 는 것 만 으로 도 서비스 추상 화 는 가치 가 었 다 . 
 테스트 대상 이 사용 하 는 의존 오브젝트 를 대체 할 수 있 도록 만든 오브젝트 를 테스트 대역 이 라고 한다 . 
 테스트 대역 중 에서 테스트 대상 으로부터 전달 받 은 정보 를 검증 할 수 있 도록 설계 된 것 을 목 오브젝트 라고 한다 .